Repository navigation
[diffusion] Replay small Triton wrappers' launches per operand signature - #43859
Merged
Merged
Conversation
A small Triton kernel's wrapper costs more host time than its GPU time: operand checks, stride math, the device guard and the JIT dispatch take ~28 us per rmsnorm_scale call at a Z-Image 512^2 shape on H200, all of it fixed by the operands' signature. SignatureLaunch keeps Triton's compiled launcher and the derived scalars per signature and relaunches with the same kernel, grid and arguments, so outputs are bit-identical. The signature includes 16-byte pointer alignment, which Triton specializes on. The launcher's calling convention is checked once per kernel against the JIT result (never inside graph capture); a convention change turns the fast path off. torch.compile traces the unchanged JIT path. SGLANG_DIFFUSION_TRITON_FAST_LAUNCH=0 disables it. Applied to rmsnorm_scale, rmsnorm_tanh_residual and zimage_qk_rmsnorm_native, the call sites #39878 replays from Rust; a Python replay of the compiled launcher costs the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mickqian
requested review from
BBuf,
niehen6174 and
yingluosanqian
as code owners
October 12, 2026 12:51
Collaborator
Author
|
/tag-and-rerun-ci |
Replace the per-wrapper signature/record code with @replay_launches: the first call per operand signature runs the wrapper while recording its Triton launches (JITFunction.run patched for the call; a dispatch mode allowing only allocation and views); later calls allocate the outputs and call the recorded compiled launchers. Kernels and wrapper bodies are untouched; each covered wrapper gains one decorator line. Views are recognised by aliasing rather than by op name: inference tensors skip the autograd decomposition, so a reshape reaches the guard as aten.reshape and a name list rejected rmsnorm_scale in a live server. Cover the hot wrappers of FLUX.1/2, Qwen-Image and Ideogram besides Z-Image's three: fused_layernorm_modulate_raw, fused_silu_mul_bitexact, fused_packed_silu_mul_bitexact, flux2_strided_qknorm_rope and fused_rope_rotate_half_bitexact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mickqian
requested review from
DarkSharpness,
HaiShaw,
HydraQYH,
celve and
yuan-luo
as code owners
October 13, 2026 06:08
4 of 5 tasks
This was referenced Oct 14, 2026
5 tasks
Merged
4 of 5 tasks
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Many of sglang's small Triton kernels cost more host time per call than GPU time. Before each launch, the Python wrapper runs operand checks, stride math, the device guard and Triton's JIT dispatch. For
rmsnorm_scaleat a Z-Image 512² shape that is ~28 µs on H200, against a ~9 µs launch. None of that work depends on tensor values, only on the operands' signature.A py-spy profile of the denoising loop (H200, 2 GPUs, eager, 256² so that the host bounds the step) shows how much of each step this is. The share below is the host time spent inside sglang's diffusion fused-kernel wrappers and their predicates:
fused_layernorm_modulate_raw14.6%,residual_gate_add8.5%fused_layernorm_modulate_raw12.3%,residual_gate_add8.2%,flux2_strided_qknorm_rope6.2%,fused_packed_silu_mul_bitexact6.1%fused_layernorm_modulate_raw10.6%,residual_gate_add7.4%,qknorm_complex_rope_cuda4.5%fused_rope_rotate_half_bitexact8.1%,residual_gate_add7.5%,modulate_scale_shift4.9%Whether this reaches end-to-end latency depends on whether the step is host-bound. That is the case on Grace hosts (GB200/GB300), with several TP ranks, and at low resolution.
#39878 removes the overhead for three Z-Image kernels by replaying recorded launches from a Rust extension. This PR does the same in pure Python, and does it generically, so that covering another kernel takes one decorator line and no change to the kernel or its wrapper.
Modifications
sglang/kernels/ops/diffusion/common/fast_launch.pyadds@replay_launches:data_ptr() % 16, plus the non-tensor arguments. On this call the wrapper runs normally while its Triton launches are recorded: compiled kernel, grid and arguments, with each tensor argument mapped by pointer to an input or a returned output.empty_stridedand call the recorded compiled launchers directly. The kernels, grids and arguments are the same, so outputs are bit-identical.torch.empty*and returns. Recording enforces what it can see of that and otherwise leaves the signature on the normal path:reshapeon inference tensors counts);.item());torch.compilethe decorator calls the wrapper itself, so it stays traceable.SGLANG_DIFFUSION_TRITON_FAST_LAUNCH=0turns it off.The decorator is applied, one line each, to the hot Triton wrappers above:
rmsnorm_scale,rmsnorm_tanh_residual,zimage_qk_rmsnorm_nativefused_layernorm_modulate_rawfused_silu_mul_bitexact,fused_packed_silu_mul_bitexactflux2_strided_qknorm_rope,fused_rope_rotate_half_bitexactKernels and wrapper bodies are unchanged: existing files change by +18/−1 lines in total (one import and one decorator per wrapper, plus the package README and
common/__init__docstrings). The mechanism is 281 lines in the new module, and the tests are 343 lines. JIT CUDA (tvm_ffi) wrappers such asresidual_gate_addare not covered, because their calls have no common hook to record.Accuracy Tests
Replay calls the same compiled kernels with the same arguments, so outputs are bit-identical. Output image md5 matched main in every end-to-end configuration below.
test/registered/kernels/ops/diffusion/test_fast_launch.py:no_gradandinference_mode.torch.compile(fullgraph=True)traces the wrapper. The off switch runs the wrapper.Passing on H200 (sm90) and GB300 (sm103), together with every existing suite that calls a decorated wrapper:
test_norm,test_model_fast_paths,test_activation,test_flux2_fp8_norm_quant,test_flux2_strided_qknorm_rope;test_layernorm_modulate,test_sites,test_shape_specialization,test_h3_vae_fused_norm,test_longcat_image_norm_modulate,test_fused_qk_rmsnorm_rope_gate;test_zimage_qknorm_fusion,test_qwen_image21_cuda,test_minimax_h3_vae_parallel_modes.Speed Tests and Profiling
Base
8afe107, torch 2.13, triton 3.7.1.Host time per call (µs, 256²-class shapes):
rmsnorm_scalermsnorm_tanh_residualzimage_qk_rmsnorm_nativefused_layernorm_modulate_rawfused_silu_mul_bitexactfused_packed_silu_mul_bitexactflux2_strided_qknorm_ropefused_rope_rotate_half_bitexactWith
SGLANG_DIFFUSION_TRITON_FAST_LAUNCH=0the numbers match main.End to end (client wall clock, median, eager):
GB300, one GPU. Servers of main and this PR are resident on the same GPU, requests are rotated between them, and processes are pinned to the GPU's NUMA node.
The previous revision of this PR applied the cache by hand inside each of the three Z-Image wrappers. It was measured in the same rotation: Z-Image 512² 357.5 / 355.1 ms, 1024² 405.2 ms. The decorator keeps most of that gain without touching the wrappers. Its per-call cost measured the same as the hand-written path, and every call replayed (counted in a live server).
GB300 ×2, Qwen-Image-2.1 TP2. main, this PR and a later JIT-replay experiment were resident together, with requests rotated over 8 rounds. The JIT-replay experiment was dropped: replay costs more than the thin tvm-ffi wrappers spend.
B200 ×2, TP2 / Ulysses2 (x86 host, AMD EPYC 9575F). Servers were swapped each round, 2 rounds.
H200, Z-Image one GPU: no change (512² 236.5 vs 234.9 ms; 1024² 862.7 vs 862.4 ms). The step is GPU-bound there.
The gain shows up where the step is host-bound: on Grace hosts, and at low resolution with several ranks. The JIT CUDA wrappers that remain (
residual_gate_add,qknorm_complex_rope_cuda,modulate_scale_shift) andcustom_all_reduceare the next host-time items for FLUX/Qwen/Ideogram.Checklist
🤖 Generated with Claude Code
CI States
Latest PR Test (Base): 🚫 Run #38355101182
Latest PR Test (Extra): ❌ Run #38355100702
Latest PR Test (AMD ROCm 10): ❌ Run #38355101222