Repository navigation
perf(cuda): raise zero-copy hybrid budget default on Linux — WDDM aperture ceiling is Windows-specific (#925) - #936
Merged
Conversation
…rture ceiling is Windows-specific (#925) The zero-copy hybrid safety budget was a single global 256 MiB constant, conservative only because of the WDDM/VidMm aperture ceiling measured on an RTX 4060 Laptop (#864/#912): distinct host-mapped weight reads above ~0.44–0.65 GB/step silently return stale data → wrong tokens, no error. #925 re-measured the hybrid on Linux (H200, driver 580.105.08, CUDA 13.0, kernel 6.6, native VMM decode path) and found that ceiling ABSENT: generation stayed byte-identical to the Step-0 baseline with cuda_graph fallbacks=0 up to 6.795 GB of distinct host-mapped weights bound and re-read per decode step (704 binds; n=3, all byte-identical) — ~15× the WDDM onset. The 256 MiB default left an ~8× decode win unused in the over-budget regime (67 vs ~8.5 tok/s). Make ZERO_COPY_SAFE_BUDGET_BYTES platform-selected: - Windows keeps 256 MiB (ZERO_COPY_SAFE_BUDGET_BYTES_WDDM) — the ceiling is real. - Non-Windows raises to 2 GiB (ZERO_COPY_SAFE_BUDGET_BYTES_NON_WINDOWS): bounded, >3× below the #925 measured-safe 6.795 GB, above the whole WDDM corruption band. Feature is opt-in (defaults OFF), so blast radius is limited to users who already set ONNX_GENAI_ZERO_COPY_HYBRID=1. Windows behaviour unchanged. 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 #936 +/- ##
=======================================
Coverage 77.76% 77.76%
=======================================
Files 352 352
Lines 145983 145983
Branches 145983 145983
=======================================
Hits 113530 113530
Misses 27970 27970
Partials 4483 4483
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
🔴 Benchmark Regression DetectedComparison 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
|
This was referenced Aug 14, 2026
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
#940) Scribe state-only batch: merged 2 inbox drops (#938 roper-decode-remaining-levers, #936 holden-zerocopy-linux-default) into decisions.md, archived older bodies to decisions-archive/2026-08.md (49,264 B → 48,169 B, under the 50 KB gate), appended histories. State-only; no code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
…win (#942) docs: the Linux zero-copy question is answered — record the ~8x win, not "untested" Four documents still said the hybrid was open and untested on Linux and pointed at #925 as a hypothesis. #925 has since been run on an H200 (driver 580.105.08, CUDA 13, kernel 6.6, Azure Linux 3.0) and #936 raised the default on the strength of it, so the docs were stale in the direction that matters most: they described the most valuable available lever as an open question. The measurement: the WDDM aperture ceiling is ABSENT on Linux. Generation stayed byte-identical to the Step-0 baseline with cuda_graph fallbacks=0 up to 6.795 GB of distinct host-mapped weights bound and re-read in place every decode step -- 704 cuMemHostRegister binds, n=3, all runs byte-identical -- about 15x the WDDM ~0.44 GB onset and ~10x the top of its 0.44-0.65 GB corruption band. In the over-budget regime the hybrid runs 67.04 tok/s against ~8.5 median for managed streaming, so the shipped 256 MiB default was leaving an ~8x decode win unused there. Both halves of the argument I used to scope the #912 negative to WDDM are now confirmed rather than asserted: the comparison arm genuinely does not exist on Linux (no OS fallback, so the competitor is managed streaming or failure), and the ceiling genuinely was a VidMm behaviour, the same family as #863 where VidMm demoted our own VMM granules behind our back. Updates PROGRESS, WEIGHT_OFFLOAD, MEMORY_ARCHITECTURE, and both halves of the memory-management design. C7 gains what is now its best worked example: the same reported capability differs by more than an order of magnitude across two platforms, and getting it wrong costs silently corrupted output in one direction and ~8x of throughput in the other. That is the argument for putting host-mapped read capacity in a capability report rather than a constant, and it is no longer hypothetical. Records the bounded 2 GiB choice and why it is bounded -- >3x under the measured-safe figure because only one GPU class was tested. #783's lesson now runs in both directions: the WDDM negative was not inherited onto Linux, and the Linux positive is not extrapolated past its hardware. Figures checked against #936 rather than paraphrased. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d60eb808-7cc6-4abc-b48d-2a6dd3841624 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d60eb808-7cc6-4abc-b48d-2a6dd3841624
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
…y 919) (#944) ## #837 item 3 — and the isolation of #886 to a single tensor Closes #837 (item 3). **Do not merge without your review.** ### Headline update from round 2 Your control killed my conservation/churn explanation, and following the trigger you predicted led exactly where you hoped: **the #886 corruption is one tensor.** - Over-pinning **67 tensors / 2.371 GB** (threshold 30 MB, budget 2.4 GB) forces *more* churn than the 21-key arm (`htod` 2.349 → 2.626 GB/step, +11.8%; `page_ins_per_token` 321 → 375) and stays **byte-identical**. Mass-forced evict-and-re-admit does not corrupt. The trigger is *which* tensor, not churn. - Bisecting the 21 bypasser keys via the `PIN_KEYS` allow-list: | Pinned keys | Count | Result | |---|---:|---| | 919,920,287,247,912,914,855,874,232,304 | 10 | FAIL (3 tokens) | | 919,920,287,247,912 | 5 | FAIL | | 919,920 | 2 | FAIL | | **919** | **1** | **FAIL** — `[96347, 3375, 724]` | | 920 (its int4 scales) | 1 | PASS (16 IDs) | | 287 (a 35 MiB block weight) | 1 | PASS (16 IDs) | **Pinning only key 919 corrupts.** `919 = 389,283,840 B = 152 064 (vocab) × 5 120 (hidden) × 0.5` — the **int4-quantised lm_head / vocab-projection weight**, the one tensor that directly produces the logits deciding token identity. Its scale tensor (920) alone is safe, so it is the weight matrix itself, not the quant metadata. This turns `MEMORY_ARCHITECTURE.md` §3.5's "unisolated value-corruption" into a single-tensor bug. ### Reconciliation — why threshold-pin is safe and bypasser-pin is not Reproducing your 2.4 GB threshold arm **with the key trace on** shows key 919 `retained_page_ins=0 bypass_page_ins=16` — the lm_head is **never pinned**, even at 2.4 GB / 67 tensors. It is the *last* large tensor touched each step (logits run after every block), so the size+arrival budget is spent before it is reached. The threshold rule is safe because it **structurally cannot pin the poisonous tensor**; the explicit rule corrupts because its allow-list **contains** it. The difference between the two selection rules *is* the finding — exactly as you asked me to state it. ### Mechanism: narrowed, and a hypothesis already refuted by a control The stable-slot machinery (#716) exists for CUDA-graph pointer baking, so the obvious cause is a graph hazard when the lm_head is promoted from bypass (throwaway VA, never baked) into the stable-slot population. **Ruled out:** pinning 919 with `ONNX_GENAI_CUDA_GRAPH=0` (`captures=0 replays=0`) still corrupts. The first three tokens match the reference before the collapse, so the retained lm_head reads correctly for the first steps and then goes wrong — consistent with **retained-page staleness** (a pinned page is filled once and served as a hit thereafter, never re-filled), not a first-read address error. Stated as a leading hypothesis, **not** a proven cause. Honest state: isolated to one tensor and one transition (retain-across-steps vs stream-fresh-each-step); precise mechanism not yet root-caused. This also means #892's "evict-and-re-admit" localisation is at best incomplete: here the page is pinned and **never re-admitted**, yet it still corrupts. ### Round-1 evidence (unchanged, verified) - 867/867 keys `reads_per_step = 1.000` — no high-reuse tensor. - Gap = 23 single-read large tensors, `bypassed_page_in_bytes/step = 1.048 GB` to the byte (~90% of the 1.158 GB recoverable gap). - Fresh #886 reproduction on `29d1515a`; softened reuse-vs-retention wording per your note (#864). - Arm 4a (threshold 1.2 GB, 33 tensors): byte-identical PASS, `htod` unchanged — you reproduced this to the byte. ### On item 3 itself The practical answer is unchanged: the gap is only safely recoverable by the funded **#864 zero-copy hybrid** (never evicts a retained page for a cold weight; ~8× on Linux #925/#936; WDDM-blocked by the aperture ceiling, not admission), so no managed-path admission policy should ship. The value here is the isolation, not a policy. ### Gates (run locally; no source changed since they last passed) - `cargo fmt -p onnx-runtime-ep-cuda -p onnx-genai-engine -- --check` ✅ - `cargo clippy -p onnx-runtime-ep-cuda --features cuda -- -D warnings` ✅ - `cargo test -p onnx-runtime-ep-cuda --features cuda --lib` — **374 passed** ✅ - `cargo test -p onnx-genai-engine --features native-backend,cuda --lib native_decode` — **96 passed** ✅ - mobius parity SOLO (`--ignored --test-threads=1`) — **2 passed** ✅ All bisection runs were env-only (the `PIN_KEYS`/threshold knobs, off by default); the shipped size-blind path is untouched and byte-identical. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: justinchuby <223556219+Copilot@users.noreply.github.com> Copilot-Session: d60eb808-7cc6-4abc-b48d-2a6dd3841624
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the zero-copy hybrid default safety budget platform-conditional instead of a single global 256 MiB constant.
Why
The 256 MiB default was conservative only because of a WDDM/VidMm memory-manager artifact measured on an RTX 4060 Laptop (#864 / #912): aggregate distinct host-mapped bytes read per decode step above ~0.44–0.65 GB silently return STALE data → wrong tokens, no error (48 cold weights collapsed generation 16 → 3 tokens).
#925 re-measured the hybrid on Linux and proved that ceiling is Windows-specific — absent here:
cuda_graphfallbacks=0 up to 6.795 GB of distinct host-mapped weights bound and re-read in place every decode step (704cuMemHostRegisterbinds), n=3, all runs byte-identical — ~15× the WDDM ~0.44 GB onset.cuMemHostRegister(READ_ONLY|DEVICEMAP)pins pages in the driver with no VidMm layer above it, so the ceiling that motivates the Windows value does not apply.Full measurement report: #925 (#925 (comment)).
Why 2 GiB (bounded, not unbounded)
Chosen deliberately conservative-but-useful:
ONNX_GENAI_ZERO_COPY_HYBRID_BUDGET_BYTES.Blast radius
Low. The whole feature is opt-in (defaults OFF behind
ONNX_GENAI_ZERO_COPY_HYBRID=1;zero_copy_hybrid_env_defaults_off_and_opts_inkept intact), so this default only affects users who already turned the hybrid on. Windows behaviour is unchanged.Implementation
const ZERO_COPY_SAFE_BUDGET_BYTES = if cfg!(target_os = "windows") { WDDM } else { NON_WINDOWS }— both named consts (ZERO_COPY_SAFE_BUDGET_BYTES_WDDM,ZERO_COPY_SAFE_BUDGET_BYTES_NON_WINDOWS) are referenced in every build, so each stays greppable and unit-testable (nodead_code).Tests / checks (local, green)
zero_copy_safe_budget_defaults_are_platform_aware: asserts WDDM=256 MiB (< the measured WDDM-safe 436 MB), non-Windows=2 GiB (< Re-measure the #864 zero-copy hybrid on Linux: the #912 negative is WDDM-scoped and does not transfer #925 6.795 GB and > WDDM band), and that the platform-selected const resolves correctly percfg.python3 .github/scripts/verify_documented_env_vars.py— env var honesty: 70 documented, all accounted forcargo fmt --all -- --check— cleancargo clippy --locked -p onnx-runtime-ep-cuda --features cuda -- -D warnings— cleancargo test -p onnx-runtime-ep-cuda --features cuda weight_paging— pass (incl. the intact opt-in-defaults-OFF test)Decision drop
.squad/decisions/inbox/holden-zerocopy-linux-default.md(Scribe to merge).References #925 and #864.
Run by the Squad team on the shared H200 box.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com