Skip to content

Stage-1 store-only disk KV tier: eligible-group scoping + worker-local per-rank store (GLM53_KV_OFFLOAD, default off) - #99

Closed
nood-co1 wants to merge 9 commits into
MiaAI-Lab:mainfrom
nood-co1:pr/kv-offload-store-only
Closed

nood-co1 wants to merge 9 commits into
MiaAI-Lab:mainfrom
nood-co1:pr/kv-offload-store-only

Conversation

@nood-co1

@nood-co1 nood-co1 commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Stage-1 store-only disk KV tier: eligible-group scoping + worker-local store with chunk headers (GLM53_KV_OFFLOAD)

Status: knobs default OFF (control boot byte-identical). Host-side evidence below is complete; the live boot receipts (section ‘Receipts’) will be posted in this thread at the next server window — a measurement run currently owns the pair.
window after the #77 measurement pass. Codex gates: advisory
CODEX-OFFLOAD1-PLAN.md (DO-NOT-PROCEED v1 → all findings adopted/rebutted in
writing before build), adversarial diff review CODEX-OFFLOAD1-REVIEW.md
(DO-NOT-SHIP → all findings adopted/rebutted, fixes in 32f993e, confirm pass
appended there).**

Problem

A session whose KV has left the ~455K-token resident GPU envelope reprefills from
zero (174 s measured at 120K; 15–25 min estimated at 450K). The research plan
(PLAN-KV-OFFLOAD.md v2.1 + stage-0 receipts) designs a disk tier: park-to-disk on
store-cascade, restore on next touch. This PR is stage 1 only: store-only,
restore disabled by flag (the launcher refuses GLM53_KV_OFFLOAD_RESTORE=1),
no restore/read path
— the tier writes; nothing reads it back yet. knob=0 is
byte-identical serving; knob=1 carries one process-wide side effect (the
allocator change described in the Launcher section below).

Three things stand between the image's shipped kv_offload stack (present —
stage-0 receipt O4/R14) and a safe store on this deployment:

  1. The connector cannot boot on the hybrid layout. build_offloading_config
    asserts tokens_per_block % tokens_per_hash == 0 for every KV-cache group;
    the KpoolTailSpec scratch group (block 4, prefix_cacheable=False) fails
    (4 % 64). Upstream PR [KV Offload] Scope offload group configs to prefix-cacheable KV cache groups vllm-project/vllm#54743 (ours) scopes the group configs
    to prefix-cacheable groups; this overlay ports that scoping onto the image's
    older tree.
  2. The image wraps group specs. The connector receives
    UniformTypeKVCacheSpecs wrappers (subclass of KVCacheSpec only): the
    scheduler's isinstance dispatch would crash on group 0's full-attention
    assert and silently strip the mamba groups' alignment/CoW semantics. The
    port adds a fail-closed single-type unwrap and reads cacheability through
    the image's participates_in_prefix_caching name.
  3. The stock fs tier is silently wrong on a 2-node TP pair (plan risk R0).
    TieringOffloadingSpec.get_manager() constructs every secondary tier
    scheduler-side against the scheduler node's staging memoryview; on
    --nnodes 2 rank 1's staging mmap lives on the other Spark, so the fs tier
    would persist rank-0 bytes plus never-written garbage as the rank-1 half of
    every file — corruption that only surfaces at restore time. The store path
    must be worker-local: each rank writes its own bytes from its own
    staging mmap to its own NVMe.

What this ships

overlay/patch_kv_offload_scope.py — eligible-group scoping (port of vllm#54743)

  • OffloadingGroupConfig gains group_idx (original boot index, required);
    build_offloading_config filters to prefix-cacheable groups minus the fork
    policy exclusion of draft-model attention groups (is_eagle_group, or exact
    SlidingWindowSpec under an EAGLE speculative config) unless
    GLM53_KV_OFFLOAD_DRAFTER=1; the divisibility assert still guards eligible
    groups.
  • Scheduler port: group_states sized by the full group count; every pairing
    through group_idx; GPULoadStoreSpec.group_sizes/block_indices stay
    full-length with zeros for ineligible groups (worker layout unchanged);
    supports_partial_tail conservatively requires every group eligible.
  • Final eligible set on this deployment: {0, 2, 3, 4, 5} — g1 out by the
    upstream predicate, g6 out by fork policy (min-exempt drafter; Per-KV-cache-group prefix-cache retention: sparse drafter, dense MLA/mamba #83
    retention-0). Logged at boot.

overlay/patch_kv_offload_store_local.py — worker-local store + §4 chunk headers

  • Store jobs carry a versioned metadata side-channel (TransferJob.glm53_store_meta):
    ordered keys aligned 1:1 with the job's block order, plus boundary-manifest
    candidates (cumulative chunk-hash chains).
  • Each worker SNAPSHOTS the job's bytes out of its own staging mmap
    synchronously at DMA completion (capture-then-write — reset_cache()
    frees staging rows regardless of pending disk work, so async mmap reads
    could tear; captured at get_finished before this rank's ack, or right
    after worker.wait() on the flush/reset path), then writes the private
    buffers (512 MB budget) to
    GLM53_KV_OFFLOAD_DIR/glm53kv_<model>_<ns>_r<rank>/<hhh>/<hh>_g<g>/<hash>.bin
    on its local NVMe — real (unpadded) bytes only (the slot-share padding
    never hits disk), fsync + tmp-rename + dir-fsync; acks are never deferred.
    Rank = the initialized TP rank cross-checked against the connector config;
    any disagreement disables the writer (never cross-rank mixing).
  • Every chunk gets a real header (format v1): magic, namespace hash
    (fences model/vllm/build/TP/KV-dtype/hash-algo/num_speculative_tokens/
    per-group real page sizes/KDA state-ABI/format version), original group
    index, per-layer segment table (conv/temporal split with shape+dtype+stride
    for KDA layers), boundary token index, payload CRC, header CRC. Any mismatch
    at read ⇒ reject.
  • Boundary manifests publish per-rank only after all four mamba payloads +
    every cumulative full-attention chunk are durable AND header-verified
    (namespace+hash+group identity; per-chunk [hash, len, crc] recorded for
    full groups too); a key that failed to write this boot can never be
    manifested (failed-key ledger). Dedup likewise requires a verifying
    header, else the file is rewritten.
  • Inline keep-K retention (GLM53_KV_OFFLOAD_KEEP_BOUNDARIES, default 2):
    a manifest is superseded once beyond the K most recent boundaries of EVERY
    chain containing it (mamba payloads unlinked; full-attention chunks stay
    dense — cumulative references keep shallower boundaries restorable).
  • Failure semantics (plan §8): failed write ⇒ unlink + log (lost store = future
    reprefill; nothing can serve wrong bytes); ENOSPC ⇒ writer pauses; crash
    between group writes ⇒ orphans, no live manifest; overlay/kv_offload_store_gc.py
    (dry-run default) verifies headers, reports orphans, sweeps on request.
  • Restore stays cold: get_num_new_matched_tokens short-circuits to 0 external
    hits under GLM53_KV_OFFLOAD_RESTORE=0 — no loads, no promotions, no
    WAITING_FOR_REMOTE_KVS.

Launcher (start.sh)

Knobs validated fail-closed inside the pre-stop gate (bad knob or broken overlay
artifact exits 2 before restart stops anything); knob=0 adds no serve
argv, no mounts, no store dir — byte-identical serving. Knob=1 appends identical
--kv-transfer-config JSON to both ranks (TieringOffloadingSpec,
cpu_bytes_to_use, blocks_per_chunk=1, offload_prompt_only=false so
decoded tokens store too, no secondary tiers — see deviations), mounts
the store dir rw on both nodes, ships both overlays to both ranks, pins
their order (scope → store_local) in GLM53_OVERLAY_ORDER, and self-checks
both patchers' injected sources (--check-injected) inside the pre-stop
gate. The store namespace is fenced by the recipe checkout SHA
(GLM53_KV_OFFLOAD_BUILD_ID).

When GLM53_KV_OFFLOAD=1, the launcher also omits
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True from both containers
(087b5ea, found live in the receipt window): vLLM rejects OffloadingConnector
under expandable segments unless the CuMem allocator is enabled, which this
deployment does not use. knob=1 therefore runs both processes on PyTorch's
default native caching allocator — a process-wide change, not scoped to the
store writer
. Expandable segments exists to reduce fragmentation OOM under
changing allocation sizes, so long-context workloads on knob=1 can in principle
OOM despite apparent free capacity where knob=0 would not (GPU_MEM_UTIL
headroom is not a fragmentation guarantee). The knob=0 path keeps the stock
entry and remains byte-identical. The +8.3% cold-prefill overhead in the
receipts thread is entangled with this allocator change; an isolation
experiment (allocator-only vs store+allocator arms) is protocolized for the
next server window.

Deviations (recorded, with reasons)

  • No fs secondary tier in the connector JSON (Codex advisory finding 3/Q1,
    adopted): the scheduler-side FileSystemTierManager byte path is exactly the
    R0 hazard; configuring-then-neutering it adds fake cascade bookkeeping and a
    stock-namespace config.json for no benefit. The worker-local writer IS the
    disk tier; the in-image fs tier remains available untouched for stage-2 read
    work.
  • blocks_per_chunk=1 uniformly (plan §4 wanted 16 for MLA): the image has one
    global chunk size; all eligible groups share block 3584. g0 chunk files carry
    27,163,136 B payloads (11 MLA + 11 indexer pages — the stage-0 correction
    pricing the indexer KV rides in automatically).
  • CRC is zlib crc32, not crc32c (stdlib; the header names its algorithm).
  • The worker→scheduler ack protocol has no per-job failure bit in stage 1
    (Codex finding 2, partially rebutted): a failed key is un-retried this boot
    but can never be manifested; the failure bit lands with stage 2's T4 work,
    where it becomes load-bearing.

Tests (host-side, all green; suite 33 passed)

file covers
tests/test_kv_offload_scope.py patcher preflight/idempotence/tamper/half-patch on pinned image fixtures (sha256-recorded byte-exact captures); eligibility predicate matrix; patched-runtime drive under a stubbed vllm: eligible {0,2,3,4,5}, original-index keys, full-length zero group_sizes, mamba alignment 3584, store-only lookup short-circuit, no-scratch-model equivalence, divisibility regression
tests/test_kv_offload_store_local.py header codec round-trip + truncation at every region + CRC flips; store-job meta 1:1 alignment + cumulative manifests + TransferJob pickle; writer: capture-then-write snapshot isolation (staging mutated after capture ⇒ disk bytes unchanged), flush-path capture, real-bytes payloads, KDA segment split, header-verified dedup + manifest ordering, failed-key ledger, keep-K retention, ENOSPC pause (no partials/temps left), TP-rank disagreement disable; GC dry-run/sweep/keep-K incl. zero-manifest orphans, stale temps, forked-chain shared-ancestor protection
tests/test_launcher_kv_offload.py knob guard matrix incl. the RESTORE=1 refusal; pre-stop gate with zero docker/ssh calls; knob=0 no-mount parity; knob=1 both-rank env/mount/scp/JSON-shape parity + synthetic one-rank mismatch; overlay order pin

Receipts (pending: server window)

To be captured in the post-#77 window; the PR stays unopened until every line
has a receipt — and thereafter until the maintainer-response hold lifts:

  1. knob=0 byte-identical behaviour: boot with GLM53_KV_OFFLOAD=0; the
    vllm serve argv is byte-identical to the previous boot (no
    --kv-transfer-config), no connector init in the log, no store mounts;
    serving probes green.
  2. knob=1 boot receipts: [glm53-kv-offload-scope] eligible groups: [0, 2, 3, 4, 5] of 7 on both ranks; TieringOffloadingManager created with
    0 secondary tiers; [glm53-kv-offload-store] writer up: rank 0/2 … on deb8
    and rank 1/2 on d9d5 with per-rank base dirs; namespace JSON on both
    Sparks' local NVMe; driver version pinned in the capture.
  3. Store-cascade evidence: during a normal session, headered files appear
    under …_r0/**_g{0,2,3,4,5}/ on deb8 AND …_r1/** on d9d5 with payload
    sizes 27,163,136 (g0) / 21,086,208 (g2,g3) / 18,743,296 (g4,g5); boundary
    manifests for every complete 3584 boundary within keep-K; per-rank byte
    totals within ±10% of the stage-0 §C model; R0 rank-routing receipt: GC
    verifier reopen + checksum pass on BOTH ranks' stores across a container
    restart (verifier-level, not a GPU restore — stage 1 has no restore).
  4. Store overhead on prefill within noise: prefill/decode rates knob=1 vs
    knob=0 on the standard probe within the kit's measured noise band.
  5. Fail-closed negatives: bad knob values + GLM53_KV_OFFLOAD_RESTORE=1
    refused pre-stop with zero docker/ssh calls (live run); GC dry-run flags a
    deliberately truncated chunk and a manifest-less orphan; ENOSPC simulation
    (small loopback fs) pauses the writer with serving unaffected.

Submitted by nood-co1 on behalf of Blockbrain Labs (https://x.com/blockbrain_labs, GitHub org blockbrain-ai). Measured on our 2× NVIDIA DGX Spark deployment.

qualitycontrol and others added 6 commits September 1, 2026 18:54
…#54743)

The OffloadingConnector cannot boot on this hybrid layout: the image's
build_offloading_config builds an offload group config for every KV-cache
group and asserts tokens_per_block % tokens_per_hash == 0 across all of
them, so the KpoolTailSpec scratch group (block 4, hash grid 64) crashes it
at boot. Port our upstream PR vllm-project/vllm#54743 onto the image tree
(vllm 0.1.dev20051+g487ecf187, mid-generation: the scheduler NamedTuple
already carries group_idx, the config boundary does not):

- OffloadingGroupConfig gains group_idx (original boot index, required);
- build_offloading_config scopes the groups tuple to eligible groups:
  prefix-cacheable (via the image's participates_in_prefix_caching name)
  minus the fork policy exclusion of draft-model attention groups
  (is_eagle_group, or exact SlidingWindowSpec under an EAGLE speculative
  config) unless GLM53_KV_OFFLOAD_DRAFTER=1; the divisibility assert still
  guards eligible groups; one boot line names the eligible set
  ({0, 2, 3, 4, 5} on this deployment);
- scheduler port: group_states sized by the FULL group count, every
  config<->state pairing through group_idx, _group_config_by_idx for the two
  subscripts, full-length GPULoadStoreSpec group_sizes/block_indices with
  zeros for ineligible groups (the worker layout is built from ALL groups
  and stays unchanged), supports_partial_tail additionally requires every
  group eligible;
- [I]-specific adaptation: a fail-closed single-type unwrap for the image's
  UniformTypeKVCacheSpecs group wrappers, without which the connector's
  FullAttentionSpec assert crashes on group 0 and mamba groups lose their
  alignment/CoW classification.

Fail-closed patcher per repo convention: all anchors in all three files
preflight before any write; MARK sentinels; idempotent (marks-only
already-present check, because patch_kv_offload_store_local.py later edits
inside these regions); atomic replace; drift => nonzero.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…53_KV_OFFLOAD)

Stage 1 of the disk KV-offload plan: the tier writes, nothing reads it back
(restore short-circuited behind GLM53_KV_OFFLOAD_RESTORE=0).

The stock fs secondary tier is byte-invalid on this 2-node TP pair:
TieringOffloadingSpec builds every secondary tier scheduler-side against the
scheduler node's staging memoryview, and rank 1's staging mmap lives on the
other Spark - the tier would persist rank-0 bytes plus uninitialised garbage
as rank-1 halves. So the disk write moves to the workers:

- TransferJob gains an optional glm53_store_meta side-channel (versioned
  picklable dict): ordered keys aligned with the job's block order plus
  boundary-manifest candidates (cumulative chunk-hash chains);
- each worker, after its GPU->CPU DMA completes, writes its own bytes from
  its own staging mmap to its own NVMe (per-rank namespaced root; rank =
  initialized TP rank cross-checked against the connector config, mismatch
  disables the writer) and only then acks the job - staging stays pinned
  until every worker acks, so bytes cannot be evicted under the writer;
- every chunk gets a versioned header: magic, namespace hash (model, vllm
  version, build id, TP, KV dtype, hash algo, num_speculative_tokens, per
  -group real page sizes, KDA state-ABI descriptor), ORIGINAL group index,
  per-layer segment table (conv/temporal split with shape/dtype/stride for
  KDA layers), boundary token index, payload CRC; payload = real (unpadded)
  bytes only - the slot-share padding never reaches disk;
- boundary manifests publish per-rank only after all four mamba payloads and
  the cumulative full-attention chunks are durable (fsync file+dir,
  tmp+rename); a crash between group writes leaves orphans, never a live
  half-boundary; a key that failed to write this boot can never be
  manifested (failed-key ledger);
- inline keep-K manifest retention (GLM53_KV_OFFLOAD_KEEP_BOUNDARIES,
  default 2): a manifest is superseded once it is beyond the K most recent
  boundaries of EVERY chain containing it; full-attention chunks stay dense;
- failure semantics: failed write = unlink + log + lost store; ENOSPC pauses
  the writer; serving is never blocked on the tier.

kv_offload_store_gc.py: standalone verifier/GC (header+CRC walk, orphan and
corrupt reporting, manifest cross-reference, keep-K sweep; DRY-RUN default).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on both ranks

- Knobs with caller-export setness capture: GLM53_KV_OFFLOAD (0|1 strict,
  default 0), GLM53_KV_OFFLOAD_DIR (absolute, no quotes/whitespace; local
  NVMe on each Spark, mounted rw at /data/glm53-kv-offload on both ranks),
  GLM53_KV_OFFLOAD_CPU_GB (1..64), GLM53_KV_OFFLOAD_RESTORE (stage-1 hard
  rule: 1 is REFUSED), GLM53_KV_OFFLOAD_DRAFTER, and
  GLM53_KV_OFFLOAD_KEEP_BOUNDARIES (0..1024) - all validated inside the
  pre-stop gate, so a bad knob or a missing/truncated/mis-pointed overlay
  artifact exits 2 before restart stops the healthy pair.
- knob=0 adds NOTHING beyond the replayed env: no --kv-transfer-config, no
  store mount, no mkdir - serving is byte-identical to a build without the
  tier. knob=1 appends identical connector JSON to both rank inner scripts
  (TieringOffloadingSpec, cpu_bytes_to_use, blocks_per_chunk=1, NO secondary
  tiers - the worker-local writer is the disk tier; the scheduler-side fs
  tier is byte-invalid on 2-node TP), mounts the store dir on both nodes and
  ships both overlays over the scp -> /tmp -> mount chain.
- GLM53_OVERLAY_ORDER pins one apply list emitted verbatim into BOTH rank
  inner scripts (the kv-offload pair is order-dependent: scope must precede
  store_local, which refuses an unscoped tree); the prefix-cache overlay
  slots from PRs MiaAI-Lab#83/MiaAI-Lab#84 ride along as [ -f ]-guarded entries.
- Dockerfile: bake both overlays after the real-tree preflight test; ship
  the GC tool and tests into /opt/glm53.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d tier

tests/fixtures/: byte-exact pinned captures of the five in-image sources the
overlays patch (vllm 0.1.dev20051+g487ecf187, sha256s in the fixtures
README) - the patchers preflight/apply against the REAL text and the tests
exec the PATCHED modules under a stubbed vllm namespace
(tests/_kv_offload_stub_env.py), so anchor drift or a broken port fails on
the host, not on the server.

- test_kv_offload_scope.py: patcher preflight/idempotence/tamper/half-patch
  on the fixtures; eligibility predicate matrix (kpool never, drafter only
  under EAGLE context, strict 0/1 env); patched-runtime drive: eligible
  {0,2,3,4,5}, original-index keys, full-length group_sizes with zeros at
  1/6, num_kv_cache_groups=7, mamba alignment 3584, store-only lookup
  short-circuit (manager.lookup never called), no-scratch uniform model
  unchanged, misaligned cacheable group still asserts.
- test_kv_offload_store_local.py: header codec round-trip + truncation at
  every region + CRC flips; store-job meta 1:1 alignment with block order +
  cumulative manifest candidates + TransferJob pickle; writer on a
  miniature 7-group layout: real-bytes payloads, KDA conv/temporal segment
  split, manifest publish ordering, failed-key ledger, keep-K retention,
  ENOSPC pause with no partial files, TP-rank disagreement disable,
  delayed-ack drain; GC dry-run/sweep/keep-K incl. cumulative full-attn
  protection.
- test_launcher_kv_offload.py: knob guard matrix, pre-stop gate (bad knob /
  missing / truncated / mis-pointed overlay => rc=2 with zero docker/ssh
  calls), knob=0 no-mount parity, knob=1 both-rank env/mount/scp/JSON-shape
  parity with a synthetic one-rank mismatch, overlay order pin.
- test_kpool_tail_slotmap.py / test_xgrammar_termination.py: launcher-wiring
  assertions moved to the GLM53_OVERLAY_ORDER list (same shape as the
  kv-capacity-log branch); test_warm_restart_stdout.py: the -S JSON-builder
  count is now 4 (2 speculative + 2 kv-offload).
- README overlay/test table rows; docs/DESIGN-kv-offload-store-only.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rdening (Codex adversarial pass)

Adversarial diff review (CODEX-OFFLOAD1-REVIEW.md) verdict DO-NOT-SHIP;
every finding adopted or rebutted in writing:

1 (BLOCKER, adopted) reset_cache() frees ALL staging rows regardless of
  pending disk work and the very next step can DMA new stores into them, so
  async mmap reads could tear a payload under a valid CRC. The writer now
  SNAPSHOTS the job's bytes synchronously at DMA completion (get_finished,
  before this rank's ack; or right after worker.wait() on the flush/reset
  path via a new capture hook) and writes from private, budget-capped
  buffers; acks are never deferred; shutdown/mmap-cleanup coupling is gone.
  Regression test: staging rows zeroed after capture, disk bytes unchanged;
  flush-path capture test.
2 (MAJOR, adopted) the launcher JSON now sets offload_prompt_only=false
  (upstream defaults true and would silently skip decoded tokens); the test
  stub mirrors the upstream default instead of masking it.
3 (MAJOR, adopted) namespace record is fsynced and VALIDATED against an
  existing file (mismatch disables the writer); GLM53_KV_OFFLOAD_BUILD_ID is
  derived from the recipe checkout SHA and replayed to both ranks; dedup and
  manifest publication header-VERIFY identity (namespace+hash+group) for
  mamba AND cumulative full-attn chunks, with per-chunk [hash, len, crc]
  recorded for full groups (plan §4 wording honored).
4 (MAJOR, adopted) temp names gain the writer thread id; the GC scans and
  sweeps stale *.tmp.*, infers mamba groups from chunk HEADERS (orphans are
  found even when a crash predates the first manifest), and applies the
  writer's leaf-walk keep-set so forked chains keep shared divergence
  ancestors; forked-chain, zero-manifest-orphan and stale-temp tests added.
5 (MAJOR, partially adopted) both patchers gain --check-injected (compiles
  every injected source standalone) and the pre-stop artifact gate runs it;
  the residual exposure (an anchor mismatch surfacing only in-container) is
  the repo-wide overlay contract and is documented, not new to this pair.
6 (MINOR, adopted) the TP-rank fallback now logs its reason and records
  rank provenance in the namespace record; the cross-check still disables
  the writer on disagreement, and the two-node R0 receipt covers the
  fallback path live.

Full suite 33 passed; store tests now 60+ checks incl. the snapshot
regression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…temp GC

Codex confirm pass on 32f993e (CODEX-OFFLOAD1-REVIEW.md appendix): findings
1/2/5 PASS, rebuttals accepted, two residuals — both adopted:

- RESIDUAL MAJOR: the namespace record appended keep_boundaries/rank_source
  AFTER hashing but compared the WHOLE record byte-exact on the next boot,
  so changing retention K (or the rank-derivation path flipping between TP
  group and config fallback) spuriously disabled the writer. The record now
  separates {fenced, info}: the fenced portion alone is hashed and
  exact-compared; info fields are diagnostics. An unparseable record still
  fails closed. Tests C22b-d (K change reuses the store; corrupted record
  disables).
- RESIDUAL MINOR: namespace-record temps live BESIDE the per-rank base (the
  store root); the GC now scans that root non-recursively for
  glm53kv_*.json.tmp.* in addition to the recursive base scan. Test D9/D10
  updated to plant and sweep one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vLLM's VllmConfig validation refuses OffloadingConnector together with
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (the VMM allocator can remap
KV-cache virtual addresses onto different physical pages under
pinned/registered KV memory). The launcher exported the value unconditionally
to both ranks, so the first GLM53_KV_OFFLOAD=1 boot failed fast at config
validation — found live in the MiaAI-Lab#99 receipt window; the check is unreachable
from the host-side stubs.

knob=1 boots now drop the env on both ranks (torch default allocator, logged);
knob=0 boots keep the stock entry — container env byte-identical to a build
without the tier. Launcher test: C3b (knob=0 keeps it, both ranks) + D1b
(knob=1 drops it, both ranks).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nood-co1

nood-co1 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Live receipts — stage-1 store-only tier (server window 2026-09-01/02)

Captured on the production pair (deb8 head / d9d5 worker) in the post-#77 server window. All blocks below are condensed/redacted excerpts of the raw transcripts (kept in our receipts mirror), not full verbatim pastes. One launcher defect was found live and fixed during the window (receipt 2a) — provenance per receipt:

  • Receipt 1 (knob=0 control) ran from staged deploy set deploy/dogfood-next4 @ 7d98567 (= production deploy/dogfood-next3 972370a + pr/kv-offload-mla-restore 0a5c33e, which stacks this branch; launcher hunks re-derived). The staged .env was checked byte-identical to production before any boot (sha256sum of both files → the same 6a692047…, 2×).
  • Receipts 2b–4 ran from next4 @ 032ee21 (= the same set + the 2a fix 087b5ea). Note the staged set includes the stacked stage-2 branch, so the running build carries Stage-2 g0 (MLA + indexer) disk restore at 3584 boundaries (GLM53_KV_OFFLOAD_RESTORE, default off) + stage-3 mamba differential harness #100's overlays too (inert; its receipts are in that thread).
  • The build ID the writer fences into the store namespace is visible in the container env (GLM53_KV_OFFLOAD_BUILD_ID=032ee216bb7a on the knob=1 boots).

1. knob=0 byte-identical serving — CAPTURED

Control boot, knobs off. The vllm serve argv is byte-identical to the previous production boot (58 NUL-split argv entries, sha256 50a55ab48f8f…343ac2 on both captures):

argv lines: 58; sha256: 50a55ab48f8f09b20c3c8c2425f567381f8f8f37f2015f224016a593e1343ac2
  ok   argv byte-identical to previous production boot
  ok   no connector init in head log
  ok   no store MOUNT on head container
  ok   no store MOUNT on worker container
health=200

(The GLM53_KV_OFFLOAD* values are replayed into the container env at knob=0 with GLM53_KV_OFFLOAD=0 — the PR's documented contract is "no serve argv, no mounts, no store dir", which holds.)

2a. Launcher defect found live — fixed in 087b5ea

The first GLM53_KV_OFFLOAD=1 ./start.sh restart failed fast at vLLM config validation (excerpt from the boot log, mirrored as bootB-fail.txt):

pydantic_core._pydantic_core.ValidationError: 1 validation error for VllmConfig
  Value error, KV connector OffloadingConnector is incompatible with
  PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True unless enable_cumem_allocator
  is also enabled. …

The launcher exported PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True unconditionally to both ranks; the check lives in VllmConfig and is unreachable from the host-side stubbed tests. Fix (087b5ea, on this branch): knob=1 boots drop the env on both ranks (logged); knob=0 boots keep the stock entry byte-identically. Review status, disclosed: 087b5ea was committed and pushed during this window; launcher stub tests C3b/D1b were added for both directions, and no fresh adversarial diff review has been performed on it yet. Its visible consequence is real: every knob=1 boot now changes the allocator for both containers, not merely the store writer — which entangles receipt 4 below.

2b. knob=1 boot receipts — CAPTURED

(EngineCore …)  [glm53-kv-offload-scope] eligible groups: [0, 2, 3, 4, 5] of 7 (drafter_included=False)   [head]
(Worker_TP0 …)  [glm53-kv-offload-scope] eligible groups: [0, 2, 3, 4, 5] of 7 (drafter_included=False)
  ok   worker: eligible groups [0, 2, 3, 4, 5] of 7   [d9d5 container log]
(EngineCore …)  Created TieringOffloadingManager with primary tier (lru, 79 blocks) and 0 secondary tier(s)
(Worker_TP0 …)  [glm53-kv-offload-store] writer up: rank 0/2 base=/data/glm53-kv-offload/glm53kv__root_…_r0 eligible=[0, 2, 3, 4, 5]
  ok   worker: writer up rank 1/2   [d9d5 container log, same line shape with _r1]
  • kv-transfer-config on the argv (both ranks): {"kv_connector":"OffloadingConnector","kv_role":"kv_both","kv_connector_extra_config":{"spec_name":"TieringOffloadingSpec","cpu_bytes_to_use":4294967296,"blocks_per_chunk":1,"offload_prompt_only":false}}
  • Namespace-fenced per-rank base dirs on each Spark's local NVMe (…_r0 on deb8, …_r1 on d9d5); namespace JSON at each root.
  • Driver version (both ranks): 580.173.02.
  • Harness note: the first receipts pass reported FAILS=3 — a bug in our receipt script (grep -q under pipefail SIGPIPE-kills docker logs), not in the deploy; the corrected re-run reports FAILS=0. Both transcripts kept.

3. Store-cascade evidence — CAPTURED

Two cold prompts (fresh salts; the probe generator is labeled "20k" and the served prompt_tokens measured ≈25.9–26.1K — all "~26K" figures below are measured prompt_tokens), then the file survey. On-disk chunk sizes = §C payload sizes + per-chunk header, byte-exact:

deb8 (rank 0):                       d9d5 (rank 1):
      9 27167874   (g0)                    9 27167874
      6 21090519   (g2,g3)                 8 21090519
      6 18747175   (g4,g5)                 6 18747175
      4 27167873                           4 27167873
      2 21090518                           2 18747174
      2 18747174                           1 27167872
      1 27167872
30 chunks + 4 manifests + ns.json    30 chunks + 4 manifests + ns.json

Byte accounting against the stage-0 §C model, shown rather than asserted: a ~26K prompt crosses 7 aligned boundaries → per chain, 7 cumulative g0 chunks + (keep-K=2) 2 kept boundaries × 4 KDA groups. Two chains ⇒ predicted 14×27,163,136 + 8×21,086,208 + 8×18,743,296 = 698,919,936 payload B + 131,852 header B = 699,051,788 B vs observed 699,051,778 B per rank (10 files carry a 1-byte-smaller header). Chunk counts match the model exactly (14/8/8). Per-probe store traffic ≈ 350 MB per rank. Equal sizes and per-file CRCs do not prove cross-rank payload byte-identity (the ranks hold different TP shards by design).

GC dry-run, both ranks:

r0: SUMMARY files=30 bytes=699051778 manifests=4 chains=2 corrupt=0 orphans=0 … mode=DRY-RUN
r1: SUMMARY files=30 bytes=699051780 manifests=4 chains=2 corrupt=0 orphans=0 … mode=DRY-RUN

R0 rank-routing receipt (across a container restart): after 8 probe chains (~2.8 GB per rank) and a full restart of the pair, the GC verifier re-opened both ranks' stores with full payload-CRC verification:

rank0 (deb8): SUMMARY files=120 bytes=2796207103 manifests=16 chains=8 corrupt=0 orphans=0 superseded=0 incomplete_manifests=0 stale_temps=0 broken_manifests=0 mode=DRY-RUN
rank1 (d9d5): SUMMARY files=120 bytes=2796207113 manifests=16 chains=8 corrupt=0 orphans=0 superseded=0 incomplete_manifests=0 stale_temps=0 broken_manifests=0 mode=DRY-RUN

(verifier-level, not a GPU restore — stage 1 has no restore.)

4. Store overhead — FAILED as written, numbers below

The body's criterion is "within the kit's measured noise band" (≤0.3% CV). Measured (cold ~26K prefills, temp 0, max_tokens=4, idle server, background transfer paused; knob=1 extended to 6 reps, reps 4–6 quoted as the settled band):

  • knob=0 control: 25.50 / 25.26 / 24.71 s
  • knob=1 settled: 27.90 / 27.31 / 26.53 s

+8.3% mean wall time (paired +7.4–9.4%) — not within noise. No decode-rate comparison was captured in this window. Two effects are entangled by necessity: (a) the store writes themselves (~350 MB per rank per probe at capture-then-write), and (b) the 2a allocator change — the connector cannot run under expandable_segments:True, so every knob=1 boot also swaps the allocator. We did not attribute the split. If this overhead matters for a workload, the knob stays 0 (the default), where serving is byte-identical.

5. Fail-closed negatives — CAPTURED (live, pre-stop; healthy pair untouched)

head StartedAt before: 2026-09-01T17:24:40.20220316Z
GLM53_KV_OFFLOAD=2 ./start.sh restart                     → rc=2  "GLM53_KV_OFFLOAD must be exactly 0 or 1 (got: 2)"
GLM53_KV_OFFLOAD_RESTORE=1 ./start.sh restart             → rc=2  "GLM53_KV_OFFLOAD_RESTORE=1 requires GLM53_KV_OFFLOAD=1 …"
GLM53_KV_OFFLOAD=1 GLM53_KV_OFFLOAD_DIR=relative/path …   → rc=2  "GLM53_KV_OFFLOAD_DIR must be an absolute path …"
head StartedAt after:  2026-09-01T17:24:40.20220316Z   →  NOTHING-STOPPED

(rc lines and messages re-associated from the interleaved transcript; the live run proves rc=2 with the serving container untouched — the zero-docker/ssh-call property is proven by the stubbed launcher tests, not by this transcript. Negative 2 exercised the stacked deploy set's stage-2 guard; the stage-1-only launcher refuses RESTORE=1 with its own message.)

GC negative (deliberate corruption, in-container since the store is root-owned): one real g3 chunk truncated to 1,000,000 B, plus one 4 KiB garbage .bin planted. Detected: the truncated chunk (CORRUPT … payload length 995689 != 21086208), the garbage file (CORRUPT … bad magic), and the affected manifest (MANIFEST-MISSING-PAYLOAD, incomplete_manifests=1); nothing deleted in dry-run. Note: the garbage file is classified corrupt, not orphan — the body's "manifest-less orphan" cell (a valid-headered chunk no manifest references) was not planted and stays pending with ENOSPC.

Pending, with reasons:

  • ENOSPC simulation (small loopback fs): needs root on the head; no passwordless sudo on this kit. The ENOSPC pause path is covered by the host writer test.
  • GC orphan-classification cell (valid-header, manifest-less chunk): not planted this window (needs a header-writing helper on the host); the corrupt/incomplete-manifest cells above are captured.

Store dirs were removed at window close (stage-1 rollback rule); the production deploy was restored and verified — deployed state at window end: deploy/dogfood-next3 972370a, knobs absent, serve argv sha equal to the pre-window capture, health 200, cold-probe times back at the control band (24.87–26.57 s).

…NGES, 3 minors adopted)

1. Test harness: _env_of_worker only counts KEY=value tokens immediately
   following -e, so a drifted worker env assignment fails the env checks
   instead of passing on a stray token.
2. Comment/log accuracy: vLLM rejects OffloadingConnector under
   expandable_segments:True UNLESS the CuMem allocator is enabled (not on
   this deployment) - start.sh comment, launcher log line, D1b test comment.
3. (PR body edit, no code) blast-radius claim corrected + allocator-drop
   disclosure paragraph; see CODEX-087B5EA-REVIEW.md disposition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nood-co1

nood-co1 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: the owed adversarial review of 087b5ea (alloc-conf drop) is done.

The receipts comment above disclosed that 087b5ea (knob=1 drops PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True on both ranks) was committed mid-window with launcher stub tests only and no fresh adversarial diff review. That review has now run — an adversarial pass over the diff plus the full launcher env-assembly, precedence, and test-harness context. Verdict: ship-with-changes — no blocker/major findings; the runtime fix is confirmed correct on both ranks (empty-array expansion is safe under set -euo pipefail on bash 3.2/5; knob=0 keeps the stock entry at the same value and position on both the head array path and the worker string path; the caller-over-.env knob resolution completes before launch_cluster on every real path; the image bakes no PYTORCH_CUDA_ALLOC_CONF ENV that an omitted -e would unmask).

Three minor findings. Findings 1–2 were adopted in 0431c66 (additive on this branch); finding 3 was applied separately to the PR body:

  1. Test harness: the worker-side env parser accepted any KEY=value token in the ssh command; it now only counts tokens immediately following -e, so a drifted worker env assignment fails C3b/D1b instead of passing on a stray token.
  2. Comment/log accuracy: vLLM rejects OffloadingConnector under expandable_segments:True unless the CuMem allocator is enabled (not used on this deployment). The in-code comment, launcher log line, and test comment now say so; the pydantic error quoted in the receipts comment already carried the full condition.
  3. PR body: the "zero blast radius on serving" claim understated knob=1 — the allocator change is process-wide on both ranks, not scoped to the store writer. The body now says knob=0 is byte-identical / knob=1 carries the allocator change, and a Launcher-section paragraph discloses the drop, the fragmentation/OOM caveat of leaving expandable segments (headroom is not a fragmentation guarantee), and CuMem as the unchosen alternative.

0431c66 is merged forward to pr/kv-offload-mla-restore (#100's branch) as c5dd69d. Host suites re-run after the changes — store branch: 33 pytest passed + 3 standalone runners rc=0; offload branch: 40 pytest passed + 3 standalone runners rc=0.

On the entangled +8.3% receipt-4 result: an isolation protocol is now written and adversarially reviewed — a three-arm boot design (control / allocator-dropped-without-store via a test-only scratch-branch hook / knob=1) with pre-declared bars. It is not a full factorial: the stock-allocator+store cell is unbootable on this deployment's non-CuMem path, so the design attributes conditional effects only (allocator effect with the store off; on-path store overhead under the dropped allocator — the background write path is priced separately since it is off the request path). It runs in the next server window; the outcome lands in this thread under the pre-declared bars, which include a not-reproduced outcome and a mixed/no-attribution outcome as well as the allocator-accounts and store-path-cost verdicts.

@nood-co1

nood-co1 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Receipt 4 update: the +8.3% store overhead did not reproduce under the pre-registered isolation protocol

Receipt 4 in this thread reported knob=1 cold prefill at ~26K tokens +8.3% vs control, failing the within-noise bar, with a caveat that it was entangled with the mandatory allocator change. We ran the pre-registered three-arm isolation protocol on the same pair (boot-isolated arms, execution order A→C→B, 8 cold reps per arm, fresh cache_salt namespace per rep, all reps hitsdelta=0, paired-median tolerance T=0.80 s declared in advance):

Arm Config Cold prefill median (~26K, n=8)
A knob=0, stock allocator (production baseline) 25.55 s
B knob=0, expandable_segments dropped (allocator alone) 25.32 s
C knob=1 (store active; allocator dropped as required) 25.91 s
A′ re-baseline after restore (n=3) 25.24 s

Median paired deltas (per-index, bootstrap 95% CI, 10,000 percentile resamples): C−A +0.36 s [+0.21, +1.51]; C−B +0.49 s [+0.31, +1.81]; B−A −0.19 s [−0.30, +0.07]. Individual C−A deltas reached +3.55 s (arm C's first two reps carried boot-settle tails of 29.24/27.03 s); settled medians (reps 3–8): A 25.63 s, C 25.95 s. A′ drift check passes (|25.24 − 25.55| = 0.31 < T). The 3-rep decode probe was within its band in all arms.

Verdict per the protocol's first-match ladder: R0 — the earlier +8.3% did not reproduce. This run does not establish whether the earlier excess was environmental, allocator-related, or store-related; it establishes that under quiescence and cold-guard gates the knob=1 on-path overhead sits within the pre-declared tolerance.

Execution exceptions, disclosed: arm B's first boot was voided before any reps (warm-up never settled — the intermittent boot-settle failure noted earlier in this thread); the single contingency reboot supplied B's eight valid reps. Store activity during arm C wrote 136 files / ~2.80 GB per rank; the driver's post-run empty-root gate FAILED (root-owned files survived a non-sudo clear) — roots were cleared with sudo afterwards and now verify empty on both ranks (0 files head, 0 files worker). Production checkout and health verified restored (972370a, health 200).

Full receipts: receipts-overhead-iso/ (per-rep rows, argv shas, boot timestamps, quiescence/cold-guard checks). The body's Receipts section will be updated to reference this correction.


Submitted by nood-co1 on behalf of Blockbrain Labs (https://x.com/blockbrain_labs, GitHub org blockbrain-ai). Measured on our 2× NVIDIA DGX Spark deployment.

…-only

# Conflicts:
#	.env.example
#	README.md
#	start.sh
@nood-co1

nood-co1 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Updated to current main (e25aa70) by merging main into the branch — merge commit 26ca3cd, plain push, no force-push; tested history preserved (83e4bd5…0431c66 incl. the disclosed 087b5ea alloc-conf fix remain ancestors). Conflicts: start.sh (7 hunks — kv-offload knob capture/config/bool-flag helper/validator block/-e lines vs main's indexer-workspace/spinwait equivalents, both sides kept; both rank-script apply blocks re-derived onto emit_overlay_block, never keep-both-merged), .env.example, README.md (overlay table: both row sets kept). Dockerfile merged clean and was verified to carry BOTH chains: #77's fat-kernel copy/patch/compile-assert lines AND this PR's kv-offload test-then-patch chain. Interaction fixes inside the merge commit: GLM53_OVERLAY_ORDER += patch_spinwait.py + patch_indexer_workspace.py (main's own inline position); patch_exl3_fat_kernel.py is deliberately NOT added to the runtime order — it is a build-time patch (usage: EXLLAMAV3_EXT SOURCE_DIR, applied in the Dockerfile against the exllamav3 source tree before compilation; running it argument-less at container boot would abort the rank scripts) — recorded as a comment on the order list so the exclusion is pinned, with the compiled kernel asserted at image build; the launcher kv-offload guard harness pre-sets the two new knobs. Re-run green: bash -n, tests/test_launcher_kv_offload.py, tests/test_kv_offload_scope.py, tests/test_kv_offload_store_local.py, full host suite 19/23 (4 known env fails identical on upstream/main). Host suites re-run green post-merge. No new live receipts were taken for this update: the pre-merge receipts apply to the kv-offload path, which the resolution leaves byte-untouched; the rest of the deployment this branch now sits on (E2 fat kernels, MNBT default 7168, #63 template, indexer-workspace/spinwait knobs) is main's own merged, receipted work, not something this PR measures.

@plotarmordev plotarmordev added triage/skip Community triage 2026-09-09: not recommended for a 2x GB10 TP=2 E3 deployment P3 Low urgency, experimental, stale, or needs more evidence merge-hold Do not merge until the stated maintainer dependency or hold is cleared. needs-final-review Prior audit is recorded; current-head review and merge disposition still need finalization. reviewed Our source review or technical verdict is recorded; not merge approval or runtime qualification. and removed triage/skip Community triage 2026-09-09: not recommended for a 2x GB10 TP=2 E3 deployment needs-final-review Prior audit is recorded; current-head review and merge disposition still need finalization. labels Sep 9, 2026
@plotarmordev

Copy link
Copy Markdown
Collaborator

Review follow-up — 26ca3cddd56a

This is a maintainer scope deferral, not an author failure. Your explicit store-only boundary, allocator caveats, and correction of the earlier overhead measurement are acknowledged.

We have not selected an end-to-end hybrid-cache disk-tier design. A store-only stage with restore disabled does not yet deliver the restore benefit that would justify adopting this integration by itself. The prior #100 outcome also needs to be accounted for before choosing a staged approach.

Please hold as-is; no rebase or new measurement is requested. The merge hold means we own the architecture/prioritization decision, not that you owe an unspecified test.

Unless explicitly noted above, this update is a source/discussion review, not a new live qualification run.

@plotarmordev

Copy link
Copy Markdown
Collaborator

Maintainer architecture decision — close the store-only stage, preserve its work

We are declining to adopt a store-only stage without a usable end-to-end hybrid restore path and are closing this PR on architecture/scope grounds, not as a duplicate or a defect-driven rejection. #58 is the experimental offload direction we will pursue; it is not qualified or approved to merge, and it does not replace the unique format/GC work here.

We reviewed #100's closed-unmerged disposition: its group-0 restore is inert on this model's seven-group hybrid layout because the mamba groups have no restore source and its load-failure integration requires a single total group. The later single-group probe reached disk-job creation but did not establish successful worker restore. Thus #100 is not an available restore stage that makes adopting #99 alone deliver the intended benefit.

Thank you @nood-co1 and Blockbrain Labs for the worker-local disk format, namespace/rank fencing, chunk headers and CRCs, durable boundary manifests, GC/verifier design, eligible-group scoping port, and unusually explicit receipts. The earlier +8.3% overhead did not reproduce under your predeclared isolation protocol; we are not reviving that result as a rejection reason or asking for the pending receipt cells.

The source branch pr/kv-offload-store-only is being left intact. Preserve the exact reviewed source at 26ca3cddd56ab5ab271ff26951db4a96eb6cc211, especially docs/DESIGN-kv-offload-store-only.md and overlay/kv_offload_store_gc.py, as design references for any future hybrid restore proposal. No rebase, new measurements, or other author work is requested. A future adoption/reopening decision belongs to maintainer @plotarmordev when an end-to-end hybrid restore direction is actually selected; this closure does not create a standing request to build one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-hold Do not merge until the stated maintainer dependency or hold is cleared. P3 Low urgency, experimental, stale, or needs more evidence reviewed Our source review or technical verdict is recorded; not merge approval or runtime qualification.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants