Repository navigation
perf(cuda-ep): make Tile capture-eligible via persistent metadata (Lever 2 PR-2) - #577
Conversation
…ver 2 PR-2) Migrate the CUDA Tile kernel from per-call launch_metadata (device alloc + htod + synchronize + free per call, plus a blocking dtoh read of the repeats input) to the PersistentMetadata / launch_persistent_metadata pattern already used by Transpose (#574) and Expand. This removes the per-call host sync so a warmed fixed-decode-signature Tile enters the parent CUDA-graph captured segment, and also drops the sync inside the eager Scan bodies where Tile runs on the qwen3.6-27b linear-attention hybrid. The repeats input (host_ints, a blocking dtoh that is illegal mid-capture) is now read ONLY on the unwarmed eager path to validate output[i]==input[i]* repeats[i]. Under a stable (dtype, input_shape, output_shape) signature the repeats vector is mathematically fixed by shape inference, so gating the read behind the warmed signature is byte-exact. The kernel metadata bytes ([output.shape, input.shape, input_strides]) are unchanged, and launch_metadata / launch_persistent_metadata pass identical args to tile_bytes. launch_metadata had Tile as its only remaining caller post-#574; removed as dead code. Correctness (byte-exact vs CPU fp32 oracle; ORT-CUDA crashes on 27b so CPU is the oracle): - 27b stock export native CUDA == CPU fp32 oracle, byte-identical (native_autoderive_io_cuda_e2e, #[ignore] GPU e2e). - qwen3-0.6b native CUDA == ORT golden, byte-identical. - New positive test tile_warmed_metadata_captures_and_matches_eager: unwarmed declines capture; warmed is Supported; eager re-run and capture+replay both byte-identical. - New negative test tile_rejects_signature_change_during_capture: feeding a changed signature mid-capture returns Err "changed during CUDA graph capture". Measured (stock 27b, profile_native --steady --decode-skip 8 --warmups 2 --runs 3 --tokens 64): 156.3 -> 150.0 ms/tok (~4.0%), tokens byte-identical. Does not beat ORT alone (that needs the Inc-1b Scan-body capture lane); this is one more op off the eager-sync hot path. Blast radius: Tile kernel path + its two tests only. No change to shared capture machinery, the #443/#543 capture invariants, offload, or GAP-3. Refs #574 (Lever 2 lineage), #443/#543 (capture invariants). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #577 +/- ##
==========================================
+ Coverage 80.75% 81.46% +0.71%
==========================================
Files 315 315
Lines 123737 123737
Branches 123737 123737
==========================================
+ Hits 99920 100803 +883
+ Misses 19746 18856 -890
- Partials 4071 4078 +7
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
✅ Benchmarks — No RegressionComparison of criterion micro-benchmarks: PR head vs merge-base, measured on the same runner in the same job (base first → PR second).
Visual flags: Host infoWhat this cannot catch
|
|
VERDICT: APPROVE 2026-08-01 · Reviewer: Harry (independent; author was Cohaagen — author-lockout in effect) PR #577, head Evidence (all commands run by Harry)
Notes / caveats (non-blocking)
ConclusionStructurally identical to merged #574, byte-exact, tightly scoped, guards proven |
|
VERDICT: APPROVE (Harry, independent review) Structurally identical to merged #574; byte-exact; guards mutation-proven non-vacuous; repeats-uniqueness crux proven sound at kernel level (tile_bytes never reads repeats; repeats[i]=output[i]/input[i] pinned by warmed (dtype,input_shape,output_shape) signature). Independent 27b re-measure: 151.90→149.01 ms/tok (+1.9%), token ids byte-identical. 22/22 construction_gpu, clippy -D warnings exit 0, 0.6b golden + 27b fp32 oracle byte-exact. APPROVE. |
What this unlocks
Lever 2 PR-2. Migrates the CUDA Tile kernel to the
PersistentMetadata/launch_persistent_metadatapattern already used by Transpose (#574) and Expand, so a warmed fixed-decode-signature Tile becomes CUDA-graph capture-eligible and drops its per-call host sync.Post-#574, Tile was the last
launch_metadatacaller inmovement.rs. Per call it did: devicealloc_raw+htodof the metadata + kernel launch +synchronize()(to guard thefree_raw) + free — plus a blockingdtohread of therepeatsinput. On the qwen3.6-27b linear-attention hybrid, Tile runs ~576×/step inside eager Scan bodies, so those syncs are on the decode hot path.Correctness — byte-exact vs CPU fp32 oracle
ORT-CUDA crashes on the 27b, so the CPU fp32 backend is the oracle.
native_autoderive_io_cuda_e2e,#[ignore]GPU e2e — passed).qwen3_0_6b_native_cuda_e2e— passed); capture engagement unchanged (captures=2, replays=34, fallbacks=0).tile_warmed_metadata_captures_and_matches_eager: unwarmed declines capture; warmed reportsSupported; eager re-run and capture+replay both byte-identical.tile_rejects_signature_change_during_capture: feeding a changed signature mid-capture returnsErr"changed during CUDA graph capture" — locks the guard's teeth.The
repeatsinput (a blockingdtoh, illegal mid-capture) is now read only on the unwarmed eager path to validateoutput[i] == input[i] * repeats[i]. Under a stable(dtype, input_shape, output_shape)signature,repeatsis mathematically fixed by shape inference, so gating that read behind the warmed signature is byte-exact. Kerneltile_bytesmetadata ([output.shape, input.shape, input_strides]) is unchanged; both launch paths pass identical args.Honest perf
Stock 27b,
profile_native --steady --decode-skip 8 --warmups 2 --runs 3 --tokens 64:~4.0% faster, tokens byte-identical. This does not beat ORT (17.38 tok/s) on its own — that requires the Inc-1b Scan-body capture lane (awaiting Justin's greenlight) plus removal of the shared eager
synchronize()on the ~50 always-sync kernels. This is one more fat op off the eager-sync hot path, in the same reviewable single-op shape as #574.Blast radius
Tile kernel path + its two tests only. Dead
launch_metadataremoved (Tile was its sole remaining caller). No change to shared capture machinery, the #443/#543 capture-correctness invariants, weight offload, session-reuse, or GAP-3.Refs #574 (Lever 2 lineage), #443/#543 (capture invariants).