Repository navigation
Canary the DSA indexer split and chunk 16384 on the long-context r2 - #302
Conversation
Pins r2 to the v2 engine image published from #300 (8ff1a487b98a), sets SGLANG_DSA_INDEXER_QSPLIT=1 and raises its prefill chunk to 16384. r1 is left completely untouched -- same #294 image, chunk 8192, pdi 1, split unset -- so it stays a live control and a targeted `compose up` recreates r2 alone. Structurally r2 now needs its own image AND environment: a YAML merge key replaces a list rather than extending it, so r2 cannot inherit the anchor's environment and add one variable. The generator derives r2's block from the anchor at generation time rather than duplicating it as a literal, so the two cannot drift. The chunk and the split are one change. At 16384 without the split a concurrent long burst left 0.04-0.65 GB free per GPU, the condition that preceded the gpu02 crash; with it, 9.3-9.5 GB. The validator now rejects --chunked-prefill-size 16384 on any replica that does not also set SGLANG_DSA_INDEXER_QSPLIT=1, and rejects the variable on any image that does not carry the patch. Both gates were verified by mutation, including the case where the expected spec itself is edited -- the first placement of the chunk gate sat in validate_w4afp8_base, where it could never fire. Lab evidence (gpu31, candidate vs #294): TTFT 0.61-0.78x, TPOT 0.53-0.85x, minimum free device memory 3.7 -> 9.3 GB, quality at the noise floor. The all-gather's cost under CC/PPCIe inside a CVM is still unmeasured; gpu31 is bare metal with CC off, so this canary is the first measurement of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review found the header still claimed "8192-token prefill chunks", "Both pin fde25985...", "flags differ only in --dist-init-addr and r2's canary pdi 2" and "(16384 fell to 0.04 GiB and is rejected)" -- all false once r2 moved to the v2 image at chunk 16384. That header is the rollback and incident reference for a file operators are told not to hand-edit, so a stale one is worse than none: it would have pointed an on-call at the wrong image during a rollback. It now states plainly that the replicas are not identical and lists each one's image, chunk, interval and provenance, plus why the chunk and the split are one change. Also enforce the occurrence count in resolve_engine_image_labels instead of unpacking it and ignoring it. find() silently takes the first hit, so if an "instance:N" marker ever stopped being replica-unique the nearest-placeholder binding could attach the wrong replica's image label. It now fails loudly, matching the exact-count discipline replace_exact uses elsewhere in the script. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lloydmak99
left a comment
There was a problem hiding this comment.
Canary changes are confined to r2's block — image bumped to v2 (8ff1a487b98a), chunk 8192→16384, its own full environment: block, and r2-specific telemetry labels; r1 and the shared anchor are untouched. r2 correctly inherits all of r1's 18 env vars plus SGLANG_DSA_INDEXER_QSPLIT=1, engine_image placeholders resolve to the right per-replica shas across all three sinks, and image shas are consistent across the compose file, generator, and validator. Looks good to merge.
Optional follow-up (non-blocking):
- Two compiled-bytecode artifacts were accidentally committed under
scripts/__pycache__/(prepare_glm53_w4afp8_long_context.cpython-314.pyc,test_glm53_w4afp8_long_context.cpython-314.pyc); no runtime impact, just clutter —git rm --cachedthem and add__pycache__//*.pycto.gitignore.
Checks: generator --check passed (target not stale); PyYAML parse + structural inspection confirmed r2's image/chunk/env, placeholder resolution, and r1/anchor identity; 4/4 Python GeneratedFileTest cases passed incl. committed-file byte-match; git diff origin/main confirmed changes confined to r2; both validate CI runs green (Ruby validator + Ruby-dependent tests run there, as Ruby/Docker were unavailable locally).
Brings in #299, #300, #302 and #307. Conflicts in the W4AFP8 long-context generator, validator and test are resolved by taking main's per-replica logic (both replicas on the v2 QSPLIT image at chunk 8192, r1 pdi 1 / r2 pdi 2, r2's own environment list derived from the anchor) and re-applying this branch's tracing additions on top: --enable-trace and the OTLP endpoint on both replicas, SGLANG_TRACE_ASYNC=1 / SGLANG_TRACE_LEVEL=0 in the anchor environment (and therefore in r2's derived list), the -trace-async-armed variant suffix on both variants, and the glm53-trace-control job. The compose file is regenerated, not hand-merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third and final PR of the long-tier chain: #299 (pdi 2) and #300 (the opt-in split patch) are merged, the
glm53-hicache-w4afp8-v2image is published, and this pins it on r2 only.What changes, and what deliberately does not
fde25985aea3(#294) — unchanged8ff1a487b98a(v2, from #300)SGLANG_DSA_INDEXER_QSPLITr1's service definition and the shared engine anchor are byte-identical to
origin/main; the only other changed path is r2's own scrape job. A targetedcompose uptherefore recreates r2 alone, and r1 keeps serving throughout.The structural bit worth reviewing
r2 now needs its own
imageandenvironment. A YAML merge key replaces a list wholesale rather than extending it, so r2 cannot inherit the anchor's environment and add one variable. The generator derives r2's block from the anchor at generation time instead of duplicating it as a literal, so the two cannot drift apart.The chunk and the split are one change
At 16384 without the split, a concurrent long burst left 0.04–0.65 GB free per GPU — the condition that preceded the gpu02 crash. With it, 9.3–9.5 GB, clearing the ≥3 GB gate.
The validator now enforces the pairing in both directions:
--chunked-prefill-size 16384on a replica withoutSGLANG_DSA_INDEXER_QSPLIT=1→ rejectedSGLANG_DSA_INDEXER_QSPLIT=1on an image that does not carry the patch → rejectedBoth were verified by mutation, including the case where the expected spec itself is edited so every equality check passes and only the gate can catch it. That test mattered: my first placement of the chunk gate sat in
validate_w4afp8_base, guarding the c4096 file where it could never fire. It was dead code until the isolation test caught it.Evidence (gpu31, candidate vs #294)
Minimum free device memory 3.7 → 9.3 GB. Quality at the noise floor: GSM8K 97.41 vs 97.37, MMLU 87.66 vs 87.66, passkey to ~450K 12/12 both, teacher-forced agreement matching control-vs-control. 0 restarts, 0 OOMs, 0 fatal log lines.
Write-up: Notion
Validation
prepare_glm53_w4afp8_long_context.py --checkpasses.validate_glm53_prod_config.rbpasses on all five prod files.test_glm53_w4afp8_long_context.py: 12 tests, 2 added. The 2 failures under local Ruby 2.6 are the pre-existingfilter_mapgap — identical onorigin/main(verified), and CI runs 3.3.Untested
The all-gather's cost under CC/PPCIe inside a CVM. gpu31 is bare metal with CC off, so this canary is the first measurement of it. If r2's TTFT regresses against r1 rather than improving, suspect the all-gather and roll back.
Deployment and rollback
r2-only
compose/downthencompose/up, with adry_runthat must plan exactlymodel-sg-glm53-w4afp8-tp4-r2. Rollback is a single step documented in the rollout doc: redeploy the preceding tag for r2 alone, which restores its image, chunk and pdi together while r1 keeps serving.🤖 Generated with Claude Code