Skip to content

[sglang-miles] Colocated RL for hybrid-state models: flush order and bf16 MoE weight-update layout - #40821

Merged
Zhichenzzz merged 10 commits into
sgl-project:sglang-milesfrom
Zhichenzzz:zhichen/sglang-miles-glm53-flash-rl
Sep 25, 2026
Merged

Zhichenzzz merged 10 commits into
sgl-project:sglang-milesfrom
Zhichenzzz:zhichen/sglang-miles-glm53-flash-rl

Conversation

@Zhichenzzz

@Zhichenzzz Zhichenzzz commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes needed for colocated RL on hybrid-state models (Qwen3.8-Flash-Next, GLM-5.3-Flash) on current sglang-miles. Supersedes #40815, which is folded in here as the first commit.

Motivation

  1. Hybrid state pools fault with CUDA error 700 at memory release. release_memory_occupation paused (unmapped) the KV cache memory-saver region and only then called flush_cache(). The hybrid pools cleared there (mamba states, qwen4_exp PLE ShortConvPool / NgramPool, KDA state) live inside that region and their clear() writes device memory, so the write hits unmapped memory. With CUDA_LAUNCH_BLOCKING=1 the fault lands on ShortConvPool.clear() via release_memory_occupation -> flush_cache -> HybridReqToTokenPool.clear. The trainer's later /flush_cache while the engine is offloaded faults the same way.
  2. bf16 MoE weight update crashes on plain flashinfer_trtllm. The TRT-LLM bf16 postprocess repacks w13/w2 into block layout for every TRT-LLM MoE runner variant, but maybe_restore_flashinfer_trtllm_bf16_weight_shape_for_load only restored the canonical shape for flashinfer_trtllm_routed. On GB300 the auto backend resolves to plain flashinfer_trtllm, so the first weight sync failed with size of tensor a (64) must match tensor b (4096) at dimension 2.

Modifications

  1. weight_updater.py: flush before tagging the KV region as offloaded and pausing it (the tag is what makes the later flush a no-op). Scheduler.flush_cache: return success without clearing while the KV region is released (the pools were flushed right before the pause).
  2. unquant.py: gate the shape restore on self.use_flashinfer_trtllm_moe, the same flag that gates the repack.
  3. qsa/mqa.py: the prefill indexer walks its key tiles with a serial loop. tilelang 0.1.14 only pipelines constant trip counts and rejected T.ceildiv(end_max - start_min, block_n) at codegen (Downcast from tirx.Sub to ir.IntImm failed), which broke every Qwen4Exp prefill on images built with that release (the miles CI image). Logits are bitwise identical on 0.1.9; an 8k x 8k prefill costs about 10% more per QSA layer on GB300.

Validation

Full-model colocated RL e2e, 8 nodes x 4 GB300, latest radixark/miles:dev, --colocate, R3 routing replay, CUDA graph on:

  • Qwen3.8-Flash-Next RL training support radixark/miles#2777 (Qwen3.8-Flash-Next): without fix 1, all four engines die with CUDA error 700 at the first memory release. With it: 5 rollouts / 5 train steps, weight-update equality check passes, train_rollout_logprob_abs_diff 0.0083 to 0.0131, raw reward 0.47 to 0.75.
  • GLM 5.3 Flash RL Support radixark/miles#2786 (GLM-5.3-Flash bf16): 4 rollouts / 3 train steps, train_rollout_logprob_abs_diff 0.0085 / 0.0098 / 0.0087, raw reward 0.50 to 0.72; routed experts were captured with the triton runner, which the miles launcher now pins for routing replay (the resolver guard was dropped from this PR).

CI States

Latest PR Test (Base): ❌ Run #36084620812
Latest PR Test (Extra): ❌ Run #36084620581
Latest PR Test (AMD ROCm 10): ❌ Run #36084620803

… KV cache

release_memory_occupation paused (unmapped) the KV cache region and only then
called flush_cache(). For hybrid models the pools cleared there live inside that
region and clear() writes device memory: req_generation, mamba states, and
qwen4_exp's PLE short-conv / n-gram states (ShortConvPool.conv_state.zero_(),
NgramPool.context.fill_()). The write hit unmapped memory and raised a sticky
CUDA error 700 at the next sync. Plain-KV models never noticed because their
clear() is host-side only.

- release_memory_occupation: flush before pausing the KV region.
- Scheduler.flush_cache: skip while the KV region is released. The trainer side
  sends /flush_cache again while the engine is offloaded; the pools were already
  flushed right before the pause, so there is nothing left to clear.
…infer_trtllm weight updates

The TRT-LLM bf16 postprocess repacks w13/w2 into block layout
([E, K/128, M, 64] viewed as bf16) for every TRT-LLM MoE runner variant,
but the pre-copy shape restore in
maybe_restore_flashinfer_trtllm_bf16_weight_shape_for_load only ran for
flashinfer_trtllm_routed. On GB300 the auto backend resolves to plain
flashinfer_trtllm, so the first RL weight sync copied canonical
[*, hidden] expert tensors into blocked params and crashed with
'size of tensor a (64) must match tensor b (4096) at dimension 2'.
Guard the restore with self.use_flashinfer_trtllm_moe, the exact flag
that gates the repack.
…E runner backends

On SM100 the DeepSeek-family override (_deepseek_moe_quant_resolution)
resolves moe_runner_backend=auto to flashinfer_trtllm for Glm5Next (and
DSv3/GLM-5) with a2a=none, for fp8 and bf16 checkpoints alike. That
backend returns a BYPASSED TopK output: routing runs inside the fused
kernel and capture_routed_experts_if_allowed never executes, so
--enable-return-routed-experts silently ships the zero-initialized host
buffer. A miles R3 consumer then replays expert-0-x-topk rows and the
training engine's MoE dispatcher dies in alltoall with 'Split sizes
doesn't match total dim 0 size'.

Add a post-process pass in moe_hook.handle_moe_kernel_config, right after
the quant-constraints pass: when
enable_return_routed_experts is set and the resolved runner bypasses
topk-id materialization (flashinfer_trtllm, experimental_sgl_trtllm,
triton_kernel, flashinfer_mxfp4), downgrade to 'auto' for
quantization None/fp8 (auto resolves to StandardTopKOutput-consuming
runners, so select_experts -> capture runs), and raise for
quantizations with no safe fallback.
…ir constraints

The bf16 shape-restore comment narrated how the bug showed up, and the
routed-experts guard carried a nine-line preamble naming its downstream consumer
plus a long docstring restating the pass ordering. Keep the constraint each line
needs; the history is in the fix commits.
@Zhichenzzz Zhichenzzz changed the title [sglang-miles] GLM-5.3-Flash colocated RL: bf16 MoE weight-update layout and R3 routed-experts capture [sglang-miles] Colocated RL for hybrid-state models: flush order, bf16 MoE weight-update layout, R3 routed-experts capture Sep 23, 2026
@yueming-yuan

Copy link
Copy Markdown
Collaborator

[P1] Flush before marking KV memory as offloaded — scheduler.py:5002
release_memory_occupation() adds kv_cache to offload_tags before invoking self.flush_cache(). This new guard therefore skips the pre-pause flush on every KV release. Resume performs no reset either, leaving radix-cache entries and allocator state referencing discarded KV contents. A CPU harness executing the actual methods confirms one cache reset on the base, zero on this PR. Set the offload tag after flushing.

@yueming-yuan

Copy link
Copy Markdown
Collaborator

random q: do you think it will be better to directly assert moe_backend not in bypass_topk, and use assert message to ask the user to set --backend auto? (in case the user doesn't want to silently change the backend)

release_memory_occupation added the tag before calling flush_cache, and
flush_cache returns early once the KV tag is set, so the flush before the
pause never ran: the radix cache and the allocators kept entries that
pointed at KV contents the pause had discarded. Flush first, then tag,
then pause. The unit test drives the real Scheduler.flush_cache and
release/resume paths: one reset on release, none while paused.
…n-routed-experts

Refuse instead of silently switching the runner to auto, so an explicit
--moe-runner-backend is never overridden behind the user's back; the
error names the fix (a topk-materializing runner such as triton).
tilelang 0.1.14 only pipelines loops with a constant trip count and
rejects T.ceildiv(end_max - start_min, block_n) at codegen (Downcast from
tirx.Sub to ir.IntImm failed), which broke every Qwen4Exp prefill on
images built with that release. The serial loop compiles on 0.1.9 and
0.1.14, gives bitwise identical logits, and costs about 10% on an
8k x 8k prefill (1.15 ms -> 1.26 ms per QSA layer on GB300).
The backend choice belongs to the RL launcher (miles pins the triton runner
for routing replay), not to server-arg resolution.
@Zhichenzzz Zhichenzzz changed the title [sglang-miles] Colocated RL for hybrid-state models: flush order, bf16 MoE weight-update layout, R3 routed-experts capture [sglang-miles] Colocated RL for hybrid-state models: flush order and bf16 MoE weight-update layout Sep 25, 2026
@Zhichenzzz
Zhichenzzz merged commit 5710228 into sgl-project:sglang-miles Sep 25, 2026
78 of 87 checks passed
ziang-and pushed a commit to zianglih/sglang that referenced this pull request Oct 2, 2026
…load; restore bf16 MoE layout (sgl-project#40821)

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
nima-ch added a commit to kineuro/kvasir that referenced this pull request Oct 13, 2026
…ge (#40)

* Thinking written as text: the splitter learns <thinking>, runs on every door, and is set per model

The reasoning splitter knew <think> but not <thinking>, ran only on Kvasir's
own door, and was set per backend. A model that wrote its reasoning as a
<thinking> tag after a swap reached the client with its thinking as the answer.

- src/reasoning.ts learns <thinking> (and keeps <think>) and becomes the one
  shared module: a sha256 stamp, scripts/share-reasoning.mjs to stamp and copy
  it to the assistant and the desk, and a test that checks the stamp.
- src/split-doors.ts reads reasoning apart on the pass-through doors: chat
  completions (reasoning_content), Anthropic's messages (a thinking block) and
  responses (a reasoning item), whole and streamed. An answer with no marker
  passes byte for byte. Legacy completions are left as they came.
- inlineReasoning is set per model (a card's model, a card, a held backend's
  model), over the backend's setting, which stays the default.
- test/thinking-doors.test.ts: a request after a swap on each door gives the
  reasoning apart from the answer; a model set to off keeps its text.

* A card whose models sleep: release and resume, and the sleep-and-wake fault never reaches a request

Record 55, B6; spec section 14 and slice C2. A Docker card with
"sleep": "release" swaps warm: the model that leaves the card releases its
GPU memory with its weights kept in the host's memory
(/release_memory_occupation on its SGLang server), and resumes them when it
is asked for again (/resume_memory_occupation).

- A release that does not answer, or whose server exits after it (SGLang
  0.5.20 and 0.5.21 on hybrid models, CUDA error 700), stops the container
  instead; a woken model that does not answer its health in time is started
  again cold. The fault costs a cold swap, never a failed request.
- A sleeping server is never asked for its health, a generation on SGLang.
- Which models sleep is kept beside the database, so a Kvasir started again
  knows them; at start the card keeps an awake model before a sleeping
  default.
- "sleep": "stop" stays the default; a llama.cpp router's card takes only
  stop.
- test/sleep.test.ts: three server images (a working memory saver, one that
  crashes at the release, one that dies after waking), each answering every
  request after a sleep and a wake.
- scripts/check-sleep-image.sh: SGLang's own flush-order test run inside a
  candidate image; it fails on 0.5.21 and passes on 0.5.21 with
  sgl-project/sglang#40821 applied.
nv-hwoo pushed a commit to nv-hwoo/sglang that referenced this pull request Oct 20, 2026
…load; restore bf16 MoE layout (sgl-project#40821)

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
nanjiangwill pushed a commit to modal-projects/sglang that referenced this pull request Oct 21, 2026
…load; restore bf16 MoE layout (sgl-project#40821)

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
(cherry picked from commit d3c23d2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDwTQn4gGFgVhzGXFs4aJx
Rockdu pushed a commit that referenced this pull request Oct 30, 2026
…load; restore bf16 MoE layout (#40821)

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
Rockdu pushed a commit that referenced this pull request Oct 30, 2026
…load; restore bf16 MoE layout (#40821)

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
Rockdu pushed a commit that referenced this pull request Oct 31, 2026
…load; restore bf16 MoE layout (#40821)

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
Rockdu pushed a commit that referenced this pull request Oct 31, 2026
…load; restore bf16 MoE layout (#40821)

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

quant LLM Quantization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants