Repository navigation
[Perf] [AMD] vision: Batch ViT across requests on ROCm when the DP encoder is enabled - #46058
vorapolsiloai wants to merge 2 commits into
Conversation
…s enabled ROCm keeps one ViT call per request (sgl-project#32415). Under --mm-enable-dp-encoder that leaves a single-image request on one attention-TP rank while the others wait, so the DP encoder has nothing to split. Use the cross-request batched path on ROCm when the DP encoder is enabled and attention-TP > 1; NPU, XPU, and ROCm without the DP encoder or at attention-TP 1 are unchanged. Under that same condition, Qwen3-VL warms its vision tower on every rank in the post-load hook (precompile_kernels_after_loading), so a non-zero rank does not pay its first ViT call (rotary torch.compile, lazy kernel loads, the vectorized pos-embed path) inside the first served image burst.
006d29d to
96d4c41
Compare
|
Measurements of this PR on GLM-5.3-Flash ( Summary
Setup
Server command (the same for every build)python3 -m sglang.launch_server \
--model-path amd/GLM-5.3-Flash-Quark-MXFP4 \
--revision b5688f25491202978c19c4d036eef579f61bbe07 \
--served-model-name amd/GLM-5.3-Flash-Quark-MXFP4 \
--quantization quark --tp-size 2 \
--attention-backend dsa --dsa-prefill-backend tilelang --dsa-decode-backend tilelang \
--linear-attn-backend triton --kv-cache-dtype bfloat16 \
--moe-runner-backend aiter --disable-shared-experts-fusion \
--disable-radix-cache --max-running-requests 64 \
--cuda-graph-backend-decode full --cuda-graph-max-bs-decode 64 \
--mem-fraction-static 0.92 \
--model-loader-extra-config '{"enable_multithread_load":true,"num_threads":8}' \
--trust-remote-code --tool-call-parser glm47 --reasoning-parser glm45 \
--enable-multimodal --limit-mm-data-per-request '{"image":1}' \
--mm-process-config '{"image":{"min_image_tokens":16,"max_image_tokens":8000}}' \
--mm-preprocess-cache-size-mb 0 --mm-enable-dp-encoder --mm-attention-backend aiter_attn \
--context-length 8192 --host 127.0.0.1 --port 30000This PR vs base
In this PR's first burst, rank 1's only logged compiles are its first With a GLM vision warm-up eppaneamd/sglang
With the warm-up, all vision compiles end before the server reports ready (launch to health 241 s, against 231 s without; 5 s poll). Without it, 6 compiles fell in the bursts (2 on rank 0, 4 on rank 1). Image sizes On the two-grid commit, one fresh server with the same flags got image bursts through the chat API at 112x112, 512x512, 1024x1024, 1920x1080, mixed sizes and 2464x2520 (up to 9 of the largest at once), from the 16-token floor to near the 8000-token ceiling of |
…P encoder With ViT batching across requests under the DP encoder on ROCm, a rank other than 0 runs its first vision call inside the first served image burst and compiles swiglu_clamped there, as Qwen3-VL does for its rotary kernel. - mm_utils.warm_up_vision_tower(visual, grids): the dummy-image loop from Qwen3-VL's warm-up, shared. - Qwen3-VL calls it with its grids (unchanged). - Glm5NextForConditionalGeneration.precompile_kernels_after_loading warms its tower under hip_dp_encoder_batches_vit() with two grids: the first compiles swiglu_clamped's 3-D (blocks) and 2-D (merger) calls, the second's new size recompiles the 3-D call with a dynamic shape. - Tests: the GLM hook's calls under the gate, none off the gate, none without a vision tower.
Motivation
ROCm (with NPU and XPU) keeps one ViT call per request in chunked prefill; the cross-request batch was excluded on ROCm in #32415 after a CI regression. With
--mm-enable-dp-encoder,run_dp_sharded_mrope_vision_modelsplits the images of one ViT call across the attention-TP ranks. Called once per request, a single-image request lands on rank 0 while the other ranks wait, so the DP encoder never splits any work. Batching across requests is what gives it work to distribute.Modifications
hip_dp_encoder_batches_vit()(multimodal/mm_utils.py): true on ROCm whenget_mm().mm_enable_dp_encoderandget_parallel().attn_tp_size > 1(the width the DP encoder shards over).mm_schedule._batch_vit_across_requests(): CUDA batches as before; NPU/XPU keep the per-request path; ROCm batches only when the helper above is true. No new environment variable.precompile_kernels_after_loadinghook that, under the same condition, runs the vision tower once on dummy inputs on every rank: grids (1,32,32) and (1,48,64), plus one call with_VECTORIZED_VL_POS_EMBED_MIN_IMAGESimages so the vectorized pos-embed path loads itstorch.uniquekernels. Outputs are discarded. Without it, under the batched path a non-zero rank ran its first ViT call (including the rotarytorch.compile) inside the first measured image burst: rank 1 compiled for ~18 s and TTFT p99 rose 128%. The startup warmup request has one image, so it only warms rank 0.Qwen4ExpForConditionalGeneration.precompile_kernels_after_loadingnow callssuper()first.Why TP1 and non-DP-encoder ROCm stay per-request
At attention-TP 1 there is nothing to distribute (the server already logs that the DP encoder "has no data-parallel work to distribute" at TP=1), and in our measurements batching at TP1 together with the ViT CUDA graph regressed (-1.8% / -2.6%). Without the DP encoder the behaviour that motivated #32415 still applies, so those paths are unchanged.
Accuracy
Outputs are not bit-exact against the per-request path, because GEMM tiling depends on M: embedding cosine 0.99996 mean, minimum token cosine 0.989. MMMU was checked on our internal stack; MMMU on this head is still to be measured.
Benchmark
Qwen3.8-27B, same-node A/B on MI350X screens (not MI355X), TP2 with
--mm-enable-dp-encoder:MI355X numbers and MMMU to be measured on this head.
Relation to #44727
Independent of the ViT CUDA graph PR (#44727). When both are enabled, each cross-request batch composition is a distinct graph layout there, bounded by its graph cache cap of 32.
Tests
test/registered/unit/multimodal/test_hip_dp_encoder_vit_batching.py(CPU): the gate across HIP/NPU/XPU/CUDA, DP encoder on/off, attention-TP 1/2; two single-image requests encode in one call only under the gate; the post-load hook warms the tower once with the three dummy calls only under the gate, and is a no-op for language-model-only checkpoints.test/registered/chunked_prefill/test_mm_chunked_embedding_unit.py: switched from patching the removedmm_schedule._is_hiptooverride_platform(is_hip=...).CI States
Latest PR Test (Base): ❌ Run #40494072702
Latest PR Test (Extra): ❌ Run #40494072546
Latest PR Test (AMD ROCm 10): ❌ Run #40494072515