Skip to content

opencl: fuse QK normalization and RoPE - #28446

Open
happyyzy wants to merge 1 commit into
ggml-org:masterfrom
happyyzy:agent/opencl-adreno-fused-qknorm-rope-stacked
Open

happyyzy wants to merge 1 commit into
ggml-org:masterfrom
happyyzy:agent/opencl-adreno-fused-qknorm-rope-stacked

Conversation

@happyyzy

@happyyzy happyyzy commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fuses Q/K RMSNorm + RoPE, K/V scaling, and V materialization in OpenCL. Also fixes CAST self-reference counting in the shared fusion checker.

Tested on Adreno 830; stable-diffusion.cpp e31a86ce9110b11a98bd5990c329093244c2d1e3.

Performance

Model Size / steps Before s/it After s/it Before sampling (s) After sampling (s) After fused kernels, total (s)
Klein 4B 512 / 4 11.81 5.45 52.76 25.54 0.154910
Klein 4B 1024 / 4 37.65 21.15 154.09 90.84 0.512706
Z-Image -Turbo 512 / 8 12.65 6.88 107.48 60.59 0.348515
Z-Image-Turbo 1024 / 8 65.03 36.27 505.22 292.91 1.488915

Profiling was OFF for the first three baselines, ON for Z-Image 1024 and all after-runs; the first three rows are not profiling-controlled comparisons. Kernel times exclude readback; sampling times exclude VAE decode.

Kernel Bandwidth

D=128, batch 1. Effective bandwidth = useful tensor bytes / mean GPU event time; utilization is relative to Snapdragon 8 Elite's 84.8 GB/s RAM peak.

Model / size H Stream L Output L Calls Mean kernel_us GB/s Utilization
Klein 512 24 512 1536 20 499.930 63.974 75.44%
Klein 512 24 1024 1536 20 1002.931 63.777 75.21%
Klein 512 24 1536 1536 80 1560.666 61.477 72.50%
Klein 1024 24 512 4608 20 555.290 57.596 67.92%
Klein 1024 24 4096 4608 20 4393.459 58.235 68.67%
Klein 1024 24 4608 4608 80 5171.642 55.656 65.63%
Z-Image 512 30 32 32 16 33.728 73.867 87.11%
Z-Image 512 30 1024 1024 16 1304.608 61.086 72.03%
Z-Image 512 30 1056 1056 240 1362.924 60.299 71.11%
Z-Image 1024 30 32 32 16 35.856 69.483 81.94%
Z-Image 1024 30 4096 4096 16 5429.744 58.708 69.23%
Z-Image 1024 30 4128 4128 240 5839.438 55.015 64.88%

Images

Case Before After
Klein 512, 4 steps Klein 512 before Klein 512 after
Klein 1024, 4 steps Klein 1024 before Klein 1024 after
Z-Image 512, 8 steps Z-Image 512 before Z-Image 512 after
Z-Image 1024, 8 steps Z-Image 1024 before Z-Image 1024 after

Z-Image 1K images above use GGML_OPENCL_PROFILING=ON; OFF produced corrupted output. This unexpected issue remains unresolved. And the klein baseline image also shows the same corrupt mode, where the sum of weights and activations is near to 8GB. This commit does not enable profiling or change its default.

Commands

Build with GGML_OPENCL_USE_ADRENO_KERNELS=ON.

export GGML_OPENCL_Q4_0_DENSE_DP4A=1
export GGML_OPENCL_XMEM_SDPA=1

Klein 512

./sd-cli --diffusion-model models/flux-2-klein-4b-Q4_0.gguf --llm models/qwen_3_4b-Q4_0.gguf --vae models/flux2-vae.safetensors -p 'a lovely cat' --cfg-scale 1 --guidance 3.5 --steps 4 --seed 42 -W 512 -H 512 --diffusion-fa --vae-conv-direct -t 4 -v -o klein_512.png

Klein 1024

./sd-cli --diffusion-model models/flux-2-klein-4b-Q4_0.gguf --llm models/qwen_3_4b-Q4_0.gguf --vae models/flux2-vae.safetensors -p 'a lovely cat' --cfg-scale 1 --guidance 3.5 --steps 4 --seed 42 -W 1024 -H 1024 --diffusion-fa --vae-conv-direct -t 4 -v -o klein_1024.png

Z-Image 512

./sd-cli --diffusion-model models/z_image_turbo-Q4_0-nobf16.gguf --llm models/qwen_3_4b-Q4_0.gguf --vae models/ae_old.safetensors -p 'a lovely cat wearing black sunglasses, studio photo' --cfg-scale 1 --guidance 3.5 --steps 8 --seed 42 -W 512 -H 512 --diffusion-fa --vae-conv-direct -t 4 -v -o zimage_512.png

Z-Image 1024

./sd-cli --diffusion-model models/z_image_turbo-Q4_0-nobf16.gguf --llm models/qwen_3_4b-Q4_0.gguf --vae models/ae_old.safetensors -p 'a lovely cat wearing black sunglasses, studio photo' --cfg-scale 1 --guidance 3.5 --steps 8 --seed 42 -W 1024 -H 1024 --diffusion-fa --vae-conv-direct -t 4 -v -o zimage_1024.png

After this PR,adreno 830 runs DITs like z-image and flux klein with throughout about 2Tflops, which means an amazing MFU about 1/3 on a phone! A perfetto timeline for one transformer block of z-image is as follows:
image

Requirements

@happyyzy
happyyzy requested review from a team and ggerganov as code owners September 5, 2026 17:19
@happyyzy happyyzy changed the title opencl: fuse QKV normalization and RoPE opencl: fuse QK normalization and RoPE Sep 5, 2026
@github-actions github-actions Bot added ggml changes relating to the ggml tensor library for machine learning OpenCL Issues specific to the OpenCL backend labels Sep 5, 2026
@happyyzy

happyyzy commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@lhez, could you take a look at the changes when you have a chance?

@happyyzy

happyyzy commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

@max-krasnyansky, could you take a look at the changes when you have a chance?

Comment thread ggml/src/ggml.c

int subgraph_uses = 0;
for (int j = i + 1; j < count; ++j) {
for (int j = i; j < count; ++j) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment why this is necessary?

Comment on lines +8101 to +8106
if (out->op != GGML_OP_RESHAPE || out->src[0]->op != GGML_OP_ADD || out->ne[0] % 2 != 0) { return nullptr; }
const auto * add = out->src[0];
const int64_t d = out->ne[0], l = out->ne[1], heads = out->ne[2] * out->ne[3];
const int64_t rotated_shape[] = {2, d / 2, l, heads}, pe_shape[] = {2, 2, d / 2, l};
const int32_t split_axes[] = {3, 0, 1, 2}, head_axes[] = {0, 2, 1, 3};
if (memcmp(add->ne, rotated_shape, sizeof(rotated_shape)) != 0) { return nullptr; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are bit difficult to read for human. Can you put each decl in its own line and don't use one liners for if?


static void ggml_opencl_op_qkv_preprocess(ggml_backend_t backend, const ggml_opencl_qkv_preprocess & match);

static const ggml_tensor * ggml_opencl_match_qkv_rope(const ggml_tensor * out, const ggml_tensor * & pe,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this fusion is for DiT, maybe we should call these functions and kernel ggml_opencl_match_dit_qkv_rope, ggml_opencl_op_dit_qkv_preprocess, kernel_fused_dit_qknorm_rope_f32?

@lhez

lhez commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

@happyyzy the commit in the PR says Assisted-by: Codex but AI usage disclosure says no -- please update.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ggml changes relating to the ggml tensor library for machine learning OpenCL Issues specific to the OpenCL backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants