Skip to content

Fix native peer-access OOM without reducing cache capacity - #286

Merged
lukealonso merged 2 commits into
mainfrom
martin/native-cache-peer-access-oom
Nov 13, 2026
Merged

lukealonso merged 2 commits into
mainfrom
martin/native-cache-peer-access-oom

Conversation

@voipmonitor

@voipmonitor voipmonitor commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Native startup with sixteen tensor-parallel ranks can fail in cuMemSetAccess after KV backing is mapped even though framebuffer memory remains available. Some cores declare all ranks as readers where only eight-rank context-parallel groups (DCP8) import the cache. When all sixteen ranks import it (DCP16), fine-grained backing for snapshot aliases can exhaust UVM's shared RM handle namespace. CUDA reports the underlying NV_ERR_INSUFFICIENT_RESOURCES (0x1a) as OOM.

Static model and peer-exchange storage is allocated before KV sizing. The failing operation grants access to the KV backing itself. The correction therefore scopes access to real importers and reduces actual backing objects while preserving the full cache layout contract.

What changed

  • Derive access from validated Memory::Peer imports, including the owner. Replace the two all-descriptor grant passes with one grant per physical mapping. Existing cores whose PeerMemory.readers includes every TP rank remain compatible, and TP16 static imports retain their required access.
  • In the cache layout, permute backing-plane assignments only among chunks with identical bytes per page, byte offset, and mapped length. Reorder complete chunks within an uninterrupted interval of one snapshot row, then merge adjacent spans of the same KV plane into one physical allocation and alias mapping. Boundary-crossing and partial row spans retain their positions and lengths.
  • Preserve every plane's byte coverage, physical byte count, recorded virtual addresses, row strides, KV page capacity, snapshot count, and each snapshot's exact conflicting-page set. Snapshot aliases retain their own access permissions.
  • Attempt cache allocation and access once, without retry or capacity backoff. Keep range-specific CUDA error context and per-reader free-memory observations. Publish capacities only after all ranks succeed.

The change is implemented in shared native storage code. Existing signed cores load without re-export or graph changes.

Result

For the Kimi K3.1 Pro DCP16 layout with 2,234 local KV pages, 112 snapshot rows, and five execution rows, KV backing chunks per rank fall from 1,920 to 1,625, with identical physical bytes and snapshot conflicts. Native serving succeeds on the first allocation attempt with the complete physical-memory plan. KV capacity and snapshot capacity are both retained:

Cache import group Default and --memory-limit=-0G --memory-limit=-1G
DCP16 4,575,232 KV tokens; 112 snapshots 3,553,280 KV tokens; 86 snapshots
DCP8 3,145,728 KV tokens; 153 snapshots 2,650,112 KV tokens; 129 snapshots

The lower capacities in the -1G column come solely from the explicitly requested reserve. There is no automatic reserve or reduction after an allocation failure.

Evidence and qualification

Native qualification used sixteen RTX PRO 6000 Blackwell Workstation Edition GPUs (SM120), NVIDIA driver 615.71.10, on Frank1. The unchanged Kimi K3.1 Pro TP16 payload cores were gb202-tp16-202611121210 for DCP16 and gb202-tp16-202611131301 for DCP8, under /data/kimi31-support/k31-synthetic-comm-lab/core-cache/models/kimi-k31-mxfp4/. Serving selected concurrency 4, seven DFlash2 draft tokens, adaptive verification, no history drafts, and --kv-cache-limit memory; the configuration reserve was zero.

  • All six combinations of DCP8/DCP16 and default/-0G/-1G passed generation at concurrency one and four (C1/C4, with a 1,024-token completion limit per measured request), three cold 32,768-token prefills, nonempty response and usage checks, and clean shutdown. Every case had one cache allocation attempt and 384 successful shared-allocation access records with exactly eight or sixteen readers, respectively. Capacities were asserted against the values in the table.
  • A driver trace of the failing DCP16 load recorded UVM status 0x1a at mapping ioctl 521,881. A separate reproducer using only 2 MiB of physical backing reached the same status with over 101 GB free per GPU. The packed DCP16 load and serving checks completed with 444,837 cumulative mapping ioctls and no failed mapping calls. These are resource counts, not measurements of a driver memory reserve.
  • 133 runtime-core CPU tests passed. Layout tests compare complete KV-plane coverage, physical bytes, alias coverage, and every row's exact conflicting-page set across the DCP8/DCP16 geometries and 168 small partial-row layouts. Existing tests cover actual-reader derivation, overdeclared reader-list compatibility, and driver error context.
  • The GPU alias regression passed with both mixed-plane and merged equal-plane layouts. Distinct byte patterns within each row are preserved while snapshots are released and every unowned KV page is overwritten.
  • Bounded TLA+ lifecycle models and observed Rust transition conformance passed; mutation checks were not run. These check logical ownership and ordering, while the GPU test checks physical alias behavior. The release CLI build passed.

The ordinary CPU run ignored seven GPU cases; the targeted alias case was then run explicitly, leaving six other GPU fixture tests unrun. Native qualification is limited to the hardware, driver, cores, and workloads above. It is serving/alias correctness qualification, not a full model-quality evaluation. The broader Helios suite was not rerun for this revision.

Focused reproduction commands, from the repository root:

cargo test --offline --locked --release --manifest-path helios/Cargo.toml \
  --features runtime-core,server --target-dir flare-cli/target --lib runtime_core:: -j 4
CUDA_VISIBLE_DEVICES=0 cargo test --offline --locked --release \
  --manifest-path helios/Cargo.toml --features runtime-core,server \
  --target-dir flare-cli/target --lib \
  runtime_core::cache_memory::tests::cuda_aliases_preserve_rows_while_unowned_pages_are_rewritten \
  -j 4 -- --ignored --exact
cargo build --offline --locked --release --manifest-path flare-cli/Cargo.toml \
  --target-dir flare-cli/target -j 4

The GPU command requires an assigned idle CUDA device. The lifecycle command and pinned TLA+ tools setup are documented in verification guidance. Exact native launch arguments, response results, and raw driver/startup logs are retained on Frank1 under /data/kimi31-dcp8-20261112/flare-dcp8-peer-access-fix/.local-dev/peer-access/; the final serving cases have the dcp16-packed-* and dcp8-packed-* prefixes and are summarized by packed-matrix.log.

Cache planning can consume nearly all free GPU memory before post-allocation
cuMemSetAccess calls create driver access state. The loader also repeats static
grants and uses every signed reader declaration, including ranks that never
import the allocation. This can turn an otherwise valid automatic KV capacity
into an allocation OOM and force an unnecessary manual reserve. Use validated
imports as the access authority and make single-host cache allocation
transactional so a failed attempt can be reduced safely.

Replace the two all-descriptor grant passes with one owner-plus-actual-importer
grant per physical mapping. Apply the same path to static and runtime-sized
storage, preserve owner-only continuation aliases, and log per-reader free-memory
deltas around cache grants. Keep core sizing declarations until every rank maps
and grants its cache; only then publish execution state and capacity bindings.
For cache cuMemCreate, cuMemMap, or detailed cuMemSetAccess OOM on one host,
synchronize all contexts, unmap every cache mapping and alias, account for
retained free-memory loss, and retry with doubling KV-budget reductions.
Cross-host allocation remains one-shot; other errors and cleanup failures abort
startup. Existing signed cores remain loadable without re-export.

Successful first attempts keep the capacity selected by the original memory plan
and pay no retry allowance. A single-host cache OOM can now release the attempt
and recover at a smaller common KV capacity instead of requiring a guessed
manual reserve. Steady-state allocation, graph contracts, and model computation
are unchanged.

Focused runtime-core tests on current main pass: 133 passed, 7 ignored. Existing
exported TP16 cores with 8-rank and 16-rank import groups completed native
serving with no manual memory reserve, including C1/C4 generation, 32K prefill,
and non-empty response checks. The 8-rank case succeeded on its first attempt
with 3,145,728 KV tokens and 153 snapshot slots; all 384 grants had exactly eight
readers. The 16-rank case hit a final-grant OOM, unmapped the attempt, and
succeeded after a 96 MiB/GPU budget reduction with 4,485,120 KV tokens and 109
slots; successful grants had 16 readers. Separate 8-rank runs also passed with
-1G and -0G reserves. The broader Helios suite had 1,366 passes; four
missing-template failures and one cache-selection failure reproduced on the
parent source. That full run predates the rebase onto current main. Native retry
exhaustion at minimum KV capacity and cross-host retry were not tested;
cross-host retry is intentionally not enabled.

Design-Signed-Off-By: Claude Code (Claude Fable 5); design-review=ed22
Co-Authored-By: Claude Code <noreply@anthropic.com>
@original-el8 original-el8 added area:caching Prefix/KV reuse, cache retention, and model or payload cache behavior. area:memory Memory budgets, allocation lifetimes, scratch sharing, and capacity. bug Something isn't working priority:P2 Normal bug priority: workflow failure or diagnosis with narrower impact or an available workaround. triage:review Proposed change needs code review; this does not imply merge readiness. labels Nov 13, 2026
@original-el8

Copy link
Copy Markdown
Collaborator

Categorized as P2 cache-allocation correctness work requiring review. The proposed transaction delays publishing capacity bindings until every local rank succeeds, then cleans up a failed cache attempt before retrying at a smaller common budget. The reported TP16 run exercised a real final-grant OOM and recovered after a 96 MiB/GPU reduction.

Review cleanup and owner/importer grants, especially continuation aliases, error paths and retry exhaustion at minimum capacity. The PR explicitly leaves cross-host allocation one-shot and has not exercised native retry exhaustion. No model label is inferred from the TP16 rank count.

Native startup with sixteen-rank cache import groups (DCP16) can exhaust
UVM/RM mapping handles while framebuffer memory remains free. Small KV
backing allocations used by snapshot aliases create separate allocation
and per-device mapping objects. CUDA exposes the resulting
NV_ERR_INSUFFICIENT_RESOURCES as an out-of-memory error. The backing can
be merged without changing virtual cache layout, so handle pressure need
not be relieved by shrinking KV.

Replace cache allocation retry and budget backoff with one allocation
attempt. Permute backing-plane assignments only between chunks with
identical bytes per page, byte offset, and mapped length, then reorder
whole chunks within one snapshot row and merge adjacent spans of the
same KV plane. Keep spans crossing row boundaries and partial row spans
in place. Preserve physical byte coverage, recorded addresses,
capacities, and each snapshot's exact set of conflicting KV pages.
Signed core metadata and owner/importer permission sets remain
compatible; no re-export is needed.

For a Kimi K3.1 Pro TP16 layout with 2,234 local KV pages and 112
snapshots, KV backing chunks per rank fall from 1,920 to 1,625. With
default memory sizing or --memory-limit=-0G, DCP16 retains 4,575,232 KV
tokens and 112 snapshot slots. Eight-rank import groups (DCP8) retain
3,145,728 tokens and 153 snapshot slots. Capacity selection is
unchanged; no additional reserve or allocation retry remains.

Native startup and replay are qualified on sixteen RTX PRO 6000
Blackwell GPUs with driver 615.71.10: both import group sizes passed
default, -0G, and -1G, including generation at concurrency one and four,
three cold 32,768-token prefills, reader checks, and clean shutdown. The
runtime-core CPU suite passed 133 tests; the GPU alias test passed with
distinct byte patterns, including merged backing. Bounded TLA+ models
and Rust conformance checks passed without mutation testing, and the
release CLI built successfully. Six other GPU fixture tests were not
run. Qualification does not establish model quality or support on other
hardware/driver versions.

Design-Signed-Off-By: Codex (GPT-6); design-review=ed22
@voipmonitor voipmonitor changed the title Fix native cache peer access and retry allocation OOM Fix native peer-access OOM without reducing cache capacity Nov 13, 2026
@original-el8
original-el8 requested a review from yatesdr November 13, 2026 19:47
@voipmonitor

Copy link
Copy Markdown
Collaborator Author

Regarding the retry-related review items in this comment: the DCP16 failure was UVM/RM mapping-resource exhaustion (NV_ERR_INSUFFICIENT_RESOURCES, 0x1a) exposed by CUDA as OOM. Static model and peer-exchange buffers are allocated before KV sizing; the failing access grant concerned the KV backing itself.

Commit d63cd1fc removes allocation retry, cleanup-for-retry, and budget backoff. It merges actual backing allocations and alias mappings within individual snapshot rows, preserving recorded addresses, physical bytes, KV capacity, snapshot count, and each snapshot's exact set of conflicting KV pages. Access remains scoped to the owner and actual importers, including for cores with overdeclared reader lists. Capacity bindings still wait until every local rank has mapped and granted its cache.

Allocation is attempted once on both single-host and cross-host paths, so retry exhaustion and the 96 MiB/GPU reduction are no longer part of the implementation. Review of alias lifetime, owner/importer permissions, and error paths remains relevant. Default and explicit zero-reserve startup retain 4,575,232 KV tokens and 112 snapshots for DCP16, and 3,145,728 tokens and 153 snapshots for DCP8, without an added reserve.

Native startup and replay are qualified on Frank1's sixteen RTX PRO 6000 Blackwell GPUs with driver 615.71.10: all six DCP8/DCP16 × default/-0G/-1G cases passed C1/C4 generation, three cold 32K prefills, reader/capacity assertions, and clean shutdown on the first allocation attempt. The 133-test runtime-core CPU suite, the GPU alias test with distinct within-row byte patterns, and bounded TLA+/Rust conformance checks passed. Six other GPU fixture tests were not run. The PR description identifies the exact cores, configuration, evidence locations, and qualification limits.

@lukealonso
lukealonso merged commit d7a2081 into main Nov 13, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:caching Prefix/KV reuse, cache retention, and model or payload cache behavior. area:memory Memory budgets, allocation lifetimes, scratch sharing, and capacity. bug Something isn't working priority:P2 Normal bug priority: workflow failure or diagnosis with narrower impact or an available workaround. triage:review Proposed change needs code review; this does not imply merge readiness.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants