Skip to content

Canary the DSA indexer split and chunk 16384 on the long-context r2 - #302

Merged
pranavraja99 merged 2 commits into
mainfrom
pranavraja99/glm53-long-qsplit-c16384
Sep 25, 2026
Merged

pranavraja99 merged 2 commits into
mainfrom
pranavraja99/glm53-long-qsplit-c16384

Conversation

@pranavraja99

Copy link
Copy Markdown
Contributor

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-v2 image is published, and this pins it on r2 only.

What changes, and what deliberately does not

r1 (control) r2 (canary)
image fde25985aea3 (#294) — unchanged 8ff1a487b98a (v2, from #300)
chunk 8192 — unchanged 16384
SGLANG_DSA_INDEXER_QSPLIT unset — unchanged 1
pdi 1 — unchanged 2 (from #299)

r1'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 targeted compose up therefore recreates r2 alone, and r1 keeps serving throughout.

The structural bit worth reviewing

r2 now needs its own image and environment. 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 16384 on a replica without SGLANG_DSA_INDEXER_QSPLIT=1 → rejected
  • SGLANG_DSA_INDEXER_QSPLIT=1 on an image that does not carry the patch → rejected

Both 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)

Workload TTFT ratio TPOT ratio
Burst, 8 × ~720K 0.76 0.72–0.76
Burst + short-request hammer 0.77–0.78 0.78–0.85
long-a2 0.61–0.69 0.53–0.56

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 --check passes.
  • validate_glm53_prod_config.rb passes 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-existing filter_map gap — identical on origin/main (verified), and CI runs 3.3.
  • Structural diff proves r1 and the anchor are unchanged.

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/down then compose/up, with a dry_run that must plan exactly model-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

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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 OpenCodeReview found 2 issue(s) in this PR.

  • ✅ 2 posted as inline comment(s)
  • 📝 0 posted as summary

⚠️ 1 warning(s) occurred during review.

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 lloydmak99 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 --cached them and add __pycache__/ / *.pyc to .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).

@pranavraja99
pranavraja99 merged commit 4c5d986 into main Sep 25, 2026
2 checks passed
henrypark133 added a commit that referenced this pull request Sep 25, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants