Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions deploy/docker/Dockerfile.sglang
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ COPY infera ./infera
RUN pip install --no-cache-dir ".[sglang]" "setuptools>=83.0.0"

# ---- GLM-5.2 DSA patch set (PD + DP-attention on gfx950) --------------------
# WHAT: four patches that together make PD + DP-attention work for GLM-5.2, with
# WHAT: five patches that together make PD + DP-attention work for GLM-5.2, with
# EAGLE MTP on top; without them it crashes on the first batch or deadlocks the DP
# group. patches/sglang_dsa/README.md carries the per-patch rationale.
#
# Three of the four are context diffs at --fuzz=0 against the pinned base, so a
# base bump FAILS here instead of mis-applying. The fourth is an anchor script
# Four of the five are context diffs at --fuzz=0 against the pinned base, so a
# base bump FAILS here instead of mis-applying. The fifth is an anchor script
# instead: Dockerfile.sglang.gfx942 needs the same fix on a v0.5.16 base where
# those diffs cannot apply, and one source of truth beats two.
#
Expand Down
3 changes: 2 additions & 1 deletion deploy/docker/patch.upstream.status.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Column meanings:

## sglang — `patches/sglang_dsa/` (baked by `Dockerfile.sglang` and `Dockerfile.sglang.gfx942`, `APPLY_SGLANG_DSA_PATCHES=1`)

Patch 01 is an anchor script and is baked by both images. The other three are
Patch 01 is an anchor script and is baked by both images. The other four are
`--fuzz=0` diffs cut against the mi35x base (v0.5.17) and are not applied by the
gfx942 image, which substitutes `dsa_page_table_rows` and `draft_cuda_graph_dp_vote`
at runtime with `--json-model-override-args '{"index_share_for_mtp_iteration":false}'`
Expand All @@ -36,6 +36,7 @@ at runtime with `--json-model-override-args '{"index_share_for_mtp_iteration":fa
| `sglang_dsa/dsa_dp_sync.diff` | `seq_lens.max().item()` is a host sync on a branch only *some* DP ranks take → DP collectives desync → deadlock | none found | [sglang#33973](https://github.com/sgl-project/sglang/pull/33973) — this file **is** that PR's diff, so it drops by deletion when it merges | **yes** (`dorado269`) | OPEN |
| `sglang_dsa/dsa_page_table_rows.diff` | page table has one row per **request**, top-k one per **token** under MTP → `assert page_table.shape[0] == topk_indices.shape[0]` | none found | [sglang#32209](https://github.com/sgl-project/sglang/pull/32209) solves the same row mismatch by **trimming q/top-k**; porting that half here fails at conc=32 and is unresolved | no (`HZY-Wade`) | OPEN |
| `sglang_dsa/draft_cuda_graph_dp_vote.diff` | the draft graph/eager choice is per-rank, so under PD + DP-attention + MTP a DP group splits across the two paths and deadlocks on the first routed request | [sglang#32527](https://github.com/sgl-project/sglang/issues/32527) | [sglang#32209](https://github.com/sgl-project/sglang/pull/32209) carries the same vote at the same site; we take only that half | no (`HZY-Wade`) | OPEN |
| `sglang_dsa/dsa_indexer_idle_metadata.diff` | EAGLE eager IDLE selects a per-step DSA backend without `forward_metadata`; the indexer reads it before the existing empty-batch guard → scheduler exits during warmup | none found for this exact traceback | [sglang#32209](https://github.com/sgl-project/sglang/pull/32209) carries an early CUDA IDLE short-circuit; [#31683](https://github.com/sgl-project/sglang/pull/31683) widens a later guard but is too late for the v0.5.17 ordering | no | OPEN at last recorded checks; re-check before release |

Background, already present in the base and **not** patched by us:
[sglang#30378](https://github.com/sgl-project/sglang/pull/30378) /
Expand Down
43 changes: 38 additions & 5 deletions deploy/docker/patches/sglang_dsa/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# sglang DSA patches

Patches that make **PD disaggregation + DP-attention** work for GLM-5.2, and,
with the fourth, **EAGLE MTP** on top. Without them the combination crashes on
Patches that make **PD disaggregation + DP-attention** work for GLM-5.2, with
**EAGLE MTP** on top. Without them the combination crashes on
the first batch or deadlocks the whole DP group under concurrency.

They apply to the sglang tree bundled in the ROCm engine images (an editable
Expand All @@ -10,7 +10,7 @@ same arm of the set — see [Applying](#applying):

| image | base | arm |
|---|---|---|
| `Dockerfile.sglang` (gfx950 / MI355X) | `lmsysorg/sglang:v0.5.17-rocm720-mi35x` | 01 + dp_sync + page_table_rows + draft_dp_vote |
| `Dockerfile.sglang` (gfx950 / MI355X) | `lmsysorg/sglang:v0.5.17-rocm720-mi35x` | 01 + idle_metadata + dp_sync + page_table_rows + draft_dp_vote |
| `Dockerfile.sglang.gfx942` (gfx942 / MI325X) | `lmsysorg/sglang:v0.5.16-rocm720-mi30x` | **01 only**, plus a mandatory runtime flag |

| # | patch | fixes |
Expand All @@ -19,6 +19,7 @@ same arm of the set — see [Applying](#applying):
| 02a | `dsa_dp_sync.diff` | a host sync on a branch only *some* DP ranks take → collectives desync → deadlock. This file is upstream PR sglang#33973 verbatim |
| 02b | `dsa_page_table_rows.diff` | page table has one row per **request**, top-k one per **token** under MTP → `assert page_table.shape[0] == topk_indices.shape[0]` |
| 04 | `draft_cuda_graph_dp_vote.diff` | the draft graph/eager choice is made **per rank** from rank-dependent inputs and diverges on the PD decode leg → deadlock |
| 05 | `dsa_indexer_idle_metadata.diff` | EAGLE eager IDLE reaches the per-step DSA indexer before that backend has `forward_metadata` → scheduler exits during warmup |

Patch 01 is a **script** and the rest are **context diffs**, and that is the
whole reason the two images can differ: the diffs are `--fuzz=0` against one
Expand Down Expand Up @@ -50,7 +51,7 @@ which takes `DSA_PATCH_SET`:

| arm | used by | applies | verification |
|---|---|---|---|
| `full` (default) | `Dockerfile.sglang` | 01 + 02a + 02b + 04 | 7 bytecode markers |
| `full` (default) | `Dockerfile.sglang` | 01 + 02a + 02b + 04 + 05 | 8 bytecode markers |
| `indexer` | `Dockerfile.sglang.gfx942` | 01 | the two patch-01 markers |

Set `APPLY_SGLANG_DSA_PATCHES=0` for a stock engine to A/B against.
Expand All @@ -65,7 +66,8 @@ By hand, against the pinned base:
```bash
cd /sgl-workspace/sglang
python3 patch_dsa_indexer_hip_dp_padded_rows.py
for d in dsa_dp_sync.diff dsa_page_table_rows.diff draft_cuda_graph_dp_vote.diff; do
for d in dsa_indexer_idle_metadata.diff dsa_dp_sync.diff \
dsa_page_table_rows.diff draft_cuda_graph_dp_vote.diff; do
patch -p1 --fuzz=0 < "$d"
done
```
Expand Down Expand Up @@ -219,6 +221,37 @@ extracts the paged-MQA backend, restructuring the `is_aiter()` dispatch this
patch hangs off. Either landing in a future base drifts the anchors, which fails
the build rather than mis-applying — but re-cut patch 01 when bumping past them.

### Patch 05: EAGLE eager IDLE has no per-step DSA metadata

On the EAGLE eager path, an IDLE DP rank skips
`draft_attn_backend.init_forward_metadata()`, then `draft_forward()` selects a
per-step backend through `ForwardContext`. The generic eager IDLE runner updates
`model_runner.attn_backend`; that is not the per-step backend the DSA indexer
reads. If IDLE is that backend's first use, even the attribute is absent:

```text
AttributeError: 'DeepseekSparseAttnBackend' object has no attribute
'forward_metadata'
```

The existing empty-`seq_lens` guard returns an all-invalid top-k tensor, but on
v0.5.17 it sits hundreds of lines after `get_indexer_metadata()` and therefore
cannot protect this access. Patch 05 adds the equivalent IDLE return immediately
after `x_meta` is available and before metadata lookup. It retains
`_broadcast_indexer_topk_from_rank0`: IDLE ranks still need the same DP
synchronization primitive as active ranks.

This is the CUDA IDLE short-circuit strategy carried in upstream
[#32209](https://github.com/sgl-project/sglang/pull/32209), adapted to the
v0.5.17 ordering. [#31683](https://github.com/sgl-project/sglang/pull/31683)
widens the later empty-batch guard, which is insufficient by itself on this
base because metadata has already been read.

**Validation status.** The failure was reproduced on MI355X TP8/DP8 with PD,
DP-attention, EAGLE(3,1,4), and decode CUDA graphs disabled. The patch has
build-time apply/bytecode coverage; repeated two-node cold-start and concurrency
validation is still pending.

### Prerequisite

There is no longer one. GLM-5.2 MTP used to need a backport of sglang #30265 to
Expand Down
54 changes: 54 additions & 0 deletions deploy/docker/patches/sglang_dsa/dsa_indexer_idle_metadata.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
PATCH 05 -- DSA indexer: short-circuit EAGLE IDLE before metadata lookup
================================================================================
WHAT An EAGLE eager IDLE forward selects a per-step DSA backend through
ForwardContext, but EagleDraftWorker skips draft_attn_backend metadata
initialization for IDLE. DSAIndexer.forward_cuda() asks that backend for
indexer metadata before reaching its existing empty-seq-lens guard, so a
backend whose first use is IDLE raises:

AttributeError: 'DeepseekSparseAttnBackend' object has no attribute
'forward_metadata'

Return the same all-invalid top-k tensor as the existing empty-seq-lens
branch before any metadata access. Keep the rank-0 broadcast: IDLE ranks
still have to issue the same DP synchronization primitive as active ranks.

SCOPE v0.5.17 only. This is a --fuzz=0 context diff for the pinned MI355X base.
The gfx942 v0.5.16 arm is unchanged and has not reproduced this failure.

UPSTREAM
#31683 widens a later empty-seq-lens guard to cover IDLE.
#32209 carries an early CUDA IDLE short-circuit alongside its EAGLE/DP
fixes. This cut is placed before get_indexer_metadata(), which is required
on v0.5.17 because that lookup precedes the existing guard.

EVIDENCE
Reproduced on 2026-08-24 with GLM-5.2-MXFP4, MI355X, TP8/DP8,
DP-attention, EAGLE(3,1,4), PD decode, and decode CUDA graphs disabled.
Multiple scheduler ranks failed during disaggregation warmup at
dsa_indexer.py:get_indexer_metadata before the server reported ready.

diff --git a/python/sglang/srt/layers/attention/dsa/dsa_indexer.py b/python/sglang/srt/layers/attention/dsa/dsa_indexer.py
--- a/python/sglang/srt/layers/attention/dsa/dsa_indexer.py
+++ b/python/sglang/srt/layers/attention/dsa/dsa_indexer.py
@@ -1558,6 +1558,20 @@
# a tuple like (x_fp8, x_scale[, y]). Use `x_meta` for shape/device queries.
x_meta = x[0] if isinstance(x, tuple) else x

+ # EAGLE IDLE forwards skip per-step draft-backend metadata initialization.
+ # Return the same sentinel top-k as the empty-seq-lens branch below before
+ # get_indexer_metadata dereferences a forward_metadata that does not exist.
+ _glm52_idle_indexer = forward_batch.forward_mode.is_idle()
+ if _glm52_idle_indexer:
+ topk_result = torch.full(
+ (x_meta.shape[0], self.index_topk),
+ -1,
+ dtype=torch.int,
+ device=x_meta.device,
+ )
+ topk_result = _broadcast_indexer_topk_from_rank0(topk_result)
+ return maybe_capture_indexer_topk(layer_id, topk_result)
+
in_piecewise_or_breakable_cuda_graph = (
_is_in_piecewise_or_breakable_cuda_graph()
)
12 changes: 9 additions & 3 deletions deploy/docker/scripts/apply_sglang_dsa_patches.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
# TWO ARMS, because the set is not uniformly portable across our engine bases:
#
# DSA_PATCH_SET=full (default; Dockerfile.sglang, mi35x / v0.5.17)
# patch 01 + dsa_dp_sync + dsa_page_table_rows + draft_cuda_graph_dp_vote.
# The three diffs are `--fuzz=0` against that one release, so this arm
# patch 01 + dsa_indexer_idle_metadata + dsa_dp_sync +
# dsa_page_table_rows + draft_cuda_graph_dp_vote.
# The four diffs are `--fuzz=0` against that one release, so this arm
# only works there.
# DSA_PATCH_SET=indexer (Dockerfile.sglang.gfx942, mi30x / v0.5.16)
# patch 01 only. 02b is substituted at RUNTIME by
Expand All @@ -32,6 +33,10 @@
# with a convincing stack. Compare the host amdgpu version against the
# container's /opt/rocm/.info/version before attributing one to sglang -- see
# the driver precondition in patches/sglang_dsa/README.md.
# dsa_dp_sync has no substitute and is not carried there: it has not been
# re-cut or measured on v0.5.16.
# dsa_indexer_idle_metadata is likewise not carried: the failure has not
# been reproduced or evaluated on that base.
#
# WHY BYTECODE VERIFICATION. Python caches compiled modules in __pycache__ keyed
# on the source mtime. A patch script that restores a backup with shutil.copy2
Expand Down Expand Up @@ -64,7 +69,7 @@ PATCH01=patch_dsa_indexer_hip_dp_padded_rows.py
# from each other and from patch 01. dsa_dp_sync.diff is upstream PR sglang#33973
# verbatim, so it drops by deleting the file the day that merges.
case "$DSA_PATCH_SET" in
full) PATCHES=(dsa_dp_sync.diff dsa_page_table_rows.diff draft_cuda_graph_dp_vote.diff) ;;
full) PATCHES=(dsa_indexer_idle_metadata.diff dsa_dp_sync.diff dsa_page_table_rows.diff draft_cuda_graph_dp_vote.diff) ;;
indexer) PATCHES=() ;;
esac

Expand Down Expand Up @@ -93,6 +98,7 @@ if [ "$DSA_PATCH_SET" = "full" ]; then
# missing makes the patch inert rather than absent, which is the failure mode
# that matters: an inert 04 looks exactly like a working one until load.
MARKERS+=(
"dsa_indexer.py:_glm52_idle_indexer"
"decode.py:force_disable_draft_cuda_graph"
"dp_attn.py:can_run_draft_cuda_graph"
"eagle_draft_cuda_graph_runner.py:can_run_dp_draft_cuda_graph"
Expand Down
Loading
Loading