Skip to content

[cuda_graph] Size dp-local decode graphs by the runner's own gather requirement - #43303

Merged
hanming-lu merged 1 commit into
mainfrom
oss-sync-7f7f18f124
Oct 9, 2026
Merged

hanming-lu merged 1 commit into
mainfrom
oss-sync-7f7f18f124

Conversation

@metamergebot

@metamergebot metamergebot commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

get_batch_sizes_to_capture filters the decode capture buckets by
get_cuda_graph_batch_size_alignment(), which multiplies the alignment by
attn_tp_size whenever the global require_gathered_buffer() is true.
DecodeCudaGraphRunner already exempts a dp-local forward from the MLP gather
through _forward_is_dp_local (the dense draft that drafts attn-TP-locally
with no cross-DP collective), but the bucket filter never saw that exemption.

So under DP attention a dp-local draft runner keeps only buckets whose
bs * rows_per_request is a multiple of attn_tp_size, pads a one- or
two-request local batch up to that bucket, and runs its attention kernels at a
shape many times the real batch. A small user-supplied bucket list (for
example --cuda-graph-bs 1 2 4) fails startup with
AssertionError: capture_bs=[] for the same reason.

Modifications

  • get_cuda_graph_batch_size_alignment and get_batch_sizes_to_capture take
    an optional keyword gathered_buffer_required. Omitting it keeps the global
    require_gathered_buffer() behaviour, so every existing caller is unchanged.
  • DecodeCudaGraphRunner.decode_graph_gather_requirements(model_runner)
    (classmethod) owns the (mlp_tp_gather, attn_tp_gather) pair the runner's
    graphs actually need, including the _forward_is_dp_local exemption.
    __init__ derives its instance predicates from it and passes
    require_gathered_buffer into both get_batch_sizes_to_capture calls.
  • ModelRunner.decode_graph_gather_requirements() delegates to the runner
    class, so max_decode_logits_rows and BaseRunner._widest_decode_rows size
    the shared logits buffer and the reduction workspace with the same alignment
    the runner captures with, instead of re-deriving a global one.
  • get_batch_sizes_to_capture pads num_max_requests with the alignment it
    computed (ceil_align(num_max_requests, mul_base)) rather than the global
    get_cuda_graph_max_batch_size.
  • capture_decode_graph reports bs= from the constructed runner's
    capture_bs in the end-of-capture log instead of re-deriving the list with
    global alignment before the runner exists. All in-tree graph runners
    (DecodeCudaGraphRunner and its NPU/XPU subclasses, CPUGraphRunner)
    expose capture_bs.

Behaviour change: a runner whose _forward_is_dp_local is true now captures
the plain decode bucket list (1, 2, 4, ...) and replays the graph sized to its
local batch. Every other runner keeps the global alignment.

Tests:

  • test_cuda_graph_setup.py::test_draft_startup_preserves_runner_local_capture_sizes:
    a dp-local draft runner with attn_tp_size=16 and buckets [1, 2, 3, 4, 8]
    keeps all five and the end-of-capture log reports them. On current main the
    same case fails at startup with AssertionError: capture_bs=[].
  • test_model_runner_decode_rows.py: the logits-row derivation forwards the
    runner's gather requirement to get_batch_sizes_to_capture.
  • Ran with CUDA hidden:
    pytest test/registered/unit/model_executor/model_runner_components/test_cuda_graph_setup.py test/registered/unit/model_executor/test_model_runner_decode_rows.py
    -> 7 passed; the neighbouring graph-runner unit suites under
    test/registered/unit/model_executor/ -> 186 passed, 10 skipped.
  • pre-commit run --files <changed files> -> passed.

Accuracy Tests

Graph bucket selection only; no kernel or model-forward change. Not run.

Speed Tests and Profiling

Not run here. The effect is confined to dp-local decode runners, whose
attention now runs at the local batch instead of an attn_tp_size-aligned
padded one.

Checklist


CI States

Latest PR Test (Base): ✅ Run #37892518588
Latest PR Test (Extra): ❌ Run #37892518374
Latest PR Test (AMD ROCm 10): ❌ Run #37892518684

…equirement

get_batch_sizes_to_capture filtered the decode buckets by
get_cuda_graph_batch_size_alignment(), which multiplies the alignment
by attn_tp_size whenever the global require_gathered_buffer() is true.
DecodeCudaGraphRunner already exempts a dp-local forward from the MLP
gather through _forward_is_dp_local, but the bucket filter never saw
that exemption: under DP attention a dp-local draft kept only buckets
whose rows were a multiple of attn_tp_size, padded a one- or
two-request local batch up to that bucket, and a small user-supplied
bucket list failed startup with `capture_bs=[]`.

