Repository navigation
DeepSeek-V4 HCA C1: single accounting authority for CSA/HCA device state groups (B6) - #2008
Conversation
… device state groups (DeepSeek-V4 C1/B6) Introduce CsaStateGroupLedger as the sole accountant for every CSA (CompressedSparseAttention) / HCA device state-group buffer, closing design blocker B6 (a second unaccounted alloc_raw path). The ledger owns bytes (never cursors), charges per (request, device) for isolation, fails closed BEFORE any physical allocation (managed limit; 0 = unlimited default), and releases via RAII so teardown returns to baseline. It is instance state threaded through CsaMetrics (the EP's telemetry surface), not a process-wide global, and not an allocator/cache/page-manager. The ledger is mandatory, not an Option: there is no optional-ledger / no-ledger escape hatch, so the type system alone forbids an unaccounted reservation path (the exact B6 hazard). Per the no-backward-compatibility directive this replaces the earlier Option<ledger> shim outright rather than layering a compat wrapper. "Disarmed" is therefore an unlimited limit (0 sentinel), not a structural bypass -- accounting still runs, it simply never refuses. CsaStateGroupDescriptor is a property gate (compression_ratio, cache_format, head geometry, device_count, compressed/carry state edges) parsed from the node -- not a model-name/shape allowlist -- carrying the exact reason on refusal via the typed CsaStateGroupRefusal enum. validate() accepts the op's ratio-4/128 range; validate_c1_runtime() admits ratio-128 (HCA) only, typed-refusing ratio-4/MTP rather than silently threading or falling back to dense. Wiring: CsaDeviceBufferManager::reserve unconditionally charges the ledger before alloc_raw and holds the RAII charge for the buffer lifetime; the CUDA CSA factory create() builds + validates the descriptor and threads the (mandatory) ledger + (request, device) key. The charge happens once at runner construction -- nothing on the captured/replay stream, host-only release -- so with the ledger unlimited (default) the reservation is byte-identical to the pre-accounting path. Tests: 11 CPU-only descriptor/ledger tests (accept/refuse matrix, fail-closed OOM without mutation, per-(request,device) isolation, baseline/peak, monotonic ids, C1 ratio gate, limit round-trip) + 6 GPU tests on a real CsaDeviceBufferManager (charge == sum(reserved), teardown to baseline, fail-closed-before-alloc no leak, isolation, stable workspace addresses, and an unlimited-ledger reservation that accounts without ever refusing). The existing CSA op-parity GPU suite (eager + capture/replay + speculative rollback + observability) stays green, proving no regression and the byte-identical disarmed path. Physical-allocator unification onto the governor/onnx-genai-kv and the native-decode present_*->past_* threading with captured >=16-decode CPU/CUDA bit-parity remain tracked next steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
d25b4d1 to
b104ce7
Compare
|
Force-pushed Change: the Byte-identity preserved: "disarmed" is now an unlimited limit ( Test delta: the obsolete Re-verified green after the change: Scope unchanged: physical-allocator unification onto the governor / Co-authored-by: Copilot |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2008 +/- ##
==========================================
+ Coverage 80.42% 80.93% +0.51%
==========================================
Files 424 425 +1
Lines 207556 208150 +594
Branches 207556 208150 +594
==========================================
+ Hits 166924 168472 +1548
+ Misses 34994 34035 -959
- Partials 5638 5643 +5
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
APPROVE B6 ACCOUNTING SLICE FOR MERGEIndependent, read-only review at exact HEAD Verified gatesSingle accounting authority — no unaccounted route.
Charges before allocation, fail-closed.
Exact per-class/total bytes, checked/saturating overflow.
RAII release exactly once on every exit path.
Isolation, unlimited sentinel, observability.
Byte-identity / op-parity preserved.
Truthful scope (verified honest).
Independent test/lint runs (idle A100, GPU 2, serial)
Non-blocking
Next honestly-scoped stepPhysical-allocator/governor unification (delegate Do not merge (draft, and pending rebase onto latest main). Verdict: APPROVE B6 ACCOUNTING SLICE FOR MERGE. — Independent reviewer, requested by Justin Chu |
…yGovernor (B6.2) Fold the CSA/HCA state-group ledger onto the one process accounting authority so there is a single set of device books, closing the B6.2 follow-up to #2008. Before this, `CsaStateGroupLedger` kept a private byte limit beside the shared `MemoryGovernor` — a second admission gate even though physical bytes went through `CudaRuntime::alloc_raw`. That is the "two sets of books" shape B6 exists to remove. Now every CSA reservation admits through `governor.reserve(Tier::Device, .., Workspace { step_scoped: false }, holder)` *before* any physical allocation, and the returned `MemoryLease` is held by the RAII `CsaStateGroupCharge` for the buffers' lifetime. CSA device residency therefore lands in `MemoryGovernor::used(Tier::Device)` alongside every other holder. The per-(request, device) / per-class counters remain only as a *derived attribution mirror* for observability the governor deliberately does not itemise — it never gates admission, so it is complementary, not a competing ledger. One reserve, one release, single reserve/commit/rollback transaction: a refused charge mutates nothing (fail closed, typed OutOfMemory before `alloc_raw`), and an alloc failure mid-reservation rolls back the physical buffers and releases the governor lease exactly once via RAII. Physical allocation still uses the EP's shared `alloc_raw`; migrating it onto a governed `DeviceAllocator` handle and threading native-decode present->past remain honestly-scoped next slices, not pulled in here. No backward-compatibility shim (per directive): the private-limit API (`CsaStateGroupLedger::set_limit`/`limit`, `CsaMetrics::*_limit`) is removed and every in-repo caller/test updated atomically. The EP threads its process governor into `CsaMetrics::with_governor` at registry build; the default (no-governor) path uses an unlimited reference `LedgerGovernor` so ordinary inference is byte-identical and disarmed. Tests (same commit, new semantics): - governor_sees_csa_reservation_in_shared_books: CSA bytes add to an unrelated holder's on one governor; teardown returns to baseline. - charge_reserves_and_releases_governor_exactly_once: fake counting governor proves one reserve / one release (no double charge). - ledger_reports_governor_device_ceiling / fails_closed_over_limit: fail-closed against the governor's device ceiling, no partial reserve. - csa_device_state reserve tests re-pointed at with_device_limit and assert the physical reservation is governor-visible and returns to 0. - CSA op-parity + capture/replay suite unchanged (26 passed, 1 ignored). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… (follow-up to #2008) (#2029) ## B6.2 — Unify CSA/HCA device accounting under the shared `MemoryGovernor` **Draft. Do not merge. Independent review required (excluding the author).** Follow-up to **#2008** (B6, merged as `7a162e9f4`), now rebased directly on `main` (the #2008 dependency has landed, so this is no longer stacked — it is a clean single-commit PR on `main`). ### Problem (B6.2) #2008 introduced `CsaStateGroupLedger` as the single *typed* accountant for CSA/HCA device state groups, but it kept its **own byte limit** (`set_limit`/`limit`) beside the process-wide `MemoryGovernor`. Physical bytes were reserved through the EP's shared `CudaRuntime::alloc_raw`, yet admission was decided by a **private CSA gate** — a second set of books. That is exactly the "two allocators / two ledgers" shape the B6 design blocker exists to remove. ### Change — one accounting authority, one transaction Every CSA reservation now admits through the shared governor **before** any physical allocation: ``` governor.reserve(Tier::Device, total, MemoryRole::Workspace { step_scoped: false }, holder) -> MemoryLease // held by the RAII CsaStateGroupCharge for the buffers' lifetime ``` - CSA device residency is now counted in `MemoryGovernor::used(Tier::Device)`, in the **same books** as every other device holder (weights, KV, workspaces). - The per-`(request, device)` / per-class counters (`compressed`/`carry`/`dense_ring`/ `index`/`scratch`) remain as a **derived attribution mirror** for observability the governor deliberately does not itemise. **The mirror never gates admission**, so it is complementary telemetry, not a competing ledger. - **Single reserve / commit / rollback transaction:** a refused charge mutates nothing (fail closed with a typed `OutOfMemory` *before* `alloc_raw`); an alloc failure mid-reservation rolls back the physical buffers and releases the governor lease **exactly once** via RAII (`Drop` clears the mirror, then the `MemoryLease` field drops). - Physical allocation stays on the EP's shared `alloc_raw` inside the governed transaction — this removes the *unaccounted* property (the real B6 defect) **without** inventing pages or touching the later-built `DeviceAllocator`/`ProcessMemoryManager` teardown ordering. ### Invariant / API - **API (crate-internal):** - `CsaStateGroupLedger::new(Arc<dyn MemoryGovernor + Send + Sync>)` — governor-backed. - `CsaStateGroupLedger::default()` — unlimited reference `LedgerGovernor` (disarmed, byte-identical); `#[cfg(test)] with_device_limit(bytes)` for fail-closed proofs. - `device_available_bytes()` / `governor_device_used()` replace the removed `limit()` / `set_limit()`. - `CsaMetrics::with_governor(governor)`; `csa_state_group_device_available_bytes()` / `csa_state_group_governor_device_used()` replace the removed `*_limit` accessors. - Provider threads its process governor at registry build (`new_with_policy_governor_and_manager`); the default (no-governor) `build_cuda_registry` path stays on the unlimited reference governor. - **No backward-compat shim** (per directive): the private-limit API is deleted and every in-repo caller/test updated atomically in this commit. ### Tests (same commit, new semantics) CPU-only (no GPU): - `governor_sees_csa_reservation_in_shared_books` — a CSA charge adds to an unrelated holder's total on one governor; teardown returns to baseline; the other holder is isolated. - `charge_reserves_and_releases_governor_exactly_once` — a fake counting `MemoryGovernor` proves **one reserve, one release** (no double charge, no second path). - `ledger_reports_governor_device_ceiling`, `ledger_fails_closed_over_limit_without_mutating` — fail closed against the governor's device ceiling, no partial reservation. GPU (idle A100, correctness — no perf claim): - `reserve_charges_ledger_and_releases_on_drop`, `reservations_are_isolated_per_request`, `reserved_workspace_addresses_are_stable`, `reserve_fails_closed_over_limit_without_leaking` (via `with_device_limit`), `unlimited_ledger_accounts_without_refusing` — now assert the **physical** reservation is governor-visible (`governor_device_used()`) and returns to baseline on teardown. - **CSA op-parity + capture/replay** integration suite unchanged: **26 passed, 1 ignored** (the ignored one needs external Mobius/MTP artifacts) — this is the stable-address / capture-no-alloc / byte-identity control. ### Validation - `cargo fmt -- --check`: clean. - `cargo clippy -p onnx-runtime-ep-cuda --features cuda,cuda-13000 --lib --tests -- -D warnings`: clean. - `cargo test -p onnx-runtime-ep-cuda --features cuda,cuda-13000 --lib csa_` on idle A100: **19 passed, 0 failed**. - `--features cuda,cuda-13000,gpu-tests --release --test compressed_sparse_attention_gpu`: **26 passed, 1 ignored**. ### Scope / honest next steps (NOT in this slice) - Migrating physical allocation off `alloc_raw` onto a **governed `DeviceAllocator` handle** (subject to the strict PMM/VMM teardown ordering) is a further, separately scoped step. - **`native_decode` present→past** state threading remains the next stacked slice. - No PagedAttention / Mobius / QMoE / BQMoE / residency / #1971 files touched. - No full-model perf / tok/s claim. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DeepSeek‑V4 HCA "C1" runtime slice — single accounting authority for CSA/HCA device state groups (B6)
Draft. Do not merge — opened for independent review.
This is the first, GPU‑verified increment of the DeepSeek‑V4 HCA (Hierarchical Compressed Attention) "C1" runtime slice. It closes design blocker B6 from
deckard-deepseek-v4-csa-hca-cuda-slice.md§7 ("onnx-genai-kv/shared accounting is not the authority; a second unaccountedalloc_rawpath exists for CSA device buffers") by introducing a single property‑typed accounting authority for CSA (CompressedSparseAttention) / HCA device state groups in the CUDA EP.What landed
New module
crates/onnx-runtime-ep-cuda/src/kernels/csa_state_group.rs:CsaStateGroupDescriptor— a property gate. Parsed from the node's declared attributes + state input edges (compression_ratio,cache_format,num_heads/head_dim/qk_rope_head_dim,index_head_dim,device_count, presence of the compressed(6)/carry(7) state edges). Not a model‑name/shape allowlist. Carries the exact reason on refusal via the typedCsaStateGroupRefusalenum (UnsupportedRatio, UnknownCacheFormat, Ratio4RequiresFp8Cache, Ratio4RequiresIndexHeadDim128, Ratio128RejectsFp4, InvalidHeadGeometry, RopeExceedsHeadDim, MultiDeviceAmbiguity, MissingStateEdge, OutOfMemory, UnsupportedC1Ratio).validate()accepts the op's full ratio‑4/128 range (op‑level).validate_c1_runtime()is the stricter C1 admission gate: ratio‑128 (HCA) only — ratio‑4/MTP are op‑supported but out of C1 scope and are typed‑refused, never silently threaded or dense‑fallback'd.CsaStateGroupLedger— the sole, mandatory accountant. Owns bytes, never cursors; charges per(request, device)for isolation;try_chargeis a check‑then‑charge under one mutex that fails closed BEFORE any physical allocation (managed limit;0 = unlimited, the steady‑state default); RAIICsaStateGroupChargereleases onDropso teardown returns to baseline. It is instance state threaded throughCsaMetrics(§8) — not a process‑wide global, and it is not an allocator/cache/page‑manager: it accounts the bytes the existing physical path (runtime.alloc_raw) reserves.No optional/no‑ledger escape hatch. Per the no‑backward‑compatibility during development directive, the ledger is a mandatory
Arc<CsaStateGroupLedger>on the reservation path (not anOption), so the type system alone forbids an unaccounted reservation — the exact B6 hazard. The earlierOption<ledger>seam was replaced outright rather than wrapped in a compat shim, and every in‑repo caller/fixture/test was updated atomically. "Disarmed" is therefore an unlimited limit (0 sentinel), not a structural bypass: accounting still runs, it simply never refuses, so byte‑identity is preserved without leaving a second unaccounted path.Wiring:
CsaDeviceBufferManager::reserveunconditionally charges the ledger beforealloc_rawand holds the RAII charge for the buffer lifetime (Drop frees buffers, then releases the charge).create()builds the descriptor,validate()s it (typed refusal), takes a per‑runner request id, and threads the (mandatory) ledger +(request, device)key.CsaMetricsgained theArc<CsaStateGroupLedger>pluspubobservability accessors (resident/peak/compressed/dense‑ring bytes, charge failures, active group count, per‑(request,device)residency, get/set limit).Invariants honored
OutOfMemorybefore any device memory is reserved (no leak).(request, device); teardown returns to baseline.Tests & measurement
(request,device)isolation, baseline/peak, monotonic ids, C1 ratio gate, limit round‑trip.CsaDeviceBufferManager(A100‑80GB, GPU 2):charge == sum(reserved), teardown → baseline, fail‑closed‑before‑alloc with no leak, per‑request isolation, stable workspace addresses, and an unlimited‑ledger reservation that accounts without ever refusing (charge_failures == 0, teardown → baseline — replacing the old no‑ledger case now that the ledger is mandatory).cargo fmtclean;cargo clippy -D warningsclean on lib and tests.Honestly scoped next steps (tracked, NOT in this slice)
CsaStateGroupLedger::try_chargeto delegate to the adoptedMemoryGovernorreserve(Tier::Device, bytes, MemoryRole::Workspace)/onnx-genai-kvso the physical bytes become governor‑visible (final B6 unification). The ledger'stry_chargeis already shaped as that adapter seam. This is an architecture migration (not a compat shim), deliberately deferred.present_* → past_*compressed/carry state at the existingensure_capacitysafe boundary and callsvalidate_c1_runtime, with captured ≥16‑decode CPU/CUDA bit‑parity andfallbacks == 0.CPU op‑level present→past carry/cursor progression across the decode boundary is already covered by the CPU oracle test
ratio128_stateful_carry_matches_full_recompute_across_decode_boundary.Co-authored-by: Copilot