Two clocks one training step: CPU timings or GPU timings?

Two clocks one training step: CPU timings or GPU timings?

Hey folks!

Did you ever wrapped model(x) in time.perf_counter() and gotten numbers that make no sense?

I realized it's a common enough trap and wrote a detailed write up here:

https://medium.com/traceopt/two-clocks-one-training-step-how-traceml-measures-pytorch-performance-357bc8e28dc7

TL;DR:

CUDA runs async. model(x) just enqueues kernels and returns, so a perf_counter() bracket around it measures how long Python took to queue the work, but not how long the GPU took to run it. The pending GPU time gets charged to whatever blocks next.

The tried the textbook fix, torch.cuda.synchronize() before each reading, which gives you accurate numbers but entirely about a different run.

Every sync becomes a stall, and it serializes exactly the CPU/GPU overlap you were trying to measure.

If one tires CUDA events (start.record() / end.record() / elapsed_time), it may fix both: the GPU stamps the markers as it passes, and you read them later with a non-blocking query() so nothing ever waits.

But i realized "CUDA events everywhere" is also wrong.

DataLoader next() is CPU work.

In a ML pipeline its time is high while the GPU's input wait is near zero, because the fetch overlaps the previous step.

Where I ended up: record both clocks for every phase, pick ONE clock per analysis window (and say which), report never-measured as null instead of 0.0, and only compare runs on a clock both measured.

How do you handle this in your own timing code: sync and eat the stall, or keep the two clocks separate?

u/pendu777 — 13 days ago
▲ 4 r/CUDA

Two clocks one training step: CPU timings or GPU timings?

Hey folks!

Did you ever wrapped model(x) in time.perf_counter() and gotten numbers that make no sense?

I realized it's a common enough trap and wrote a detailed write up here:

https://medium.com/traceopt/two-clocks-one-training-step-how-traceml-measures-pytorch-performance-357bc8e28dc7

TL;DR:

CUDA runs async. model(x) just enqueues kernels and returns, so a perf_counter() bracket around it measures how long Python took to queue the work, but not how long the GPU took to run it. The pending GPU time gets charged to whatever blocks next.

The tried the textbook fix, torch.cuda.synchronize() before each reading, which gives you accurate numbers but entirely about a different run.

Every sync becomes a stall, and it serializes exactly the CPU/GPU overlap you were trying to measure.

If one tires CUDA events (start.record() / end.record() / elapsed_time), it may fix both: the GPU stamps the markers as it passes, and you read them later with a non-blocking query() so nothing ever waits.

But i realized "CUDA events everywhere" is also wrong.

DataLoader next() is CPU work.

In a ML pipeline its time is high while the GPU's input wait is near zero, because the fetch overlaps the previous step.

Where I ended up: record both clocks for every phase, pick ONE clock per analysis window (and say which), report never-measured as null instead of 0.0, and only compare runs on a clock both measured.

How do you handle this in your own timing code: sync and eat the stall, or keep the two clocks separate?

u/pendu777 — 13 days ago

Two clocks one training step: CPU timings or GPU timings?

Hey folks!

Did you ever wrapped model(x) in time.perf_counter() and gotten numbers that make no sense?

I realized it's a common enough trap and wrote a detailed write up here:

https://medium.com/traceopt/two-clocks-one-training-step-how-traceml-measures-pytorch-performance-357bc8e28dc7

TL;DR:

CUDA runs async. model(x) just enqueues kernels and returns, so a perf_counter() bracket around it measures how long Python took to queue the work, but not how long the GPU took to run it. The pending GPU time gets charged to whatever blocks next.

The tried the textbook fix, torch.cuda.synchronize() before each reading, which gives you accurate numbers but entirely about a different run.

Every sync becomes a stall, and it serializes exactly the CPU/GPU overlap you were trying to measure.

If one tires CUDA events (start.record() / end.record() / elapsed_time), it may fix both: the GPU stamps the markers as it passes, and you read them later with a non-blocking query() so nothing ever waits.

But i realized "CUDA events everywhere" is also wrong.

DataLoader next() is CPU work.

In a ML pipeline its time is high while the GPU's input wait is near zero, because the fetch overlaps the previous step.

Where I ended up: record both clocks for every phase, pick ONE clock per analysis window (and say which), report never-measured as null instead of 0.0, and only compare runs on a clock both measured.

How do you handle this in your own timing code: sync and eat the stall, or keep the two clocks separate?

u/pendu777 — 15 days ago
▲ 2 r/mlops

The cost of catching bottle necks in your training pipeline - Three ways compared: TraceML vs torch.profiler vs cProfile and here's what each one actually costs.

Hello People!

Figuring out bottle necks and training stalls in your training work loads usually means firing up a profiler post-hoc and probably staring at a trace for twenty, right?

I was thinking of how to reduce this friction? what does this actually cost, tool by tool.

I took one run I knew was input-bound (dataloader starving the GPU) and measured it three ways: torch.profiler, cProfile, and TraceML, a lighter always-on OSS tool I've been contributing to.

For each one I looked at overhead, how much the profiler itself perturbs the GPU utilization it's trying to measure, output size, and how much manual digging it takes to get from the raw output to "the dataloader is the problem."

Short version: torch.profiler and cProfile are precise but heavy and after the fact, closer to a scalpel. Something that just sits there and flags "this step looks off" while training runs is doing a different job, not replacing them.

Numbers and traces are in the post.

Curious how other people usually catch this before it burns your precious compute.

https://medium.com/traceopt/traceml-vs-torch-profiler-vs-cprofile-what-each-one-costs-to-find-the-same-bottleneck-745a57e13ee9?sharedUserId=apendyala

TraceML is open source: pip install traceml-ai. Star or contribute at github.com/traceopt-ai/traceml
'

u/pendu777 — 27 days ago

The cost of catching bottle necks in your training pipeline - Three ways compared: TraceML vs torch.profiler vs cProfile and here's what each one actually costs.

Hello People!

Figuring out bottle necks and training stalls in your training work loads usually means firing up a profiler post-hoc and probably staring at a trace for twenty, right?

I was thinking of how to reduce this friction? what does this actually cost, tool by tool.

I took one run I knew was input-bound (dataloader starving the GPU) and measured it three ways: torch.profiler, cProfile, and TraceML, a lighter always-on OSS tool I've been contributing to.

For each one I looked at overhead, how much the profiler itself perturbs the GPU utilization it's trying to measure, output size, and how much manual digging it takes to get from the raw output to "the dataloader is the problem."

Short version: torch.profiler and cProfile are precise but heavy and after the fact, closer to a scalpel. Something that just sits there and flags "this step looks off" while training runs is doing a different job, not replacing them.

Numbers and traces are in the post.

Curious how other people usually catch this before it burns your precious compute.

https://medium.com/traceopt/traceml-vs-torch-profiler-vs-cprofile-what-each-one-costs-to-find-the-same-bottleneck-745a57e13ee9?sharedUserId=apendyala

https://preview.redd.it/hzdjztm5jteh1.png?width=1446&format=png&auto=webp&s=4b3f7932d1a8195e8e2318bd6884ea4a05db3054

TraceML is open source: pip install traceml-ai. Star or contribute at github.com/traceopt-ai/traceml

reddit.com
u/pendu777 — 28 days ago

I profiled one input-bound PyTorch run three ways (TraceML vs torch.profiler vs cProfile). Here's what each one actually costs.

Hello Peeps!

Do you guys do a lot of training or fine tuning? Does the loss curve look fine, but the run is slower than it should be, and figuring out why usually means firing up a profiler and staring at a trace for twenty minutes?

This got me curious: what this actually costs, tool by tool. I took one run I knew was input-bound (dataloader starving the GPU) and measured it three ways: torch.profiler, cProfile, and TraceML, a lighter always-on OSS tool I've been contributing to.

For each one I looked at overhead, how much the profiler itself perturbs the GPU utilization it's trying to measure, output size, and how much manual digging it takes to get from the raw output to "the dataloader is the problem."

Short version: torch.profiler and cProfile are precise but heavy and after the fact, closer to a scalpel. Something that just sits there and flags "this step looks off" while training runs is doing a different job, not replacing them.

Numbers and traces are in the post.

Curious how other people usually catch this before it burns your precious compute.

https://medium.com/traceopt/traceml-vs-torch-profiler-vs-cprofile-what-each-one-costs-to-find-the-same-bottleneck-745a57e13ee9?sharedUserId=apendyala

https://preview.redd.it/hzdjztm5jteh1.png?width=1446&format=png&auto=webp&s=4b3f7932d1a8195e8e2318bd6884ea4a05db3054

reddit.com
u/pendu777 — 29 days ago

How much do you actually trust the GPU "utilization" number in distributed training?

Hello Peeps!

Wanted to get your views/thoughts/suggestions on something brewing in my head. I train models for a living (Phd in RL and CV background) and I've stopped trusting logged GPU utilization. What most tools (W&B system metrics, etc.) show is NVML GPU-Util, which only means a kernel was resident during the sample window, not that the SMs were busy or that the work was actually even useful.

For people who train at scale:

- Fast triage for "compute-bound vs idling": what's your first look? Mine is caching one batch on-device and looping it. If that's way faster than the real loop, I'm input-bound.

- How much weight do you put on util % vs MFU or achieved bandwidth? I treat ~35–50% MFU as the realistic band and use util only as a liveness check.

- In distributed

  1. how do you separate "GPUs fed" from "GPUs waiting on each other"?
  2. Do you measure non-overlapped collective time
  3. How do you catch stragglers when every rank still looks 100%?

Where's your line between "good enough" and full kernel/collective profiling?

Papers ask: I've got roofline, the PaLM MFU definition, and Horace He's "Brrrr" post.

Looking for the next tier — anything rigorous on measuring utilization in *distributed* training specifically. Happy to hear your thoughts!

reddit.com
u/pendu777 — 1 month ago
▲ 18 r/CUDA

How much do you actually trust GPU "utilization," and what do you profile instead?

I come from the ML side (Phd in RL, some CV), and I spend a lot of time trying to figure out where a training run's time actually goes. The more I dig, the more I distrust the numbers.

I'd like to hear how you folks do this reason about it.

The thing that keeps tripping me up: "GPU utilization" from nvidia-smi is not what I assumed. It counts a sampling window as busy if any kernel ran in it, so a run can show a healthy 90% while the SMs are mostly idle, or look fine while the GPU is actually starved waiting on the input pipeline. It tells you the GPU was touched, not that it did useful work.

I have no clean rule for which to trust when:

- nvidia-smi utilization (coarse, misleading as above)

- SM/achieved occupancy from Nsight Compute (kernel-level, hard to reason about across a whole run)

- Nsight Systems timelines (great for CPU/GPU overlap, but a lot to read for a yes/no question)

- torch profiler, kernel time vs wall time

- CUDA events I place by hand around phases

- plain wall-clock deltas, the only thing I fully trust, but they tell me nothing about why

What I'd love to hear from people who have dealt with this or have figured out ways to optimize this:

- To answer just "is this workload compute-bound or starved," what do you look at first? Not the deep dive, the fast triage.

- How much do you trust utilization percentages at all? Is there a number you trust more

(occupancy, achieved FLOPs, memory throughput)?

- How do you reason about CPU/GPU overlap and your line between "the GPU is fed, good enough" and moving on to actual kernel work?

How does one decide what to measure and what to ignore. What does your real triage actually look like?

reddit.com
u/pendu777 — 1 month ago

TraceML: Diagnosing a real PyTorch DataLoader bottleneck: 51% GPU util, one three-line fix, 43% faster

I'm the author, and this uses our open-source tool (TraceML, Apache-2.0). Posting because the finding, and the need for this kind of diagnosis, is the value addition part.

TL;DR: A ResNet-18 run on a single T4 (AWS g4dn.xlarge, 4 vCPUs) looked completely healthy, but the GPU sat at ~51% utilization the whole time, starved by a default num_workers=0 DataLoader. A three-line change (num_workers, pin_memory, persistent_workers) took 2,000 steps from 633s to 358s (43% less wall clock) and flipped the run from input-bound to compute-bound. Same model, data, seed, and step count. Everything is wall-clock measured.

Everyone knows to set num_workers; that is not the point, and a memorized value would not have saved this run. It is not a best practice with a correct answer, but a moving target tied to CPU cores, storage, transforms, and batch size. Copying num_workers=8 from a blog is just a different guess than the zero you started with: on the wrong machine it still starves the GPU, slows the run by oversubscribing cores, or hides an inefficient input pipeline behind more processes.

The engineer who wrote this baseline was not missing knowledge; nothing in an ordinary run surfaces the waste. A starved loss curve is indistinguishable from a healthy one, the job completes, and GPU utilization is not on screen while you train. A framework may hint about workers, but a hint with no number carries no urgency. "Your GPU idled at 51% this run, here is the before and after" is a different kind of statement: a diagnosis, not a lint rule.

Full writeup: https://medium.com/traceopt/diagnosing-a-pytorch-dataloader-bottleneck-in-a-real-training-run-40bbe394b834

Tool (open source): https://github.com/traceopt-ai/traceml

Happy to get into the methodology in the comments.

reddit.com
u/pendu777 — 1 month ago
▲ 2 r/deeplearning+1 crossposts

Diagnosing a real PyTorch DataLoader bottleneck: 51% GPU util, one three-line fix, 43% faster

Disclosure: I'm the author, and this uses our open-source tool (TraceML, Apache-2.0). Posting because the finding, and the need for this kind of diagnosis, is the value addition part.

TL;DR: A ResNet-18 run on a single T4 (AWS g4dn.xlarge, 4 vCPUs) looked completely healthy, but the GPU sat at ~51% utilization the whole time, starved by a default num_workers=0 DataLoader. A three-line change (num_workers, pin_memory, persistent_workers) took 2,000 steps from 633s to 358s (43% less wall clock) and flipped the run from input-bound to compute-bound. Same model, data, seed, and step count. Everything is wall-clock measured.

Everyone knows to set num_workers; that is not the point, and a memorized value would not have saved this run. It is not a best practice with a correct answer, but a moving target tied to CPU cores, storage, transforms, and batch size. Copying num_workers=8 from a blog is just a different guess than the zero you started with: on the wrong machine it still starves the GPU, slows the run by oversubscribing cores, or hides an inefficient input pipeline behind more processes.

The engineer who wrote this baseline was not missing knowledge; nothing in an ordinary run surfaces the waste. A starved loss curve is indistinguishable from a healthy one, the job completes, and GPU utilization is not on screen while you train. A framework may hint about workers, but a hint with no number carries no urgency. "Your GPU idled at 51% this run, here is the before and after" is a different kind of statement: a diagnosis, not a lint rule.

Full writeup: https://medium.com/traceopt/diagnosing-a-pytorch-dataloader-bottleneck-in-a-real-training-run-40bbe394b834

Tool (open source): https://github.com/traceopt-ai/traceml

Happy to get into the methodology in the comments.

reddit.com
u/pendu777 — 1 month ago