Skip to content

CUDA: fuse RMS_NORM + SCALE into one kernel - #29393

Merged
ggerganov merged 1 commit into
ggml-org:masterfrom
InflexCZE:cuda-rms-norm-scale-fusion
Sep 25, 2026
Merged

ggerganov merged 1 commit into
ggml-org:masterfrom
InflexCZE:cuda-rms-norm-scale-fusion

Conversation

@InflexCZE

@InflexCZE InflexCZE commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Additional information

Tested HW
GPU 2 x NVIDIA GeForce GTX 1080 Ti 11 GiB (sm_61, Pascal), 250 W limit
Driver 582.66, WDDM (CUDA 13.0 driver API)
CPU Intel Core i7-13700KF (16 cores / 24 threads)
RAM 64 GiB DDR4-3200 (4 x 16 GiB)
OS Windows 11 Pro 10.0.26200

llama-server -m Qwen3.8-27B-UD-Q4_K_XL.gguf -ngl 99 -np 1 -c 56000 -ub 128 -b 2048 -ts 53,47 -ot "token_embd\.weight=CPU" --spec-type draft-mtp --spec-draft-n-max 3 --spec-draft-p-min 0.5 --cache-ram 20480

build pp 8000 pp 20000
own b10828 371.7 337.2
own b10829 355.6 (-4.3%) 321.6 (-4.6%)
own b10829 + fix 370.2 336.5
own master 356.5 322.0
own master + fix 371.4 (+4.2%) 337.4 (+4.8%)

llama-bench -m Qwen3.8-27B-UD-Q4_K_XL.gguf -ngl 99 -ts 53/47 -ot "token_embd\.weight=CPU" -b 2048 -ub 128,512 -p 512,2048 -n 128 -r 5 (No speculative decoding)

build pp512 @ub128 pp2048 @ub128 pp512 @ub512 pp2048 @ub512 tg128
prebuilt b10828 372.2 420.5 262.6 388.7 12.73
prebuilt b10829 369.5 418.7 262.1 385.3 12.68
prebuilt b11140 367.2 418.2 262.0 385.3 12.90
own b10828 372.3 420.4 262.6 388.7 12.73
own b10829 368.1 418.1 261.9 385.3 12.67
own b10829 + fix 372.0 420.0 262.2 388.1 12.75
own master 367.7 419.1 262.2 385.4 12.90
own master + fix 372.6 420.6 262.8 388.6 12.98

Correctness

llama-perplexity -c 2048 --chunks 16 -ub 128

build PPL
prebuilt b10828 (old ggml_l2_norm) 3.2351 +/- 0.05721
prebuilt b10829 3.2030 +/- 0.05589
own master 3.2030 +/- 0.05589
own master + fix 3.2030 +/- 0.05589

test-backend-ops test -b CUDA0

op CUDA0 CUDA1
RMS_NORM_SCALE 10/10 10/10
NORM_SCALE 10/10 10/10
RMS_NORM_MUL_ADD 36/36 36/36
RMS_NORM_MUL_ROPE 222/222 222/222
RMS_NORM 51/51 51/51
RMS_NORM_BACK 10/10 10/10
NORM 50/50 50/50
L2_NORM 20/20 20/20
SCALE 4/4 4/4

Scope and limits

  • Minimal changes just to get the fuse back
  • Measured on one machine only: Pascal (sm_61), Windows, no CUDA graphs => launch overhead

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES - Claude Code (Opus 5.5) found revision, prepared fix, benchmarked. I reviewed, verified outputs, and wrtite this PR.

- ggml-org#28068 builds the GDN q/k l2norm as ggml_scale(ggml_rms_norm(x, eps/n), 1/sqrt(n)). This adds 2 SCALE nodes per GDN layer, 96 extra kernel launches per ubatch on Qwen3.8-27B (48 GDN layers).
- The extra kernels take no measurable GPU time, but each launch has a host/driver cost. It is small with plain batch processing and about 10x larger with draft-mtp speculative decoding.
- rms_norm_f32 gets a do_scale flag, the same pattern as do_multiply/do_add, so the fused path shares the kernel, the reduction and the launcher. It computes scale * (rsqrt(mean + eps) * x), which matches the unfused rms_norm + scale bit for bit, so ggml-org#28068 numerics are kept.
- Fusion only fires when SCALE has no bias and the rms_norm output has a single consumer (ggml_can_fuse).
- Metal (ggml-org#28948) and SYCL (ggml-org#28931) already fuse the same pattern.

Measured on 2x GTX 1080 Ti (sm_61, PCIe 3.0 x16 + x4), i7-13700KF, Windows 11, driver 582.66, CUDA 12.9.
Qwen3.8-27B-UD-Q4_K_XL, -ngl 99 -ts 53,47 -ot token_embd=CPU, master fee39dd.

llama-bench -ub 128,512 -p 512,2048 -n 128 -r 5, tok/s:

  build           pp512@128  pp2048@128  pp2048@512  tg128
  master           367.7      419.1       385.4      12.90
  master + fix     372.6      420.6       388.6      12.98
                   +1.3%      +0.4%       +0.8%      +0.6%

llama-server cold prefill, -c 56000 -ub 128 -b 2048, draft-mtp n-max 3 p-min 0.5, mean of 2 rounds x 3 reps:

  build           pp 8000         pp 20000
  master          356.5           322.0
  master + fix    371.4 (+4.2%)   337.4 (+4.8%)

- Launches per ubatch go from 1032.9 + 841.7 back to 978.9 + 799.7 (CUDA0 + CUDA1), the b10828 count. The GPU op sum is unchanged.
- test-backend-ops RMS_NORM_SCALE, NORM_SCALE, RMS_NORM_MUL_ADD, RMS_NORM_MUL_ROPE, RMS_NORM, RMS_NORM_BACK, NORM, L2_NORM and SCALE all pass on both GPUs.
- Perplexity is identical to the unfused build: 3.2030 +/- 0.0559 at -c 2048, 16 chunks.
- Draft acceptance counts per request match the unfused build.

Assisted-by: Claude Opus 5.5
@InflexCZE
InflexCZE requested a review from a team as a code owner September 24, 2026 17:33
@ggml-gh-bot

ggml-gh-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown

Hi @InflexCZE, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • PR Template not respected: Please respect the template when creating a new pull request. Make sure to fill out all required sections.

Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

@ggml-gh-bot ggml-gh-bot Bot added the draft PR will be changed to draft by github-actions bot label Sep 24, 2026
@am17an am17an added the merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. label Sep 24, 2026
@github-actions
github-actions Bot marked this pull request as draft September 24, 2026 18:21
@github-actions github-actions Bot added ggml changes relating to the ggml tensor library for machine learning CUDA Related to the CUDA backend and removed draft PR will be changed to draft by github-actions bot labels Sep 24, 2026
@InflexCZE

Copy link
Copy Markdown
Contributor Author

@ggml-gh-bot Fixed contributing guidelines link. Was that it?

@am17an
am17an marked this pull request as ready for review September 25, 2026 02:47
@ggerganov
ggerganov merged commit 1ab7e5a into ggml-org:master Sep 25, 2026
14 checks passed
adromir pushed a commit to adromir/llama-cpp-turboquant that referenced this pull request Sep 25, 2026
- ggml-org#28068 builds the GDN q/k l2norm as ggml_scale(ggml_rms_norm(x, eps/n), 1/sqrt(n)). This adds 2 SCALE nodes per GDN layer, 96 extra kernel launches per ubatch on Qwen3.8-27B (48 GDN layers).
- The extra kernels take no measurable GPU time, but each launch has a host/driver cost. It is small with plain batch processing and about 10x larger with draft-mtp speculative decoding.
- rms_norm_f32 gets a do_scale flag, the same pattern as do_multiply/do_add, so the fused path shares the kernel, the reduction and the launcher. It computes scale * (rsqrt(mean + eps) * x), which matches the unfused rms_norm + scale bit for bit, so ggml-org#28068 numerics are kept.
- Fusion only fires when SCALE has no bias and the rms_norm output has a single consumer (ggml_can_fuse).
- Metal (ggml-org#28948) and SYCL (ggml-org#28931) already fuse the same pattern.

Measured on 2x GTX 1080 Ti (sm_61, PCIe 3.0 x16 + x4), i7-13700KF, Windows 11, driver 582.66, CUDA 12.9.
Qwen3.8-27B-UD-Q4_K_XL, -ngl 99 -ts 53,47 -ot token_embd=CPU, master fee39dd.

llama-bench -ub 128,512 -p 512,2048 -n 128 -r 5, tok/s:

  build           pp512@128  pp2048@128  pp2048@512  tg128
  master           367.7      419.1       385.4      12.90
  master + fix     372.6      420.6       388.6      12.98
                   +1.3%      +0.4%       +0.8%      +0.6%

llama-server cold prefill, -c 56000 -ub 128 -b 2048, draft-mtp n-max 3 p-min 0.5, mean of 2 rounds x 3 reps:

  build           pp 8000         pp 20000
  master          356.5           322.0
  master + fix    371.4 (+4.2%)   337.4 (+4.8%)

- Launches per ubatch go from 1032.9 + 841.7 back to 978.9 + 799.7 (CUDA0 + CUDA1), the b10828 count. The GPU op sum is unchanged.
- test-backend-ops RMS_NORM_SCALE, NORM_SCALE, RMS_NORM_MUL_ADD, RMS_NORM_MUL_ROPE, RMS_NORM, RMS_NORM_BACK, NORM, L2_NORM and SCALE all pass on both GPUs.
- Perplexity is identical to the unfused build: 3.2030 +/- 0.0559 at -c 2048, 16 chunks.
- Draft acceptance counts per request match the unfused build.

Assisted-by: Claude Opus 5.5
feal87 added a commit to feal87/myllama.cpp that referenced this pull request Sep 25, 2026
Merge upstream commits:
  - llama: add llama_prec_policy + model-driven W4A4 path (ggml-org#24364)
  - llama: fix tensor split for fused qkv with uneven K/V head sizes (ggml-org#29294)
  - metal: split fa kernels into per-dtype libraries (ggml-org#29329)
  - metal: FWHT kernels for block widths above 512 (ggml-org#29095)
  - CUDA: fuse RMS_NORM + SCALE into one kernel (ggml-org#29393)
  - common: extract shared unicode path/string helpers (ggml-org#29415)
  - common,rpc: simplify fs_create_directory_with_parents() (ggml-org#29432)
  - rpc: include nb in the get_alloc_size cache key (ggml-org#29283)
  - [SYCL] support sparse FA (ggml-org#28796)
  - musa: fix PH1 operator failures and build issues (ggml-org#29193)
  - HIP: bump HIP_VERSION required for fp8 (ggml-org#29231)
  - opencl: add q5_k bin kernel (ggml-org#29401)
  - hexagon: add q5_k quant type support (ggml-org#29123)
  - hexagon: use DMA for contiguous dim1 CONCAT (ggml-org#29404)
  - mtmd: fix mel preprocessor in LFM2 audio (ggml-org#29403)
  - vulkan: fix legacy GLSLC without cooperativeMatrix (ggml-org#29409)
  - gguf-py: ByteLevel processing defaults bos/eos to False (ggml-org#29422)
  - gguf-py: TemplateProcessing has final word on add_special_token (ggml-org#29417)

Assisted-by: Pi
sky-mighty pushed a commit to sky-mighty/llama.cpp that referenced this pull request Sep 26, 2026
- ggml-org#28068 builds the GDN q/k l2norm as ggml_scale(ggml_rms_norm(x, eps/n), 1/sqrt(n)). This adds 2 SCALE nodes per GDN layer, 96 extra kernel launches per ubatch on Qwen3.8-27B (48 GDN layers).
- The extra kernels take no measurable GPU time, but each launch has a host/driver cost. It is small with plain batch processing and about 10x larger with draft-mtp speculative decoding.
- rms_norm_f32 gets a do_scale flag, the same pattern as do_multiply/do_add, so the fused path shares the kernel, the reduction and the launcher. It computes scale * (rsqrt(mean + eps) * x), which matches the unfused rms_norm + scale bit for bit, so ggml-org#28068 numerics are kept.
- Fusion only fires when SCALE has no bias and the rms_norm output has a single consumer (ggml_can_fuse).
- Metal (ggml-org#28948) and SYCL (ggml-org#28931) already fuse the same pattern.

Measured on 2x GTX 1080 Ti (sm_61, PCIe 3.0 x16 + x4), i7-13700KF, Windows 11, driver 582.66, CUDA 12.9.
Qwen3.8-27B-UD-Q4_K_XL, -ngl 99 -ts 53,47 -ot token_embd=CPU, master fee39dd.

llama-bench -ub 128,512 -p 512,2048 -n 128 -r 5, tok/s:

  build           pp512@128  pp2048@128  pp2048@512  tg128
  master           367.7      419.1       385.4      12.90
  master + fix     372.6      420.6       388.6      12.98
                   +1.3%      +0.4%       +0.8%      +0.6%

llama-server cold prefill, -c 56000 -ub 128 -b 2048, draft-mtp n-max 3 p-min 0.5, mean of 2 rounds x 3 reps:

  build           pp 8000         pp 20000
  master          356.5           322.0
  master + fix    371.4 (+4.2%)   337.4 (+4.8%)

- Launches per ubatch go from 1032.9 + 841.7 back to 978.9 + 799.7 (CUDA0 + CUDA1), the b10828 count. The GPU op sum is unchanged.
- test-backend-ops RMS_NORM_SCALE, NORM_SCALE, RMS_NORM_MUL_ADD, RMS_NORM_MUL_ROPE, RMS_NORM, RMS_NORM_BACK, NORM, L2_NORM and SCALE all pass on both GPUs.
- Perplexity is identical to the unfused build: 3.2030 +/- 0.0559 at -c 2048, 16 chunks.
- Draft acceptance counts per request match the unfused build.

Assisted-by: Claude Opus 5.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CUDA Related to the CUDA backend ggml changes relating to the ggml tensor library for machine learning merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants