Skip to content

Feat/cuda graph training - #671

Merged
EHxuban11 merged 3 commits into
devfrom
feat/cuda-graph-training
Jul 30, 2026
Merged

Feat/cuda graph training#671
EHxuban11 merged 3 commits into
devfrom
feat/cuda-graph-training

Conversation

@EHxuban11

@EHxuban11 EHxuban11 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Code provenance

Original implementation written for this PR. CUDA graph capture is built entirely on PyTorch public API (torch.cuda.make_graphed_callables, torch.cuda.is_current_stream_capturing) following PyTorch documented capture recipes. No third-party training framework code was consulted or referenced.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Greptile Summary

Implements opt-in, single-GPU CUDA graph training while retaining eager loss computation and fallback behavior.

  • Adds graph capture, shape-based dispatch, buffer restoration, and eager fallback infrastructure.
  • Integrates capture specifications for YOLO9 and RF-DETR.
  • Exposes the feature through training configuration and the CLI.
  • Adds CUDA parity, routing, fallback, and AMP tests plus usage documentation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported BatchNorm warm-up drift is addressed by snapshotting buffers before capture, restoring them in place before the first replay, and testing exact YOLO9 buffer parity.

Important Files Changed

Filename Overview
libreyolo/training/cuda_graph.py Adds graph capture management, output-tree adaptation, shape dispatch, in-place buffer restoration, and permanent eager fallback after capture failures.
libreyolo/training/trainer.py Integrates optional graph routing into both training loops while retaining eager execution for unsupported configurations and shape misses.
libreyolo/models/yolo9/trainer.py Defines the captured-network/eager-loss boundary for the plain YOLO9 detection head.
libreyolo/models/rfdetr/trainer.py Defines the captured RF-DETR network path while keeping target conversion and Hungarian criterion execution eager.
libreyolo/models/rfdetr/transformer.py Avoids capture-time host synchronization and caches fixed spatial-shape tensors by shape and device.
tests/unit/test_cuda_graph_training.py Covers dispatch and fallback behavior and verifies YOLO9 loss, parameter, and buffer parity between eager and graphed execution.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Batch[Training batch] --> Enabled{CUDA graph enabled and supported?}
  Enabled -- No --> Eager[Eager model forward]
  Enabled -- Yes --> Shape{Captured shape?}
  Shape -- No --> Warmup{Shape repeated enough?}
  Warmup -- No --> Eager
  Warmup -- Yes --> Snapshot[Snapshot model buffers]
  Snapshot --> Capture[Warm up and capture network]
  Capture --> Restore[Restore buffers in place]
  Restore --> Replay[Replay captured network]
  Shape -- Yes --> Replay
  Replay --> Loss[Eager family loss]
  Eager --> Loss
  Loss --> Backward[Backward and optimizer step]
Loading

Reviews (2): Last reviewed commit: "Restore module buffers after capture war..." | Re-trigger Greptile

Context used (3)

train(cuda_graph=True) captures the network forward/backward with
torch.cuda.make_graphed_callables while the loss, optimizer, EMA and LR
schedule stay eager. Dispatch is shape-keyed: one graph is captured once
a batch shape repeats, and any other shape (multi-scale batches, last
partial batch) runs eager, so enabling the flag never changes numerics.
Unsupported setups (non-CUDA, DDP, distillation, families or tasks
without a capture spec) fall back to eager with one warning.

Families wire in through cuda_graph_train_spec: yolo9 (detect, exact
DDetect head, includes yolo9_p2) graphs the whole network and reuses the
head loss; rfdetr (detect) graphs the full LWDETR network, whose
training forward never reads targets, and keeps the Hungarian criterion
eager.

Capture-enabling fixes that also stand alone:
- rfdetr transformer: cache the per-resolution spatial_shapes tensor
  instead of writing Python ints element-wise into a CUDA tensor each
  forward (unpinned H2D copies, illegal during capture), and skip the
  int() readback sanity assert only while a stream is capturing.
- trainer loop: drop total_loss_raw with outputs/loss at iteration end
  so no autograd-graph reference survives the step; live AccumulateGrad
  nodes bound to the default stream invalidate later capture.

Measured on an RTX 5070 Ti (AMP, 640px, no dataloader bottleneck):
yolo9-t b16 1.56x, yolo9-t b8 2.25x, yolo9-s b16 1.23x, yolo9-m b8
1.10x steps/s. Parity gated in tests/unit/test_cuda_graph_training.py:
yolo9 bit-identical loss and parameters vs eager; rfdetr step-0
bit-identical and trajectory within its eager run-to-run atomics noise.
The trainer's _autocast_context now forwards cache_enabled (disabled
only while a CUDA graph training manager is active); the fake autocast
in the dtype test needed the keyword, and both states are now asserted.
Comment thread libreyolo/training/cuda_graph.py
make_graphed_callables warm-up runs extra forward passes on the live
model, advancing BatchNorm running stats past what one eager step
performs, so validation, EMA and checkpoints would drift from eager.
The manager now snapshots every network buffer before capture and
restores it in place afterwards (copy_ keeps the addresses the captured
kernels recorded), on both the success and failure paths; the replay for
the capture batch then applies that batch's single update exactly as
eager would. The YOLO9 parity test now also asserts bit-equal buffers
after six steps, and fails if the restore is removed.
@EHxuban11
EHxuban11 merged commit 585ffa0 into dev Jul 30, 2026
11 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant