Skip to content

Instantly share code, notes, and snippets.

@legraphista
Last active April 3, 2025 03:14
Show Gist options
  • Save legraphista/c7f11c29dcc415a309406ae6da941e6e to your computer and use it in GitHub Desktop.
Save legraphista/c7f11c29dcc415a309406ae6da941e6e to your computer and use it in GitHub Desktop.
nvidia driver with p2p support for rtx 4090
# step 0 - cleanup your existing drivers
sudo apt-get --purge remove "*nvidia*"
sudo apt-get --purge remove "*cuda*" "*cudnn*" "*cublas*" "*cufft*" "*cufile*" "*curand*" "*cusolver*" "*cusparse*" "*gds-tools*" "*npp*" "*nvjpeg*" "nsight*" "*nvvm*" "*libnccl*"

# step 0.1 - disable iommu
ll /sys/class/iommu/
# if this folder is empty, continue
# if the folder is not empty, see https://docs.dolphinics.com/latest/guides/iommu.html

sudo reboot

# step 1 - install drivers 
sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt-get update

# Search for available Nvidia drivers and install the latest version.
apt search --names-only nvidia-driver
sudo apt install nvidia-driver-560-open # or latest

sudo reboot

# step 1.1 - verify driver
nvidia-smi

# --- 

# step 2 - install cuda & cudnn
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update

apt search --names-only cuda-toolkit
sudo apt install cuda-toolkit-12-6 # or latest

apt search --names-only cudnn
sudo apt install cudnn9-cuda-12-6 # or latest

sudo reboot

# step 2.1 - verify driver
nvidia-smi

# --- 

# step 3 - make cuda samples
sudo apt install git cmake

# Clone the Nvidia CUDA samples repository to test CUDA installation.
cd ~
git clone https://github.com/nvidia/cuda-samples
cd cuda-samples

make -j `nproc`

# --- 

# step 4 - test p2p 
./bin/x86_64/linux/release/deviceQuery
# look for "> Peer access from NVIDIA GeForce RTX 4090 (GPU0) -> NVIDIA GeForce RTX 4090 (GPU1) : {Yes/No}" 
# if you see Yes - stop, you already have p2p
# if you see No - continue

# --- 

# step 5 - uninstall official driver (just the driver)
sudo apt remove nvidia-driver-550-open
sudo apt remove nvidia-dkms-550-open
sudo apt remove nvidia-driver-550-server-open
sudo apt remove nvidia-dkms-550-server-open

# step 5.1 - double check
sudo dpkg -l | grep nvidia
# verify that all nvidia drivers / dkms are uninstalled 

# --- 

# step 6 - install patched driver
cd ~
git clone [email protected]:tinygrad/open-gpu-kernel-modules.git
cd open-gpu-kernel-modules

# step 6.1 - Building your driver
nvidia-smi # note down driver version - you've uninstalled the driver, but it should still be loaded in memory, if not, use `dpkg -l | grep nvidia` to grab the version `560.35.03-0ubuntu1` => `560.35.03`

git remote add upstream [email protected]:NVIDIA/open-gpu-kernel-modules.git
git fetch --all

# rebase patch with git or your preferred GUI
git rebase -Xignore-space-change -i upstream/560.35.03 # your driver version - make sure tag exists - you might have to deal with a conflicting README


# step 6.2 - make driver
./install.sh
# make modules -j$(nproc)
# sudo checkinstall make modules_install -j$(nproc)
# name = nvidia-driver-550-open-patch-tinygrad
# version = {driver-version}-p2p

sudo depmod

sudo reboot

# step 6.3 verify driver
nvidia-smi

# --- 

# step 7 - test p2p
cd ~/cuda-samples
./bin/x86_64/linux/release/deviceQuery
# look for "> Peer access from NVIDIA GeForce RTX 4090 (GPU0) -> NVIDIA GeForce RTX 4090 (GPU1) : {Yes/No}" 
# if you see Yes - stop, you're done
# if you see No - go to troubleshooting

# --- 

# troubleshooting
see https://morgangiraud.medium.com/multi-gpu-nvidia-p2p-capabilities-and-debugging-tips-fb7597b4e2b5
see https://morgangiraud.medium.com/multi-gpu-tinygrad-patch-4904a75f8e16
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install torch plotly tqdm numpy matplotlib seaborn"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import gc\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[device(type='cuda', index=0),\n",
" device(type='cuda', index=1),\n",
" device(type='cuda', index=2),\n",
" device(type='cuda', index=3),\n",
" device(type='cuda', index=4),\n",
" device(type='cuda', index=5)]"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list_of_gpus = [torch.device(f\"cuda:{i}\") for i in range(torch.cuda.device_count())]\n",
"list_of_gpus"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cuda:0 -> cuda:3\n",
"cuda:1 -> cuda:4\n",
"cuda:5 -> cuda:3\n",
"cuda:0 -> cuda:1\n",
"cuda:4 -> cuda:5\n",
"cuda:2 -> cuda:1\n",
"cuda:4 -> cuda:0\n",
"cuda:5 -> cuda:1\n",
"cuda:3 -> cuda:0\n",
"cuda:1 -> cuda:5\n",
"cuda:3 -> cuda:2\n",
"cuda:4 -> cuda:1\n",
"cuda:5 -> cuda:0\n",
"cuda:4 -> cuda:2\n",
"cuda:0 -> cuda:5\n",
"cuda:3 -> cuda:1\n",
"cuda:2 -> cuda:0\n",
"cuda:4 -> cuda:3\n",
"cuda:1 -> cuda:0\n",
"cuda:2 -> cuda:4\n",
"cuda:3 -> cuda:5\n",
"cuda:1 -> cuda:2\n",
"cuda:0 -> cuda:4\n",
"cuda:2 -> cuda:5\n",
"cuda:3 -> cuda:4\n",
"cuda:5 -> cuda:2\n",
"cuda:1 -> cuda:3\n",
"cuda:5 -> cuda:4\n",
"cuda:2 -> cuda:3\n",
"cuda:0 -> cuda:2\n",
"All 30 GPU transfer combinations are present\n"
]
}
],
"source": [
"\n",
"speed_matrix = np.zeros((len(list_of_gpus), len(list_of_gpus)))\n",
"\n",
"test_pattern = []\n",
"for gpu1 in list_of_gpus:\n",
" for gpu2 in list_of_gpus:\n",
" if gpu1 == gpu2:\n",
" continue\n",
" test_pattern.append((gpu1, gpu2))\n",
"\n",
"# Shuffle and ensure no adjacent pairs share GPUs\n",
"np.random.shuffle(test_pattern)\n",
"i = 0\n",
"while i < len(test_pattern)-1:\n",
" gpu1, gpu2 = test_pattern[i]\n",
" next_gpu1, next_gpu2 = test_pattern[i+1]\n",
" \n",
" # If adjacent pairs share any GPU, swap with next non-conflicting pair\n",
" if gpu1 in (next_gpu1, next_gpu2) or gpu2 in (next_gpu1, next_gpu2):\n",
" for j in range(i+2, len(test_pattern)):\n",
" candidate_gpu1, candidate_gpu2 = test_pattern[j]\n",
" if gpu1 not in (candidate_gpu1, candidate_gpu2) and gpu2 not in (candidate_gpu1, candidate_gpu2):\n",
" test_pattern[i+1], test_pattern[j] = test_pattern[j], test_pattern[i+1]\n",
" break\n",
" i += 1\n",
"\n",
"\n",
"for gpu1, gpu2 in test_pattern:\n",
" print(f\"{gpu1} -> {gpu2}\")\n",
"\n",
"# Verify all GPU combinations are present\n",
"expected_combinations = set()\n",
"for gpu1 in list_of_gpus:\n",
" for gpu2 in list_of_gpus:\n",
" if gpu1 != gpu2:\n",
" expected_combinations.add((gpu1, gpu2))\n",
"\n",
"test_pattern_set = set(test_pattern)\n",
"\n",
"assert len(expected_combinations) == len(test_pattern_set), \"Some GPU combinations are missing\"\n",
"assert expected_combinations == test_pattern_set, \"Test pattern doesn't match expected GPU combinations\"\n",
"\n",
"print(f\"All {len(test_pattern)} GPU transfer combinations are present\")\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"GPU cuda:0 can access: GPU cuda:1: ✓, GPU cuda:2: ✓, GPU cuda:3: ✓, GPU cuda:4: ✓, GPU cuda:5: ✓, \n",
"GPU cuda:1 can access: GPU cuda:0: ✓, GPU cuda:2: ✓, GPU cuda:3: ✓, GPU cuda:4: ✓, GPU cuda:5: ✓, \n",
"GPU cuda:2 can access: GPU cuda:0: ✓, GPU cuda:1: ✓, GPU cuda:3: ✓, GPU cuda:4: ✓, GPU cuda:5: ✓, \n",
"GPU cuda:3 can access: GPU cuda:0: ✓, GPU cuda:1: ✓, GPU cuda:2: ✓, GPU cuda:4: ✓, GPU cuda:5: ✓, \n",
"GPU cuda:4 can access: GPU cuda:0: ✓, GPU cuda:1: ✓, GPU cuda:2: ✓, GPU cuda:3: ✓, GPU cuda:5: ✓, \n",
"GPU cuda:5 can access: GPU cuda:0: ✓, GPU cuda:1: ✓, GPU cuda:2: ✓, GPU cuda:3: ✓, GPU cuda:4: ✓, \n"
]
}
],
"source": [
"for gpu1 in list_of_gpus:\n",
" print(f\"GPU {gpu1} can access: \", end=\"\")\n",
" for gpu2 in list_of_gpus:\n",
" if gpu1 == gpu2:\n",
" continue\n",
" can_access = torch.cuda.can_device_access_peer(gpu1, gpu2)\n",
" print(f\"GPU {gpu2}: {'✓' if can_access else '✗'}, \", end=\" \")\n",
" print()\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
" 0%| | 0/30 [00:00<?, ?it/s]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:0 to cuda:3\n",
" Generating random tensor for cuda:0\n",
" Copying to cuda:0\n",
" Copying from cuda:0 to cuda:3\n",
" Done in 0.08 seconds at 26.16 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 3%|▎ | 1/30 [00:04<02:22, 4.92s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:1 to cuda:4\n",
" Generating random tensor for cuda:1\n",
" Copying to cuda:1\n",
" Copying from cuda:1 to cuda:4\n",
" Done in 0.08 seconds at 26.15 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 7%|▋ | 2/30 [00:09<02:20, 5.00s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:5 to cuda:3\n",
" Generating random tensor for cuda:5\n",
" Copying to cuda:5\n",
" Copying from cuda:5 to cuda:3\n",
" Done in 0.08 seconds at 26.12 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 10%|█ | 3/30 [00:14<02:13, 4.95s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:0 to cuda:1\n",
" Generating random tensor for cuda:0\n",
" Copying to cuda:0\n",
" Copying from cuda:0 to cuda:1\n",
" Done in 0.08 seconds at 26.16 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 13%|█▎ | 4/30 [00:19<02:06, 4.88s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:4 to cuda:5\n",
" Generating random tensor for cuda:4\n",
" Copying to cuda:4\n",
" Copying from cuda:4 to cuda:5\n",
" Done in 0.08 seconds at 26.13 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 17%|█▋ | 5/30 [00:24<02:01, 4.86s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:2 to cuda:1\n",
" Generating random tensor for cuda:2\n",
" Copying to cuda:2\n",
" Copying from cuda:2 to cuda:1\n",
" Done in 0.08 seconds at 26.11 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 20%|██ | 6/30 [00:29<01:59, 4.97s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:4 to cuda:0\n",
" Generating random tensor for cuda:4\n",
" Copying to cuda:4\n",
" Copying from cuda:4 to cuda:0\n",
" Done in 0.08 seconds at 26.15 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 23%|██▎ | 7/30 [00:34<01:55, 5.01s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:5 to cuda:1\n",
" Generating random tensor for cuda:5\n",
" Copying to cuda:5\n",
" Copying from cuda:5 to cuda:1\n",
" Done in 0.08 seconds at 26.08 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 27%|██▋ | 8/30 [00:39<01:50, 5.01s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:3 to cuda:0\n",
" Generating random tensor for cuda:3\n",
" Copying to cuda:3\n",
" Copying from cuda:3 to cuda:0\n",
" Done in 0.08 seconds at 26.08 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 30%|███ | 9/30 [00:44<01:45, 5.01s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:1 to cuda:5\n",
" Generating random tensor for cuda:1\n",
" Copying to cuda:1\n",
" Copying from cuda:1 to cuda:5\n",
" Done in 0.08 seconds at 26.12 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 33%|███▎ | 10/30 [00:49<01:40, 5.01s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:3 to cuda:2\n",
" Generating random tensor for cuda:3\n",
" Copying to cuda:3\n",
" Copying from cuda:3 to cuda:2\n",
" Done in 0.08 seconds at 26.15 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 37%|███▋ | 11/30 [00:54<01:35, 5.01s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:4 to cuda:1\n",
" Generating random tensor for cuda:4\n",
" Copying to cuda:4\n",
" Copying from cuda:4 to cuda:1\n",
" Done in 0.08 seconds at 26.04 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 40%|████ | 12/30 [00:59<01:30, 5.01s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:5 to cuda:0\n",
" Generating random tensor for cuda:5\n",
" Copying to cuda:5\n",
" Copying from cuda:5 to cuda:0\n",
" Done in 0.08 seconds at 26.06 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 43%|████▎ | 13/30 [01:04<01:25, 5.00s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:4 to cuda:2\n",
" Generating random tensor for cuda:4\n",
" Copying to cuda:4\n",
" Copying from cuda:4 to cuda:2\n",
" Done in 0.08 seconds at 26.13 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 47%|████▋ | 14/30 [01:09<01:20, 5.00s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:0 to cuda:5\n",
" Generating random tensor for cuda:0\n",
" Copying to cuda:0\n",
" Copying from cuda:0 to cuda:5\n",
" Done in 0.08 seconds at 26.08 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 50%|█████ | 15/30 [01:14<01:15, 5.00s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:3 to cuda:1\n",
" Generating random tensor for cuda:3\n",
" Copying to cuda:3\n",
" Copying from cuda:3 to cuda:1\n",
" Done in 0.08 seconds at 26.01 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 53%|█████▎ | 16/30 [01:19<01:09, 4.99s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:2 to cuda:0\n",
" Generating random tensor for cuda:2\n",
" Copying to cuda:2\n",
" Copying from cuda:2 to cuda:0\n",
" Done in 0.08 seconds at 26.04 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 57%|█████▋ | 17/30 [01:24<01:04, 4.99s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:4 to cuda:3\n",
" Generating random tensor for cuda:4\n",
" Copying to cuda:4\n",
" Copying from cuda:4 to cuda:3\n",
" Done in 0.08 seconds at 26.08 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 60%|██████ | 18/30 [01:29<00:59, 4.98s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:1 to cuda:0\n",
" Generating random tensor for cuda:1\n",
" Copying to cuda:1\n",
" Copying from cuda:1 to cuda:0\n",
" Done in 0.08 seconds at 25.94 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 63%|██████▎ | 19/30 [01:34<00:54, 4.99s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:2 to cuda:4\n",
" Generating random tensor for cuda:2\n",
" Copying to cuda:2\n",
" Copying from cuda:2 to cuda:4\n",
" Done in 0.08 seconds at 26.12 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 67%|██████▋ | 20/30 [01:39<00:49, 4.99s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:3 to cuda:5\n",
" Generating random tensor for cuda:3\n",
" Copying to cuda:3\n",
" Copying from cuda:3 to cuda:5\n",
" Done in 0.08 seconds at 25.99 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 70%|███████ | 21/30 [01:44<00:45, 5.02s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:1 to cuda:2\n",
" Generating random tensor for cuda:1\n",
" Copying to cuda:1\n",
" Copying from cuda:1 to cuda:2\n",
" Done in 0.08 seconds at 26.08 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 73%|███████▎ | 22/30 [01:49<00:40, 5.02s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:0 to cuda:4\n",
" Generating random tensor for cuda:0\n",
" Copying to cuda:0\n",
" Copying from cuda:0 to cuda:4\n",
" Done in 0.08 seconds at 26.08 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 77%|███████▋ | 23/30 [01:54<00:35, 5.03s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:2 to cuda:5\n",
" Generating random tensor for cuda:2\n",
" Copying to cuda:2\n",
" Copying from cuda:2 to cuda:5\n",
" Done in 0.08 seconds at 26.01 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 80%|████████ | 24/30 [01:59<00:30, 5.04s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:3 to cuda:4\n",
" Generating random tensor for cuda:3\n",
" Copying to cuda:3\n",
" Copying from cuda:3 to cuda:4\n",
" Done in 0.08 seconds at 26.04 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 83%|████████▎ | 25/30 [02:04<00:25, 5.03s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:5 to cuda:2\n",
" Generating random tensor for cuda:5\n",
" Copying to cuda:5\n",
" Copying from cuda:5 to cuda:2\n",
" Done in 0.08 seconds at 26.04 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 87%|████████▋ | 26/30 [02:09<00:20, 5.03s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:1 to cuda:3\n",
" Generating random tensor for cuda:1\n",
" Copying to cuda:1\n",
" Copying from cuda:1 to cuda:3\n",
" Done in 0.08 seconds at 25.97 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 90%|█████████ | 27/30 [02:15<00:15, 5.04s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:5 to cuda:4\n",
" Generating random tensor for cuda:5\n",
" Copying to cuda:5\n",
" Copying from cuda:5 to cuda:4\n",
" Done in 0.08 seconds at 25.94 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 93%|█████████▎| 28/30 [02:20<00:10, 5.03s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:2 to cuda:3\n",
" Generating random tensor for cuda:2\n",
" Copying to cuda:2\n",
" Copying from cuda:2 to cuda:3\n",
" Done in 0.09 seconds at 25.11 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 97%|█████████▋| 29/30 [02:25<00:05, 5.02s/it]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing cuda:0 to cuda:2\n",
" Generating random tensor for cuda:0\n",
" Copying to cuda:0\n",
" Copying from cuda:0 to cuda:2\n",
" Done in 0.08 seconds at 25.92 GB/s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 30/30 [02:30<00:00, 5.00s/it]\n"
]
}
],
"source": [
"\n",
"import time\n",
"from tqdm import tqdm\n",
"\n",
"\n",
"for gpu1, gpu2 in tqdm(test_pattern):\n",
" print(f\"Testing {gpu1} to {gpu2}\")\n",
"\n",
" gc.collect()\n",
" torch.cuda.empty_cache()\n",
"\n",
" # Warm up transfer\n",
" warm_up = torch.randn(1024, 1024, device=gpu1)\n",
" _ = warm_up.to(gpu2)\n",
" torch.cuda.synchronize(gpu1)\n",
" torch.cuda.synchronize(gpu2)\n",
" del warm_up\n",
"\n",
" # 8gb\n",
" print(f\" Generating random tensor for {gpu1}\")\n",
" random_tensor = torch.tensor(np.random.randint(0, 100, (1024, 1024, 1024), dtype=np.int16))\n",
"\n",
" # copy from CPU to GPU 1\n",
" print(f\" Copying to {gpu1}\")\n",
" gpu1_tensor = random_tensor.to(gpu1)\n",
"\n",
" # copy from GPU 1 to GPU 2\n",
" _start = time.perf_counter()\n",
" print(f\" Copying from {gpu1} to {gpu2}\")\n",
" gpu2_tensor = gpu1_tensor.to(gpu2)\n",
" torch.cuda.synchronize(gpu1)\n",
" torch.cuda.synchronize(gpu2)\n",
" _end = time.perf_counter()\n",
"\n",
" _total_time = _end - _start\n",
" _bytes_moved = random_tensor.numel() * random_tensor.element_size()\n",
" _speed_GBs = _bytes_moved / _total_time / 1e9\n",
" print(f\" Done in {_end - _start:.2f} seconds at {_speed_GBs:.2f} GB/s\")\n",
"\n",
" speed_matrix[gpu1.index, gpu2.index] = _speed_GBs\n",
"\n",
" # pull tensor back to CPU\n",
" cpu_tensor = gpu2_tensor.to(\"cpu\")\n",
"\n",
" # check if the tensors are equal\n",
" if not torch.equal(random_tensor, cpu_tensor):\n",
" print(f\"GPU tensors are not equal\")\n",
"\n",
" del gpu2_tensor\n",
" del gpu1_tensor\n",
" del random_tensor\n",
"\n",
"gc.collect()\n",
"torch.cuda.empty_cache()\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAArIAAAJOCAYAAABLKeTiAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAa0JJREFUeJzt3XlYVeXax/HfZp5BQQETBUUxp3IuLTWnTHtNmzxmGR5TO2KZ5kRZDpVo07HUbDpqpTbbMe1k5mypmQqZphTOs2YCjiDs9f7hcZ92DLJhD2z8fq5rXZes4Vn3ekK7ubn3s0yGYRgCAAAA3IyHqwMAAAAASoNEFgAAAG6JRBYAAABuiUQWAAAAbolEFgAAAG6JRBYAAABuiUQWAAAAbolEFgAAAG6JRBYAAABuiUQWAFxk6dKluvHGG+Xn5yeTyaTMzExXh1RuxcbGKjEx0dVhAChnSGSBUti7d6+GDh2qunXrKiAgQAEBAapfv76SkpK0bds2q3MnTJggk8lk2a6cO27cOGVnZxc47/fffy/0ng0bNlT79u2vGtvkyZP173//uyyPV6ScnBxNnz5dt9xyiypVqiQfHx9Vq1ZNPXr00Icffqj8/HzLufv27bN6bk9PT9WoUUO9evVSWlpagfNefvnlQu/58ssvy2Qyad++fYUe/+t9ituKGsMVTp06pfvvv1/+/v6aOXOmPvjgAwUGBjr0nvv27VP//v1Vu3Zt+fn5KSoqSm3bttX48eMdel8AcBQvVwcAuJslS5aod+/e8vLyUt++fXXDDTfIw8NDu3bt0sKFCzVr1izt3btXNWvWtLpu1qxZCgoK0tmzZ7Vs2TK98MILWrlypb7//nuZTCa7xTd58mTde++96tmzp93GlKSTJ0/qjjvu0JYtW3T77bdr3Lhxqly5so4dO6bly5frgQceUEZGhp555hmr6/r06aNu3bopPz9fO3fu1KxZs/T1119r48aNuvHGG8scV5UqVfTBBx9Y7XvllVd06NAh/fOf/yxwbnnx448/6syZM3ruuefUqVMnh98vIyNDLVq0kL+/v/7+978rNjZWR48e1datWzV16lRNnDjR4TEAgL2RyAI22L17t/72t7+pZs2aWrFihaKjo62OT506VW+88YY8PAr+suPee+9VRESEJOnRRx/VPffco4ULF2rjxo26+eabnRJ/WTz00ENKTU3V559/rrvvvtvqWHJysjZv3qz09PQC1zVt2lQPPvig5es2bdqoR48emjVrlt56660yxxUYGGg1viR99NFHOn36dIH9f2YYhi5evCh/f/8yx1AaJ06ckCSFhYXZbcxz584VWdX95z//qbNnzyotLa3AD1lXYgEAd0NrAWCDF198UefOndOcOXMKJLGS5OXlpccff1wxMTFXHatDhw6SLrcp2IvJZNK5c+f03nvvWX6d/ue+wtTUVN1xxx0KCQlRUFCQOnbsqI0bN1513A0bNuibb77RoEGDCiSxVzRv3lx9+/a96liOeO6SiI2N1Z133qlvvvlGzZs3l7+/vyWRnjNnjjp06KCqVavK19dX9evX16xZs4oc47vvvlPLli3l5+enWrVq6f3337c679KlS5o4caLq1KkjPz8/hYeH65ZbbtG3334rSWrfvr0efvhhSVKLFi0K/Hf64Ycf1LVrV4WGhiogIEDt2rXT999/b3WPK60ov/zyix544AFVqlRJt9xyS5HPv3v3blWvXr1AEitJVatWLfQ5ly1bZunhrV+/vhYuXFjg2szMTD3xxBOKiYmRr6+v4uPjNXXqVJnNZqvzzGazpk2bpgYNGsjPz0+RkZEaPHiwTp8+bXWeYRh6/vnnVb16dQUEBOi2227Tjh07Ctz3anMM4NpARRawwZIlSxQfH69WrVqVeazdu3dLksLDw8s81hUffPCBHnnkEbVs2VKDBg2SJNWuXVuStGPHDt16660KCQnR6NGj5e3trbfeekvt27fXmjVrin2mxYsXS1KxFc6ScsRzl1R6err69OmjwYMHa+DAgUpISJB0ue2jQYMG6tGjh7y8vLR48WINGTJEZrNZSUlJVmNkZGTo3nvv1YABA/Twww9r9uzZSkxMVLNmzdSgQQNJl5PMlJQUy3+L7Oxsbd68WVu3blXnzp319NNPKyEhQW+//bYmTZqkuLg4y3+nlStX6o477lCzZs00fvx4eXh4WBLtdevWqWXLllbx3HfffapTp44mT54swzCKfPaaNWtq+fLlWrlypeWHieL89ttv6t27tx599FE9/PDDmjNnju677z4tXbpUnTt3liSdP39e7dq10+HDhzV48GDVqFFD69evV3Jyso4ePapp06ZZxhs8eLDmzp2r/v376/HHH9fevXs1Y8YMpaam6vvvv5e3t7ck6dlnn9Xzzz+vbt26qVu3btq6dau6dOmi3Nxcq/iuNscArhEGgBLJysoyJBk9e/YscOz06dPGyZMnLdv58+ctx8aPH29IMtLT042TJ08ae/fuNd566y3D19fXiIyMNM6dO2d13smTJwu9f4MGDYx27dpdNc7AwEDj4YcfLrC/Z8+eho+Pj7F7927LviNHjhjBwcFG27Ztix2zV69ehiQjMzPTav+FCxesnvv06dOWY3v37jUkGRMnTjROnjxpHDt2zFi9erXRpEkTQ5Lx+eefW5330ksvFXrvl156yZBk7N2796rPfkX37t2NmjVrWu2rWbOmIclYunRpgfP//N/rittvv92oVatWoWOsXbvWsu/EiROGr6+v8eSTT1r23XDDDUb37t2LjXHOnDmGJOPHH3+07DObzUadOnWM22+/3TCbzVbxxcXFGZ07d7bsu/L90qdPn2Lvc8X27dsNf39/Q5Jx4403GsOGDTP+/e9/W77/CnvOK/+NDOPy9390dLTRpEkTy77nnnvOCAwMNH799Ver68eOHWt4enoaBw4cMAzDMNatW2dIMubPn2913tKlS632nzhxwvDx8TG6d+9u9fxPPfWUIcnq+7okcwyg4qO1ACihKysMBAUFFTjWvn17ValSxbLNnDmzwDkJCQmqUqWK4uLiNHjwYMXHx+urr75SQECAw2PPz8/XsmXL1LNnT9WqVcuyPzo6Wg888IC+++47qxUU/qqoZ3/zzTetnruwX22PHz9eVapUUVRUlNq3b6/du3dr6tSpRbYoOFJcXJxuv/32Avv/3CeblZWl33//Xe3atdOePXuUlZVldW79+vV16623Wr6uUqWKEhIStGfPHsu+sLAw7dixQ7/99ptN8aWlpem3337TAw88oFOnTun333/X77//rnPnzqljx45au3ZtgV/ZP/rooyUau0GDBkpLS9ODDz6offv26bXXXlPPnj0VGRmpd955p8D51apVU69evSxfh4SEqF+/fkpNTdWxY8ckSZ9++qluvfVWVapUyRLr77//rk6dOik/P19r1661nBcaGqrOnTtbndesWTMFBQVp1apVkqTly5crNzdXjz32mNUHIJ944okC8ZV2jgFULLQWACUUHBwsSTp79myBY2+99ZbOnDmj48ePF/nr988//1whISHy9vZW9erVLb9KtkVpVzc4efKkzp8/b/lV+p9df/31MpvNOnjwoOVX43/152cPDQ217L/nnnvUsGFDSdKTTz5ptfzWFYMGDdJ9990nDw8PhYWFqUGDBvL19bX5GeyxskNcXFyh+7///nuNHz9eGzZs0Pnz562OZWVlWT1zjRo1ClxfqVIlq17PSZMm6a677lLdunXVsGFDde3aVQ899JAaN25cbHxXkrIr/bOFycrKUqVKla76TIWpW7euPvjgA+Xn5+uXX37RkiVL9OKLL2rQoEGKi4uzWj0hPj6+wJzXrVtX0uVlvKKiovTbb79p27ZtRa4GceVDZL/99puysrIK9OL+9bz9+/dLkurUqWN1vEqVKlbPLJV+jgFULCSyQAmFhoYqOjpa27dvL3DsSn9pceuUtm3b1rJqQWH8/PwkSRcuXCj0+Pnz5y3nOFu9evUkSdu3b1ebNm0s+2NiYiwfbLtSlfurOnXqFLu8VEme+8/nlUVhKxTs3r1bHTt2VL169fTqq68qJiZGPj4++s9//qN//vOfBSqgnp6ehY5t/Kk/tW3bttq9e7cWLVqkZcuW6d1339U///lPvfnmm3rkkUeKjO/KvV566aUilyb7a1W8NKsueHp6qlGjRmrUqJFuvvlm3XbbbZo/f77Ny4CZzWZ17txZo0ePLvT4lcTXbDaratWqmj9/fqHnlWZZtNLOMYCKhUQWsEH37t317rvvatOmTQU+dFNWVz5Nnp6eXmDVg/Pnz+vgwYPq0qXLVccprHJZpUoVBQQEFLo81q5du+Th4VHsSgt33nmnpkyZovnz51slsvZQXGzS5fkICAgo9oeAsli8eLFycnL05ZdfWlVbr/y6u7QqV66s/v37q3///jp79qzatm2rCRMmFJtkXanSh4SEOGVtWenyahOSdPToUav9GRkZMgzD6vvp119/lXR5VQPpcrxnz569aqy1a9fW8uXL1aZNm2IT7yt/B3777TerFpiTJ08WWN1AKt0cA6hY6JEFbDB69GgFBATo73//u44fP17guFHMp8avpmPHjvLx8dGsWbMKVAHffvtt5eXl6Y477rjqOIGBgQVederp6akuXbpo0aJFVlXj48ePa8GCBbrlllsUEhJS5Jht2rRR586d9fbbb2vRokWFnlPaZ78S2+LFi3XgwAGrYwcOHNDixYvVpUuXIiuhZXVl3D/Hn5WVpTlz5pR6zFOnTll9HRQUpPj4eOXk5BR7XbNmzVS7dm29/PLLhbawnDx5stQxrVu3TpcuXSqw/z//+Y8kFWg7OXLkiL744gvL19nZ2Xr//fd14403KioqSpJ0//33W5Zm+6vMzEzl5eVZzsvPz9dzzz1X4Ly8vDzL92unTp3k7e2t6dOnW/33+PPqB1eUdo4BVCxUZAEb1KlTRwsWLFCfPn2UkJBgebOXYRjau3evFixYIA8PD1WvXt3msatWrapnn31W48aNU9u2bdWjRw8FBARo/fr1+vDDD9WlSxf93//931XHadasmZYvX65XX31V1apVU1xcnFq1aqXnn39e3377rW655RYNGTJEXl5eeuutt5STk6MXX3zxquPOmzdPXbt2Vc+ePXXHHXeoU6dOqlSpkuXNXmvXri1Rol2YyZMn66abblLTpk01aNAgxcbGat++fXr77bdlMpk0efLkUo1bEl26dJGPj4/+7//+T4MHD9bZs2f1zjvvqGrVqgWqlCVVv359tW/fXs2aNVPlypW1efNmffbZZxo6dGix13l4eOjdd9/VHXfcoQYNGqh///667rrrdPjwYa1atUohISGWpdBsNXXqVG3ZskV33323pY9069atev/991W5cuUCH6iqW7euBgwYoB9//FGRkZGaPXu2jh8/bpXgjxo1Sl9++aXuvPNOyxJk586d088//6zPPvtM+/btU0REhNq1a6fBgwcrJSVFaWlp6tKli7y9vfXbb7/p008/1WuvvaZ7771XVapU0ciRI5WSkqI777xT3bp1U2pqqr7++usCFfnSzjGACsaFKyYAbisjI8P4xz/+YcTHxxt+fn6Gv7+/Ua9ePePRRx810tLSrM692rJafzVv3jzjpptuMgIDAw1fX1+jXr16xsSJE42LFy+W6Ppdu3YZbdu2tSy19Ocli7Zu3WrcfvvtRlBQkBEQEGDcdtttxvr160v83BcuXDCmTZtm3HzzzUZISIjh5eVlREVFGXfeeacxf/58Iy8vz3Lu1ZbV+qudO3cavXv3NqpWrWp4eXkZVatWNf72t78ZO3fuLHF8VxS1/FZRyzV9+eWXRuPGjQ0/Pz8jNjbWmDp1qjF79uwCy34VNUa7du2slkZ7/vnnjZYtWxphYWGW740XXnjByM3NtZxT2PJbV6Smphp33323ER4ebvj6+ho1a9Y07r//fmPFihWWc2z9vvr++++NpKQko2HDhkZoaKjh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Tk6OsrOzrTYAAAA4V4krskUla4GBgfL09LRbQJs2bZLZbFbfvn2Vk5NjdWzMmDEaN26cLl68qKCgIE2ZMkXdu3e3270PHz6srl276r777tPAgQOLPC8lJUUTJ060230BAEAFQhuA05S4IhsWFqZKlSoV2Pz9/ZWQkKB33nnHphvHx8fLZDIpPT3dan+tWrUUHx8vf3//AteMGjVKaWlpOnTokE6fPm1VsQ0JCVFWVlaBazIzM+Xp6Vlk+8IVR44c0W233abWrVvr7bffLvbc5ORkZWVlWbaDBw8Wez4AAADsr8QV2VWrVhW6PzMzU1u2bNGoUaPk5eWl/v37l2i88PBwde7cWTNmzNBjjz121URTutx2EB8fX+ixhIQEffTRR8rJyZGvr69l/9atWxUXFydvb+8ixz18+LBuu+02NWvWTHPmzJGHR/H5va+vr9U9AAAAJHpkna3EiWy7du2KPHbXXXcpNjZW06dPL3EiK0lvvPGG2rRpo+bNm2vChAlq3LixPDw89OOPP2rXrl1q1qxZicfq27evJk2apH79+mn06NEKDQ3V2rVrNW3aNL344otFXnf48GG1b99eNWvW1Msvv6yTJ09ajkVFRZX4/gAAAHAuu61a0K5dOz3xxBM2XVO7dm2lpqZq8uTJSk5O1qFDh+Tr66v69etr5MiRGjJkSInHCgsL07p16zR27Fj16NFDWVlZio+P16uvvqoBAwYUed23336rjIwMZWRkqHr16lbH/rr8FwAAQLFYfsup7JbIZmVlKTQ01ObroqOjNX36dE2fPr3Y8/bt23fVserWrauFCxfadP/ExEQlJibadA0AAABczy6J7KVLl/TSSy+pVatW9hgOAADATZn+uzliXPxViRPZu+++u9D9WVlZ2rFjh0wmk9atW2e3wAAAAIDilDiRLaptICYmRvfcc4/69u1bqtYCAACACoMeWacqcSI7Z84cR8YBAAAA2MSmHtmNGzdq8eLFys3NVceOHdW1a1dHxQUAAOB+qMg6VYkT2c8++0y9e/eWv7+/vL299eqrr2rq1KkaOXKkI+MDAAAAClXiV9SmpKRo4MCBysrK0unTp/X8889r8uTJjowNAADAvRgmx20ooMSJbHp6ukaOHClPT09J0pNPPqkzZ87oxIkTDgsOAADAnRiG4zYUVOJE9vz58woJCbF87ePjIz8/P509e9YhgQEAAADFsenDXu+++66CgoIsX+fl5Wnu3LmKiIiw7Hv88cftFx0AAIA74cNeTlXiRLZGjRp65513rPZFRUXpgw8+sHxtMplIZAEAAOAUJU5k9+3b58AwAAAAKgBHfTCLD3sVqsQ9sgAAAEB5UuKK7IULF7RixQrdeeedkqTk5GTl5ORYjnt6euq5556Tn5+f/aMEAABwAybj8uaIcVFQiRPZ9957T1999ZUlkZ0xY4YaNGggf39/SdKuXbtUrVo1DR8+3DGRAgAAAH9S4taC+fPna9CgQVb7FixYoFWrVmnVqlV66aWX9Mknn9g9QAAAALdhOHBDASVOZDMyMtSoUSPL135+fvLw+N/lLVu21C+//GLf6AAAAIAilLi1IDMz06on9uTJk1bHzWaz1XEAAIBrDqsWOFWJK7LVq1fX9u3bizy+bds2Va9e3S5BAQAAAFdT4kS2W7duevbZZ3Xx4sUCxy5cuKCJEyeqe/fudg0OAADArdAj61Qlbi146qmn9MknnyghIUFDhw5V3bp1JUnp6emaMWOG8vLy9NRTTzksUAAAgHKPV9QW68KFCzIMQwEBAZKk/fv364svvlD9+vXVpUsXm8crcSIbGRmp9evX6x//+IfGjh0rw7g8oyaTSZ07d9Ybb7yhyMhImwMAAADAteGuu+7S3XffrUcffVSZmZlq1aqVvL299fvvv+vVV1/VP/7xD5vGs+nNXnFxcVq6dKlOnjypjRs3auPGjTp58qSWLl2qWrVq2XRjAACACofWgmJt3bpVt956qyTps88+U2RkpPbv36/3339fr7/+us3jlbgi+2eVK1dWy5YtS3MpAAAArlHnz59XcHCwJGnZsmW6++675eHhoZtuukn79++3eTybKrIAAAAoxpXltxyxVQDx8fH697//rYMHD+qbb76x9MWeOHFCISEhNo9HIgsAAACnePbZZzVy5EjFxsaqVatWuvnmmyVdrs42adLE5vFK1VoAAACAgkzG5c0R41YE9957r2655RYdPXpUN9xwg2V/x44d1atXL5vHI5EFAACAQ9WoUUM9evRQjx491KFDB0VFRVkdL+1nr0rVWvDBBx+oTZs2qlatmqUxd9q0aVq0aFGpggAAAKgQWLWgUB988IF8fX2VlJSkiIgI9e7dW/Pnz1dmZmaZxrU5kZ01a5ZGjBihbt26KTMzU/n5+ZKksLAwTZs2rUzBAAAAoOJp166dXnnlFf3222/6/vvvdeONN2r69OmKiopShw4dNG3aNO3Zs8fmcW1OZKdPn6533nlHTz/9tDw9PS37mzdvrp9//tnmAAAAAHDtaNCggZKTk7Vx40bt3btXffr00YoVK9SwYUM1bNhQX331VYnHsrlHdu/evYV+qszX11fnzp2zdTgAAABco6KjozVw4EANHDhQ58+f1zfffCNfX98SX29zIhsXF6e0tDTVrFnTav/SpUt1/fXX2zocAABAhWGSg1YtsP+QLmcYhlatWqULFy6odevWqlSpks0rF9icyI4YMUJJSUm6ePGiDMPQpk2b9OGHHyolJUXvvvuurcNVKFU9zyjIk6V5S2PleV5xXFp9jzJ3ZfGt2faeLPzP4j2NXB2CW8vKD3R1CG7pYn6eq0OAjTIzMzVs2DBt3bpVN910k1555RV169ZN69evlyRVrVpVy5YtU+PGjW0a1+ZE9pFHHpG/v7/GjRun8+fP64EHHlC1atX02muv6W9/+5utwwEAAFQcjnoLl5u/2WvkyJHasGGDHn74YS1evFhdu3aVYRjasGGDPDw8NHr0aD399NNavHixTeOWah3Zvn37qm/fvjp//rzOnj2rqlWrlmYYAACAisVRS2W5+fJbX3/9tRYsWKB27dopMTFRMTExWrlypVq1aiVJmjp1qnr06GHzuKX6sFdeXp7q1KmjgIAABQQESJJ+++03eXt7KzY21uYgAAAAUHEdP35cdevWlSRdd9118vPzU0xMjOV4jRo1dPLkSZvHtbmhMzEx0dLP8Gc//PCDEhMTbQ4AAACgwuCFCIUym81Wy7Z6enrKZPpfu8Sf/2wLmyuyqampatOmTYH9N910k4YOHVqqIAAAAFCxvfvuuwoKCpIk5eXlae7cuYqIiJAknTlzplRj2pzImkymQm+WlZVlecsXAADAtchkOGj5LTevyNaoUUPvvPOO5euoqCh98MEHBc6xlc2JbNu2bZWSkqIPP/zQUiLOz89XSkqKbrnlFpsDAAAAQMW2b98+h4xrcyI7ZcoUtWvXTgkJCbr11lslSevWrVN2drZWrlxp9wABAADcBqsWFOrixYtavny57rzzTklScnKycnJyLMe9vLw0adIk+fn52TSuzR/2atCggbZt26b7779fJ06c0JkzZ9SvXz/t2rVLDRs2tHU4AAAAVHBz587VW2+9Zfl6xowZWr9+vVJTU5WamqoPPvhAb7zxhs3j2lSRvXTpkrp27ao333xTkydPtvlmAAAAFRoV2ULNnz9fo0ePttq3YMEC1ap1+e2U8+bN08yZMzVixAibxrWpIuvt7a1t27bZdAMAAABc2zIyMtSo0f9eae3n5ycPj/+loS1bttQvv/xi87g2txY8+OCD+te//mXzjQAAACq6K6sWOGJzZ5mZmVY9sSdPnrR6iZbZbLY6XlI2J7J5eXmaNWuWmjdvrsGDB2vEiBFWGwAAAFwnJSVFLVq0UHBwsKpWraqePXsqPT3d6pz27dvLZDJZbY8++uhVx965c6d69Oih0NBQBQYGqkWLFjpw4MBVr6tevbq2b99e5PFt27apevXqV3+4v7B51YLt27eradOmkqRff/3V6lhp38oAAABQIRimy5sjxi2hNWvWKCkpSS1atFBeXp6eeuopdenSRb/88osCAwMt5w0cOFCTJk2yfB0QEFDsuLt379Ytt9yiAQMGaOLEiQoJCdGOHTtKtNJAt27d9Oyzz6p79+4Fzr9w4YImTpyo7t27l/gZr7A5kV21apXNNwEAAIBzLF261OrruXPnqmrVqtqyZYvatm1r2R8QEKCoqKgSj/v000+rW7duevHFFy37ateuXaJrn3rqKX3yySdKSEjQ0KFDVbduXUlSenq6ZsyYYUm4bWVzawEAAACKYDhwk5SdnW21laSvNCsrS5JUuXJlq/3z589XRESEGjZsqOTkZJ0/f77IMcxms7766ivVrVtXt99+u6pWrapWrVrp3//+dwkmRYqMjNT69et1/fXXa+zYserVq5d69eql5ORk1a9fX999950iIyNLNNaf2VyRve2224ptIeClCAAA4Frl6FfUxsTEWO0fP368JkyYUOR1ZrNZTzzxhNq0aWO13v8DDzygmjVrqlq1atq2bZvGjBmj9PR0LVy4sNBxTpw4obNnz2rKlCl6/vnnNXXqVC1dulR33323Vq1apXbt2l31GeLi4rR06VL98ccfysjIkCTFx8cXSLBtYXMie+ONN1p9fenSJaWlpWn79u16+OGHSx0IAAAAinfw4EGFhIRYvvb19S32/KSkJG3fvl3fffed1f5BgwZZ/tyoUSNFR0erY8eO2r17d6HtAmazWZJ01113afjw4ZIu54Tr16/Xm2++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i9q9e7eCg4N1yy23aMCAAYqPj3d1aOUec1c2zF/ZMH9lw/yVHnMH2IZE1sF4fWDpMXdlw/yVDfNXNsxf6TF3QMnxYS8Hu9KoL13+JCpKjrkrG+avbJi/smH+So+5A0qOiiwAAADcEhVZAAAAuCUSWQAAALglElkAAAC4JRJZAAAAuCUSWQAAALglElkAAAC4JRJZAAAAuCUSWQDlWmxsrKZNm+bw++zbt08mk0lpaWkOvxcAwD5IZAEUKzExUSaTSSaTSd7e3oqMjFTnzp01e/Zsmc1mu91n7ty5CgsLK7D/xx9/1KBBg+x2H+nyM/Xs2dNqX0xMjI4ePaqGDRva9V6Fyc7O1jPPPKMGDRrI399f4eHhatGihV588UWdPn3acl779u0tc+/n56f69evrjTfesByfMGGCbrzxxgLjk5QDuFaQyAK4qq5du+ro0aPat2+fvv76a912220aNmyY7rzzTuXl5Tn03lWqVFFAQIBD7yFJnp6eioqKkpeXl0Pv88cff+imm27SnDlzNHLkSP3www/aunWrXnjhBaWmpmrBggVW5w8cOFBHjx7VL7/8ovvvv19JSUn68MMPHRojALgLElkAV+Xr66uoqChdd911atq0qZ566iktWrRIX3/9tebOnWs5LzMzU4888oiqVKmikJAQdejQQT/99JPl+E8//aTbbrtNwcHBCgkJUbNmzbR582atXr1a/fv3V1ZWlqUCOWHCBEkFWwtMJpPeffdd9erVSwEBAapTp46+/PJLy/H8/HwNGDBAcXFx8vf3V0JCgl577TXL8QkTJui9997TokWLLPdavXp1oVXMNWvWqGXLlvL19VV0dLTGjh1rlbi3b99ejz/+uEaPHq3KlSsrKirKEndRnnrqKR04cECbNm1S//791bhxY9WsWVNdunTRhx9+qCFDhlidHxAQoKioKNWqVUsTJkwo8LwAcC0jkQVQKh06dNANN9yghQsXWvbdd999OnHihL7++mtt2bJFTZs2VceOHfXHH39Ikvr27avq1avrxx9/1JYtWzR27Fh5e3urdevWmjZtmkJCQnT06FEdPXpUI0eOLPLeEydO1P33369t27apW7du6tu3r+UeZrNZ1atX16effqpffvlFzz77rJ566il98sknkqSRI0fq/vvvt1SZjx49qtatWxe4x+HDh9WtWze1aNFCP/30k2bN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",
"text/plain": [
"<Figure size 800x600 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"# Create a copy of speed_matrix and set diagonal to None to ignore same-GPU transfers\n",
"masked_matrix = speed_matrix.copy()\n",
"for i in range(len(list_of_gpus)):\n",
" masked_matrix[i,i] = np.nan\n",
"\n",
"fig, ax = plt.subplots(figsize=(8, 6))\n",
"im = ax.imshow(masked_matrix, cmap='viridis')\n",
"\n",
"# Add labels\n",
"ax.set_xticks(np.arange(len(list_of_gpus)))\n",
"ax.set_yticks(np.arange(len(list_of_gpus)))\n",
"ax.set_xticklabels([f'GPU {i}' for i in range(len(list_of_gpus))])\n",
"ax.set_yticklabels([f'GPU {i}' for i in range(len(list_of_gpus))])\n",
"\n",
"# Add colorbar\n",
"cbar = plt.colorbar(im)\n",
"cbar.set_label('GB/s')\n",
"\n",
"# Add title and axis labels\n",
"plt.title('GPU to GPU Transfer Speeds')\n",
"plt.xlabel('Destination GPU')\n",
"plt.ylabel('Source GPU')\n",
"\n",
"# Rotate x-axis labels for better readability\n",
"plt.setp(ax.get_xticklabels(), rotation=45, ha=\"right\")\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.15"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@liunn5
Copy link

liunn5 commented Dec 20, 2024

driver565.57.1 enabled resize-bar, question:CUDA error at simpleP2P.cu:129 code=205(cudaErrorMapBufferObjectFailed) "cudaDeviceEnablePeerAccess(gpuid[1], 0)"。 why this

@legraphista
Copy link
Author

driver565.57.1 enabled resize-bar, question:CUDA error at simpleP2P.cu:129 code=205(cudaErrorMapBufferObjectFailed) "cudaDeviceEnablePeerAccess(gpuid[1], 0)"。 why this

You might have to enable resizable bar in your BIOS. it should be under pci-e settings

@liunn5
Copy link

liunn5 commented Dec 20, 2024 via email

@legraphista
Copy link
Author

I've never had this issue, try posting in https://github.com/tinygrad/open-gpu-kernel-modules and ask for help there. They most likely know better

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment