Repository navigation
Conversation
…h decode At batch 1 an Ouro-1.4B token launches about 5,700 GPU kernels, most of them small elementwise and reduction kernels from RMSNorm (8 per norm, 4 norms per layer), RoPE and SiLU*up, repeated for 24 layers and 4 loops. CUDA graphs remove their launch cost but not their minimum run time. This adds fused Triton kernels for the three that are bitwise identical to the PyTorch reference: - RMSNorm: torch still computes the mean (so the reduction order is the reference's); one Triton kernel squares, one computes rsqrt(mean + eps), the scale, the cast and the weight multiply. 3 launches instead of 8; with a residual, torch adds it (4 instead of 9). - RoPE and SiLU*up: one kernel each. - Rounding points are forced with inline PTX (cvt.rn.*.f32, mul.rn.f32; kernels/rounding.py). Writing the same casts is not enough: Triton/LLVM keep an fp32 product live across .to(bf16).to(f32) and contract a*b+c into an FMA, so a fused residual epilogue computed round(r + w*n) where torch computes round(r + round(w*n)); 18.4% of 75,776 bf16 elements differed by one ulp. Fused kernels run only while a CUDA graph is being captured (VLLM_RLT_FUSED_SCOPE=graph, default): a Triton launch from Python costs more host time than the torch ops it replaces, so eager paths (prefill, sync eager decode) stay on torch. With fusion in every scope, sync eager decode on a GH200 ran at 0.40x and prefill steps took 33-46% longer. VLLM_RLT_FUSED_NORM/ROPE/ACT = exact (default) | approx | 0. The approximate one-kernel versions are opt-in: they are faster but change greedy outputs. GH200, Ouro-1.4B, 512-token prompts, 128 output tokens, async scheduling + CUDA graphs, one run per level, all arms in one job: kernels per token (batch 1): 5689 -> 2885; GPU time 13.05 -> 8.91 ms throughput vs unfused: 1.44x / 1.44x / 1.23x / 1.14x at 1 / 8 / 32 / 128 token identity vs unfused: 169/169 requests GSM8K (87-question subset, greedy, serial): 62/87 both, 0 sequences differ. SiLU*up: 0 mismatching elements in 8.3e8 (bf16/fp16/fp32, 3 gate scales). Approximate set, for comparison: 1.70x at batch 1, but -2.30 and -3.45 points on the same GSM8K subset. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 25, 2026
0z5a
marked this pull request as ready for review
October 25, 2026 16:45
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.
Summary
At batch 1 an Ouro-1.4B token launches about 5,700 GPU kernels, most of them small elementwise and reduction kernels from RMSNorm (8 per norm, 4 norms per layer), RoPE and SiLU·up, repeated for 24 layers × 4 loops. CUDA graphs remove their launch cost but not their minimum run time. This PR adds fused Triton kernels for the three that are bitwise identical to the PyTorch reference, and uses them only while a CUDA graph is being captured.
rsqrt(mean + eps), the scale, the cast and the weight multiply. 3 launches instead of 8 (4 instead of 9 with a residual, which torch adds).kernels/rounding.py): inline PTXcvt.rn.*.f32/mul.rn.f32. Writing the same casts as torch is not enough: Triton/LLVM keep an fp32 product live across.to(bf16).to(f32)and contracta*b+cinto an FMA. Our first fused residual epilogue computedround(r + w*n)where torch computesround(r + round(w*n)); 18.4% of 75,776 bf16 elements differed by one ulp.VLLM_RLT_FUSED_SCOPE=graph, default): a Triton launch from Python costs more host time than the torch ops it replaces, so eager paths (prefill, sync eager decode) stay on torch. With fusion in every scope, sync eager decode on GH200 ran at 0.40× and prefill steps took 33–46% longer.VLLM_RLT_FUSED_NORM/_ROPE/_ACT=exact(default) |approx|0. The approximate one-kernel versions stay opt-in: faster, but they change greedy outputs.Measurements
GH200, Ouro-1.4B, 512-token prompts, 128 output tokens, async scheduling + CUDA graphs, all three arms in one job, one run per level:
Platforms. GH200: torch 2.14.0+cu130, Triton 3.8.0, NIXL 1.4.1. RTX 5090: torch 2.13.0+cu130, Triton 3.7.1. Models: Ouro-1.4B (24 layers, 4 loops, fixed depth) and Huginn-0125 (2 prelude + 4 recurrent + 2 coda layers, 32 loops). Burst workloads issue C requests at once with random-token prompts; session workloads replay multi-turn agent traces with the prefix cache on.
Tests
pytest --run-gpu tests/test_normalization.py tests/test_rotary_embedding.py tests/test_ouro.py tests/test_cuda_graph.py): 124 passed, 2 skipped (fused RoPE needs a power-of-two half dimension). Without FlashAttention installed, the FlashAttention tests failed at import on this branch and onmain(66920da) alike.Notes
The GPU numbers come from a development branch that contains all changes of this series; most cells are single runs, and on the shared GH200 host two runs of
mainat 32 requests differed by up to 16%, so smaller differences are ties. This branch alone was checked with the CPU suite, ruff and the GPU tests above.Series. Draft PRs from one study of serving looped models. They are independent except #152, which is stacked on #147; several touch the same files, so later ones may need a rebase after earlier ones merge.