Thread the requirement through instead of re-deriving it globally:

- get_cuda_graph_batch_size_alignment / get_batch_sizes_to_capture
  take an optional keyword gathered_buffer_required; omitted, both
  behave as before.
- DecodeCudaGraphRunner.decode_graph_gather_requirements(model_runner)
  owns the (mlp_tp_gather, attn_tp_gather) pair, including the dp-local
  exemption. __init__ derives its instance predicates from it and passes
  require_gathered_buffer to both bucket derivations.
- ModelRunner.decode_graph_gather_requirements() delegates to the runner
  class so max_decode_logits_rows and BaseRunner._widest_decode_rows
  size the logits buffer and the reduction workspace with the alignment
  the runner captures with.
- get_batch_sizes_to_capture pads num_max_requests with the alignment it
  computed rather than the global get_cuda_graph_max_batch_size.
- capture_decode_graph reports bs= from the constructed runner's
  capture_bs in the end-of-capture log instead of deriving the list with
  global alignment before the runner exists.

A dp-local runner now captures the plain decode bucket list and replays
the graph sized to its local batch; every other runner is unchanged.

Co-authored-by: charlotte12l <charlotte12l@users.noreply.github.com>
@metamergebot metamergebot added the run-ci CI: run the baseline test suite on this PR label Oct 9, 2026
@metamergebot

Copy link
Copy Markdown
Collaborator Author

This port clears our bar on 95d8bd63fdd189a19f5ec2f88eb327a55d6449ce: every base-a and base-b job is green.
base-c and the accelerator suites are reported but not gated, because they run
on scarce hardware and upstream merges through them routinely.

gate 21✓✗3✂5 lint✓ build 5✓✗1✂1 A✓ B✓ C 0/10⏳ other 7✓✂7 · accel ✗amd,npu,xpu

@hanming-lu @merrymercy @charlotte12l — could one of you take a look and merge it if you are happy? Nothing else is waiting on our side.

@hanming-lu
hanming-lu merged commit 66d840a into main Oct 9, 2026
218 of 263 checks passed
@hanming-lu
hanming-lu deleted the oss-sync-7f7f18f124 branch October 9, 2026 22:21
detain added a commit to detain/sglang that referenced this pull request Oct 15, 2026
…ject#4

Conflict resolutions (35 files), preserving stack behavior:

srt/utils/common.py: make_layers — folded our per-layer empty_cache body
  INTO upstream's stage-binding build() (layer_stack refactor sgl-project#43453-43460).
  Upstream removed is_building_neighbour_layer/building_neighbour_layer
  entirely (our earlier sgl-project#42481 pick); only user was qwen4_exp.
srt/models/qwen4_exp.py: import unions (cached_property/partial/TYPE_CHECKING,
  linear_attn_parallel_group); dropped dead is_building_neighbour_layer import;
  prefetch_stream keeps our _graph_prefetch_buffer line with upstream's
  ple_offload_embedding conditional; our PLE host-staging design kept
  (_offloads_ple guard moot under new stage facts).
srt/managers/cache_controller.py: upstream sgl-project#43550 absorbed our sgl-project#39830
  prefetch-degrade fixes; took upstream restructure, deleted auto-merge's
  stale duplicate tails (double-ack/deadlock hazard), kept our rationale
  comments. Fenced start_writing + load_fence_stream survive.
srt/mem_cache/hybrid_cache/hybrid_cache_controller.py: same pattern;
  sidecar degrade comment updated ('upstream adopted unconditionally').
model_executor/pool_configurator.py: UNION draft-cell sizing — our FP4
  _compute_eagle_fp4_draft_cell_size branch + upstream HiSparse
  draft_loc_space_scale else-branch. b05a7c1 None layer_info guard intact.
srt/layers/attention/qsa/sparse_attn.py: kept OUR FP8-descale chunk loop
  (superset; upstream sgl-project#41933 only re-commented); took upstream
  _pack_qsa_prefill_kv + pack_qsa_prefill_kv; our ck interface keeps
  k_scale/v_scale/max_q; __all__ union.
qsa/mqa.py: import functools + min_scored_len kwarg into our triton-first
  _resolve_mqa_backend dispatch; upstream ctas_per_row rework kept.
qsa/qsa_indexer.py: batch-invariance gate moved INTO upstream's shared
  use_fast_topk predicate.
qwen_sparse_attn_backend.py: imports union; gather path UNION — upstream
  pack_qsa_prefill_kv used when scales are unit (QSA stores unscaled K/V),
  our index_select fallback when checkpoint k/v descales != 1.0.
managers/schedule_policy.py: add_chunked_req UNION — upstream slot-reserve
  gate first, then our prefill_chunk_alignment truncation (monotone-safe);
  all 3 alignment sites (1177/1340/1608) intact.
managers/tokenizer_manager.py: import unions (PositionalEmbedsTransport +
  RequestPreprocessor + watermarking fns).
entrypoints/openai/serving_base.py: kept OUR request_preprocessor.run path.
model_executor/runner/eager_runner.py: took upstream TCPCG eager-fallback
  removal (sgl-project#42287); kept our shared cp_extend_forward call shape (our
  runner_utils/cp_extend.py is byte-equivalent to upstream's
  _execute_extend_cp — single source of truth).
runner/flashinfer_autotune.py: import union (can_run_dummy_draft_extend +
  cdiv/ceil_align + upstream get_eager_max_batch_size sgl-project#43789).
arg_groups/model_hook.py: Gemma4 accepted_backends union fa4 + flashinfer
  (NVFP4-KV enablement).
multimodal_gen/runtime/layers/lora/linear.py: KEPT OUR sgl-project#37460 pipeline
  structure; upstream runtime_lora_scale==0 early-return ordering adopted;
  _add_lora_delta/_add_lora_output_offset wired into pipeline; kept
  UPSTREAM's quality-tiered _scale_lora_delta (deleted our simple variant).
  DEBT: upstream RowParallel bias_in_gemm lossless opt not ported (perf-only).
multimodal_gen/runtime/layers/attention/layer.py: _gather_sharded_sequence
  took upstream sgl-project#43793 .contiguous() fix (subsumes our 55aaa02 guard);
  our _forward_with_kv_gather k/v guards kept (separate path, not covered).
configs/pipeline_configs/base.py + configs/sample/minimax_h3.py + envs.py:
  unions (supports_continuous_batching / teacache_params+fields /
  SGLANG_TEACACHE_CALIBRATE).
models/dits/ltx_2.py mask-fill: took upstream (identical to our fix).
ltx_2/denoising.py: took upstream pass_specs (sgl-project#43667) — subsumes our
  legacy one-stage branch; our _ltx2_model_forward_context helpers already
  upstreamed (entry-142 sgl-project#42928 CONFIRMED SUPERSEDED).
ltx_2/decoding_av.py: KEPT OUR split-stage AV decode (independent pool
  scaling) over upstream single-class restructure; adopted upstream
  chunked pinned-memory _postprocess_video; TODO(merge-4): upstream
  EncodeWhileDecoding streaming + rank-0 retain gating not ported.
kernels/ops/diffusion/activation/silu_mul_bitexact.py: our out= column-slice
  support + XPU guard UNION with upstream @replay_launches (sgl-project#43859).
kernels/ops/diffusion/README.md + docs performance-optimization.mdx: unions.
rust/sglang-renderer: ACCEPTED upstream crate removal (sampling.rs git rm);
  our sgl-project#42779 top_k vocab-bound port moves to sglang-server message/sampling.rs
  (follow-up).
Tests: anthropic test_serving — UNION fake-class exception param + our
  request_preprocessor stubs, took upstream's _FakeNonStreamingOpenAI removal
  (68 pass). test_server_args — union both new tests (pre-existing GPU-only
  collection ENV fail unchanged). test_lora_inference_mode — took upstream
  sgl-project#43028 runtime_scale+quality-tier test (supersedes our adapted variant;
  our kept quality-tiered _scale_lora_delta satisfies it).
  test_fused_moe_triton_config — kept our int4-filename + RTX-PRO-6000 JSON
  tests despite upstream sgl-project#43503 test-drop. test_hicache_file_lru_unit —
  union our corrupt-page-degrade class + upstream sgl-project#42077 uncached-batch test
  (40 pass). test_hicache_host_register — upstream alias host_common.
  test_cuda_graph_setup — took upstream rewrite of our override-decode-runner
  test into draft-local-capture-sizes test (sgl-project#43303 API).

Verified auto-merged stack markers survived: flashinfer_backend KVLocPlan/
prefill_wrappers_ragged/NVFP4-KV forwarding, memory_pool set_kv_buffer_prefix_valid,
fp8 grouped_moe kwarg, fp8_utils reshape fix 3642490, SGLANG_QWEN4_PLE_FILE_REUSE,
schedule_batch freed_rows fix, prefill_chunk_alignment sites, sgl-project#42736 autotune
default-on, PCIe-IPC vocab gathers, flashinfer 0.7.1 pin taken (NOT installed).

Co-Authored-By: Claude Opus 5.5 (1M context)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants