the most dangerous number in ml training is 100% gpu utilization.
ours showed it for 29 minutes while training nothing.
here's how we
@hariharanms95 got from 6.1s → 2.5s per training step.
when we first ran it, nvidia-smi showed the GPUs running, but there was no loss and no error.
nvidia-smi was showing 100% utilization the whole time, but each card was only drawing around 120W, when an h200 actually training pulls somewhere around 600 to 700W.
that's when we realised utilization only tells you a kernel is sitting on the GPU and power draw was the number telling us the truth.
since we were on 4 GPUs, our first guess was the usual one, that NCCL was stuck waiting on some GPU that never responded, so we ran it again on a single GPU and it hung the exact same way at the same 120W.
that ruled out the multi-GPU side completely and left us with two suspects, torch.compile or one of our custom kernels.
what finally worked was a compilation change. our FP8 custom ops had their own compile wrappers nested inside a model we were already compiling as a whole, so we removed those and compiled the full model once, and the next run trained normally.
but honestly, the hang happened on other platform but the run that worked was on jarvis labs, a different setup. the FP8 path was optional anyway, so we can't say for sure which change fixed it.
once it was training, the log showed 6,352 ms per step at step 10, which looked slow until we realised it was a running average that also counted step 0, and step 0 alone took 8,700 ms just for startup. taking that out:
((6,352 × 11) - 8,700) / 10 ≈ 6,117 ms (refer image 2)
so the real starting point was ~6.12s and not 6.35s. but if you compare configs using averages with different startup costs, you end up comparing warm-up time instead of training speed.
for every test after that we kept one thing fixed, 1,048,576 tokens per optimizer step on 4× H200, because changing the micro-batch also changes how many gradient accumulation steps you need.
• seq 2048, micro-batch 4, eager: ~6.12s (32 accumulation steps)
• seq 1024, micro-batch 32, eager: ~4.98s (8 accumulation steps)
• seq 1024, micro-batch 32, torch.compile: ~2.48s
(refer image 3)
going from 32 accumulation passes to 8 meant each pass did more work and the GPUs wasted less time on overhead, but since we also changed the sequence length in the same step, we can't really split the credit there. the second jump was only compilation, and that alone cut step time in half.
one thing that almost fooled us again was that the first compiled step took around 31 seconds to build the kernels, so any early average made the compiled run look slower than eager.
we also tried micro-batch 64, which was a bit faster at ~2.41s per step, but memory per GPU jumped from ~76.5GB to ~131.9GB, which left almost no room for longer sequences or validation, so for a 3% gain it wasn't worth it and we stayed with 32.
what that speedup was worth over the full schedule (95,367 steps on 4× H200, step time only):
• ~6.12s per step → ~162 hours → ~648 GPU-hours
• ~4.98s per step → ~132 hours → ~528 GPU-hours
• ~2.48s per step → ~66 hours → ~262 GPU-hours (we made it here)
the real run took about 76 hours end to end, with validation and checkpoints included, and cost us roughly $1178.
at the speed we started with, the same run would have needed about 2.5× the GPU time, so somewhere around $4,500 to $5,000.