rope: CUDA-graph-safe Triton ViT RoPE, unblocking Qwen3.5 in encode_server - #39
Open
ZhengWG wants to merge 1 commit into
Open
rope: CUDA-graph-safe Triton ViT RoPE, unblocking Qwen3.5 in encode_server#39ZhengWG wants to merge 1 commit into
ZhengWG wants to merge 1 commit into
Conversation
The encoder server (python/sglang/srt/disaggregation/encode_server.py)
runs the ViT path of multimodal models such as Qwen3.5 / Qwen3-VL via
get_image_feature / get_video_feature, which now goes through the
existing Qwen3VLMoeVisionModel + ViTCudaGraphRunner CUDA-graph capture
path (gated by SGLANG_VIT_ENABLE_CUDA_GRAPH=1).
The blocker for enabling that on Qwen3.5 was the apply_rotary_pos_emb
op used inside VisionAttention:
@torch.compile(dynamic=True, backend=get_compiler_backend())
def apply_rotary_pos_emb_native(...):
...
When captured inside torch.cuda.graph, the dynamo guards / dynamic
shape state injected by torch.compile manifest as a 'random_rng'
capture failure, so the ViT graph cannot be built.
This change:
1. Adds a Triton kernel (no torch.compile) for NEOX-style rotary
embedding on q/k tensors of shape [N, H, D] with cos/sin of shape
[N, D]. The kernel performs the rotation in fp32 internally for
numerical parity with apply_rotary_pos_emb_native and writes q/k
in-place. Triton kernels are stateless and CUDA-graph safe, so the
ViT graph capture succeeds.
2. Wires the Triton kernel as the default apply_rotary_pos_emb on CUDA
via apply_rotary_pos_emb_cuda_triton, which silently falls back to
the native (torch.compile'd) implementation for shapes/strides it
does not own (e.g. LM-side 4D q/k as in Gemma3).
3. Updates docs to list Qwen3.5 / Qwen3.5-MoE as a supported model for
ViT CUDA Graph and documents the encoder-server invocation.
4. Adds a unit test covering numerical parity against the native
implementation across multiple dtypes and head sizes, in-place
semantics, the 4D fallback path, and a torch.cuda.CUDAGraph
capture + replay correctness check.
CPU / NPU paths are unchanged.
Co-authored-by: Zheng Wengang <zwg0606@gmail.com>
ZhengWG
marked this pull request as ready for review
May 11, 2026 13:11
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
The disaggregated encoder process (
python/sglang/srt/disaggregation/encode_server.py) runs the ViT path of multimodal models such as Qwen3.5 / Qwen3.5-MoE / Qwen3-VL viamodel.get_image_feature/get_video_feature. Those route intoQwen3VLMoeVisionModel.forward(...), which already has a CUDA-graph runner (ViTCudaGraphRunner) gated bySGLANG_VIT_ENABLE_CUDA_GRAPH=1.In practice CUDA Graph capture for Qwen3.5 failed before this change. Inside
VisionAttention.forwardthe rotary embedding is applied with:which on CUDA resolves to:
When this is invoked inside
with torch.cuda.graph(graph):, the dynamic-shape guards and RNG state injected bytorch.compileviolate CUDA Graph capture rules and surface as therandom_rngcapture failure. The vision graph is never built, so end-to-end ViT latency stays in the unfused, eager-kernel regime.Modifications
New CUDA-graph-safe Triton kernel for NEOX-style vision RoPE:
_triton_vision_rope_qk_inplace_kerneland wrappertriton_vision_rope_qk_inplaceinpython/sglang/srt/layers/rotary_embedding/triton_kernels.py.q [N, H, D],k [N, KH, D],cos / sin [N, D], in-place; accumulates in fp32 for numerical parity withapply_rotary_pos_emb_native.torch.cuda.graphcapturable.CUDA dispatch for
apply_rotary_pos_emb:apply_rotary_pos_emb_cuda_tritoninpython/sglang/srt/layers/rotary_embedding/utils.py.is_cuda()is true. Silently falls back to the existingapply_rotary_pos_emb_nativefor non-ViT shapes (e.g. 4D LM-side q/k as ingemma3_causal.py, non-2D cos/sin, mismatched dtypes, non-contiguous strides).Docs updated to advertise Qwen3.5 / Qwen3.5-MoE as supported by ViT CUDA Graph and to document the encoder-server invocation:
docs/advanced_features/cuda_graph_for_multi_modal_encoder.mddocs_new/docs/advanced_features/cuda_graph_for_multi_modal_encoder.mdxUnit test
test/registered/kernels/test_vision_rope_triton.py:apply_rotary_pos_emb_nativeacross(num_tokens, num_heads, num_kv_heads, head_size, dtype)configs covering bf16 / fp16 / fp32 and GQA layouts.torch.cuda.CUDAGraphcapture + replay correctness test, asserting the exact contract that the oldtorch.compilepath violated.Accuracy Tests
The kernel is bit-for-bit equivalent (up to fp32 accumulator order) to the existing
apply_rotary_pos_emb_nativereference. The new test compares against the reference atatol=rtol=1e-2for bf16/fp16 and1e-5for fp32, across multiple shapes and dtypes.Speed Tests and Profiling
The Triton kernel replaces a
torch.compile(dynamic=True)-wrapped sequence of unfused PyTorch ops (q.float(),cos.unsqueeze,rotate_half,q*cos,rotate_half(q)*sin,q_embed.to(orig_q_dtype), ...) with a single fused launch that does the rotation in fp32 and writes back q/k in-place. On top of that, end-to-end the ViT can finally run inside CUDA Graph for Qwen3.5, which collapses the per-layer kernel-launch overhead that dominates small-batch ViT latency on the encoder server.Checklist