pytorch / pytorch/pytorch

[CUDA Graph] Size-specific ~2.1× slowdown for contiguous CUDA `Tensor.copy_` on GB200

Open
#191,284 11 comments 0 reactions 0 assignees View on GitHub
bot-triaged has workaround high priority module: cuda module: cuda graphs module: performance topic: performance triaged
Dominant language
Python
Stars
103k
Forks
29.5k
PR merge metrics
PR metrics pending

Description

### 🐛 Describe the bug

> **AI-assistance disclosure:** I used codex

A contiguous, same-dtype CUDA-to-CUDA `Tensor.copy_` normally sustains about
6.5–6.8 TB/s in both eager execution and CUDA Graph replay. However, **graph
replay falls to approximately 3.1 TB/s at exactly 512 MiB and for the tested
1 GiB and 2 GiB memcpy nodes**, while the adjacent 508 MiB and 516 MiB cases
remain near full bandwidth. Splitting the 1 GiB and 2 GiB transfers into
256 MiB graph nodes also restores full bandwidth. Rates count one source read
plus one destination write: `2 × bytes / complete CUDA-event time`, in decimal
TB/s.

## Measurements

A contiguous CUDA-to-CUDA `Tensor.copy_` slows down only at specific CUDA
Graph memcpy-node sizes. Rates count one source read plus one destination
write: `2 × bytes / complete CUDA-event time`, in decimal TB/s.

| Total copy | Nodes | MiB/node | Eager time | Graph time | Eager TB/s | Graph TB/s |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 508 MiB | 1 | 508 | 0.158183 ms | 0.158042 ms | 6.735 | 6.741 |
| 512 MiB | 1 | 512 | 0.165612 ms | 0.347006 ms | 6.483 | **3.094** |
| 516 MiB | 1 | 516 | 0.160397 ms | 0.160734 ms | 6.747 | 6.732 |
| 1 GiB | 1 | 1,024 | 0.319806 ms | 0.689774 ms | 6.715 | **3.113** |
| 1 GiB | 4 | 256 | 0.328524 ms | 0.323698 ms | 6.537 | 6.634 |
| 2 GiB | 1 | 2,048 | 0.630680 ms | 1.375304 ms | 6.810 | **3.123** |
| 2 GiB | 8 | 256 | 0.656700 ms | 0.641940 ms | 6.540 | 6.691 |

## Reproduction

```python
import statistics

import torch

MIB = 1 << 20

def timed_ms(issue, iterations):
for _ in range(3):
issue()
torch.cuda.synchronize()

samples = []
for _ in range(7):
start = torch.cuda.Event(enable_timing=True)
stop = torch.cuda.Event(enable_timing=True)
start.record()
for _ in range(iterations):
issue()
stop.record()
stop.synchronize()
samples.append(start.elapsed_time(stop) / iterations)
return statistics.median(samples)

def benchmark(mib):
size = mib * MIB
source = torch.full((size,), 0xA5, dtype=torch.uint8, device="cuda")
destination = torch.empty_like(source)

def copy():
destination.copy_(source)

iterations = (16 << 30) // size
eager_ms = timed_ms(copy, iterations)

stream = torch.cuda.Stream()
stream.wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(stream):
copy()
torch.cuda.current_stream().wait_stream(stream)
torch.cuda.synchronize()

graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph, stream=stream):
copy()
graph_ms = timed_ms(graph.replay, iterations)

assert destination[0].item() == 0xA5
rate = lambda ms: 2 * size / (ms / 1000) / 1e12
return eager_ms, graph_ms, rate(eager_ms), rate(graph_ms)

print(torch.__version__, torch.version.cuda, torch.cuda.get_device_name(0))
print("MiB eager_ms graph_ms eager_TB/s graph_TB/s")
for mib in (508, 512, 516):
print(mib, *benchmark(mib))
```

### Versions

| Component | Value |
| --- | --- |
| GPU | NVIDIA GB200, compute capability 10.0, 152 SMs |
| GPU memory / L2 | 197,897,748,480 / 135,528,448 bytes |
| PyTorch | 2.13.0+cu132, git `cf30153c4c131c8164ee7798e5022d810682e2cb` |
| CUDA used to build PyTorch | 13.2 |
| Packaged CUDA runtime | 13.2.75 |
| NVIDIA driver / VBIOS | 580.82.07 / 97.00.B9.00.6B |
| Python / Triton | 3.12.13 / 3.7.1 |
| OS | CentOS Stream 9, aarch64, Linux 6.13.2, glibc 2.34 |

cc @ezyang @gchanan @kadeng @msaroufim @ptrblck @eqy @tinglvv @nWEIdia @mcarilli @eellison @penguinwu @BoyuanFeng

Contributor guide

Open the contributing guide

Research direction

Start by running the Python reproduction on the specified GB200, PyTorch 2.13.0+cu132, and CUDA 13.2 environment, comparing the 508, 512, and 516 MiB cases and the split-node cases. Investigate the CUDA Graph replay path for contiguous CUDA-to-CUDA Tensor.copy_ operations. Done means the size-specific slowdown is explained and the affected graph copies no longer show the reported bandwidth drop.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.