Skip to content

[Bugfix] Skip the aligned-scale round-trip that frees the masked-path scale (SM90 deepep capture crash) - #32097

Closed
Jialin wants to merge 1 commit into
sgl-project:mainfrom
Jialin:jialino/fix-deepep-masked-scale-uaf
Closed

Jialin wants to merge 1 commit into
sgl-project:mainfrom
Jialin:jialino/fix-deepep-masked-scale-uaf

Conversation

@Jialin

@Jialin Jialin commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Since #30924 the masked plain-silu path emits the down-gemm activation scale already MN-major TMA-aligned. On SM90 (DEEPGEMM_NEED_TMA_ALIGNED_SCALES) the runner still rebinds it through deep_gemm's get_mn_major_tma_aligned_tensor, whose short-circuit branch returns a NON-owning alias across the tvm-ffi boundary; the rebind then drops the scale's only owner, freeing its storage mid-forward. The next allocation reuses the block and the down gemm converts a dangling pointer, failing decode CUDA-graph capture with RuntimeError: The specified pointer resides on host memory and is not registered with any CUDA device (base-c-test-deepep-4-gpu-h100, 100% deterministic since #30924; B200/H200 take the UE8M0 branch and never rebind). On allocators that keep the freed VA mapped, the same bug silently computes the down gemm with garbage scales instead of crashing.
Evidence for the diagnosis:

Modifications

  • python/sglang/srt/layers/moe/moe_runner/deep_gemm.py: add _is_mn_major_tma_aligned, a python-side mirror of deep_gemm's short-circuit condition (stride(-2)==1, stride(-1)==ceil_align(mn,4), batch stride consistent), and guard the SM90 down-gemm rebind with it so an already-aligned scale never round-trips through the aliasing FFI path. Row-major scales keep taking deep_gemm's owned copy path, so behavior there is unchanged.
  • test/registered/unit/layers/moe/test_deep_gemm_masked_scale_layout.py: CPU-only unit tests pinning the mirror to the layout create_per_token_group_quant_fp8_output_scale actually emits (aligned and non-multiple-of-4 token counts), plus the row-major negative and a batch-stride-mismatch negative, so either side drifting apart turns the suite red.
    The root defect (tvm-ffi short-circuit returns are non-owning) lives in sgl-project/DeepGEMM and affects every caller of get_mn_major_tma_aligned_tensor / transform_sf_into_required_layout; this PR is the caller-side fix that unblocks CI, and an upstream ownership fix is being filed separately.

Accuracy Tests

No numeric change: the guard only skips a transform that would have returned the same layout (deep_gemm's own short-circuit), and it also eliminates the silent-garbage-scale failure mode on allocators that keep the freed VA mapped. Validation is test/registered/ep/test_deepep_small.py on the base-c-test-deepep-4-gpu-h100 job, which fails deterministically on current main and passes with this change; the new CPU unit tests pass locally.

Speed Tests and Profiling

Neutral to slightly positive: removes one host-side FFI round-trip per masked down-gemm on SM90. No kernel changes.

Checklist

Review and Merge Process

  1. Ping Merge Oncalls to start the process. See the PR Merge Process.
  2. Get approvals from CODEOWNERS and other reviewers.
  3. Trigger CI tests with comments or contact authorized users to do so.
    • Common commands include /tag-and-rerun-ci, /tag-run-ci-label, /rerun-failed-ci
  4. After green CI and required approvals, ask Merge Oncalls or people with Write permission to merge the PR.

CI States

Latest PR Test (Base): ❌ Run #29940954181
Latest PR Test (Extra): ❌ Run #29940953575

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

…-trip (SM90 deepep capture crash)

Since sgl-project#30924 the masked plain-silu path emits the down-gemm activation scale
already MN-major TMA-aligned. On SM90 (DEEPGEMM_NEED_TMA_ALIGNED_SCALES) the
runner rebinds it through deep_gemm's get_mn_major_tma_aligned_tensor, whose
short-circuit branch returns a NON-owning alias across the tvm-ffi boundary
(entry-time torch::from_blob borrow, re-exported as the result); the rebind
then drops the scale's only owner, freeing its storage mid-forward. The next
allocation reuses the block and the down gemm converts a dangling pointer,
failing decode CUDA-graph capture with "the specified pointer resides on host
memory" (base-c-test-deepep-4-gpu-h100, 100% deterministic since sgl-project#30924;
B200/H200 take the UE8M0 branch and never rebind).

Route both masked-gemm scale rebinds through a keep-owner wrapper: call the
transform, and when it returns an alias of the input (same data_ptr while both
are alive), keep the owning original instead of the alias. This detects the
aliasing outcome rather than mirroring deep_gemm's alignment condition, so no
deep_gemm layout logic is duplicated in sglang and wheel-side condition changes
cannot silently reintroduce the use-after-free. Row-major scales keep taking
the owned copy path unchanged.
@Jialin
Jialin force-pushed the jialino/fix-deepep-masked-scale-uaf branch from b6d084a to 8b6aefb Compare July 22, 2026 17:06
@Jialin
Jialin marked this pull request as ready for review July 22, 2026 17:07
@Jialin

Jialin commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

/tag-run-ci-label

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@Jialin

Jialin commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

/tag-run-ci-label

@Jialin

Jialin commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

/tag-and-rerun-ci

@hnyls2002

Copy link
Copy Markdown
Collaborator

/rerun-test test_deepep_small.py

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Results for /rerun-test test_deepep_small.py:

🚀 4-gpu-h100 (1 test): ❌ View workflow run

cd test/ && python3 registered/ep/test_deepep_small.py

@hnyls2002

Copy link
Copy Markdown
Collaborator

/rerun-test registered/ep/test_deepep_small.py::TestTP.test_gsm8k registered/ep/test_deepep_small.py::TestTBOWithTPAttnAndDenseDP.test_gsm8k

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Results for /rerun-test registered/ep/test_deepep_small.py::TestTP.test_gsm8k registered/ep/test_deepep_small.py::TestTBOWithTPAttnAndDenseDP.test_gsm8k:

🚀 4-gpu-h100 (2 tests): ❌ View workflow run

cd test/ && python3 registered/ep/test_deepep_small.py TestTP.test_gsm8k
cd test/ && python3 registered/ep/test_deepep_small.py TestTBOWithTPAttnAndDenseDP.test_gsm8k

@hnyls2002

Copy link
Copy Markdown
Collaborator

Ran a few discriminating tests on this PR (results: full file, class-level):

  • The ownership fix works: the capture-time "pointer resides on host memory" crash is gone and the decode graph captures fine.
  • TestTBOWithTPAttn still fails, but with a different, second defect: NaN detected! sampler: next_token_logits on the very first 7-token prefill (eager, deepep normal / contiguous grouped GEMM — the masked path this PR patches is not involved).
  • Matrix: TestTP (TP attn, no TBO) passes; all dp-attn + TBO classes pass; TestTBOWithTPAttn and TestTBOWithTPAttnAndDenseDP both hit the NaN. So the failing ingredient is TBO x attn-TP, regardless of dense sharding.
  • One lead: with TBO two-chunk (3+4 tokens) scattered across attn-TP=4, the per-rank quant/dispatch m is 0 or 1 — a shape no passing config produces (dp+TBO sees m=3/4, TestTP m=7). Both the dispatch-side and down-input quant on this path switched from the v2 kernel to the unified flat kernel in [JIT] Trait-driven per_token_group_quant: unify the quant kernel family (flat + masked) #30924.

So this PR looks correct but not sufficient to turn base-c-test-deepep-4-gpu-h100 green; the NaN needs a separate fix.

@Jialin

Jialin commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

Ran a few discriminating tests on this PR (results: full file, class-level):

  • The ownership fix works: the capture-time "pointer resides on host memory" crash is gone and the decode graph captures fine.
  • TestTBOWithTPAttn still fails, but with a different, second defect: NaN detected! sampler: next_token_logits on the very first 7-token prefill (eager, deepep normal / contiguous grouped GEMM — the masked path this PR patches is not involved).
  • Matrix: TestTP (TP attn, no TBO) passes; all dp-attn + TBO classes pass; TestTBOWithTPAttn and TestTBOWithTPAttnAndDenseDP both hit the NaN. So the failing ingredient is TBO x attn-TP, regardless of dense sharding.
  • One lead: with TBO two-chunk (3+4 tokens) scattered across attn-TP=4, the per-rank quant/dispatch m is 0 or 1 — a shape no passing config produces (dp+TBO sees m=3/4, TestTP m=7). Both the dispatch-side and down-input quant on this path switched from the v2 kernel to the unified flat kernel in [JIT] Trait-driven per_token_group_quant: unify the quant kernel family (flat + masked) #30924.

So this PR looks correct but not sufficient to turn base-c-test-deepep-4-gpu-h100 green; the NaN needs a separate fix.

@hnyls2002 Thanks for verifying. Seems multiple tests are failing, do you feel reverting #30924 might be a better move?

@hnyls2002

Copy link
Copy Markdown
Collaborator

@Jialin Yes, but too many conflicts...

@Jialin

Jialin commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

Will close the PR for now, and expect others would follow up with a proper fix on it. IIUC, currently the unittest is skipped.

@Jialin Jialin closed this Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants