Skip to content

llama : scale moe down projection per expert token - #30861

Merged
ggerganov merged 4 commits into
masterfrom
gg/amax-pool1d-meta
Oct 21, 2026
Merged

ggerganov merged 4 commits into
masterfrom
gg/amax-pool1d-meta

Conversation

@ggerganov

@ggerganov ggerganov commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Overview

ref #30656
alt #30733

Replace the global amax rescaling for the MoE down projection with a per-expert-token factor computed by a POOL_1D max reduction along the intermediate dimension. The same exact power-of-two factor is undone on the output, so the roundtrip preserves the matmul result. Handle POOL_1D in the Meta split-state logic.

Additional information

  • The scaling factor is exact (power of two), so the input is divided by the same scale used on the output.

Requirements

@github-actions github-actions Bot added the ggml changes relating to the ggml tensor library for machine learning label Oct 20, 2026
Comment thread src/llama-graph.h Outdated
Comment thread src/llama-graph.cpp Outdated
Comment thread ggml/src/ggml-backend-meta.cpp Outdated
Replace the global amax rescaling for the MoE down projection with a per-expert-token factor computed by a POOL_1D max reduction along the intermediate dimension. Undo the scale on the output with the same exact power-of-two factor. Handle POOL_1D in the Meta split-state logic so a split along axis 0 becomes a replicated per-device partial result, enabling tensor parallelism.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
@ggerganov
ggerganov force-pushed the gg/amax-pool1d-meta branch from 9e7f76c to 0c5b7cb Compare October 20, 2026 13:47
Return the rescaled activation and the optional scale tensor through a small struct instead of an output pointer.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)
@ggerganov

Copy link
Copy Markdown
Member Author

Tested on CUDA - all good. Should be ready to merge.

@ggerganov ggerganov added the merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. label Oct 20, 2026
@Trivaxy

Trivaxy commented Oct 20, 2026

Copy link
Copy Markdown

Tested on Vulkan with Mellum2.1, tps and prefill are much better now

@ggerganov
ggerganov merged commit 8baabd5 into master Oct 21, 2026
33 of 34 checks passed
@ggerganov
ggerganov deleted the gg/amax-pool1d-meta branch October 21, 2026 06:01
@ggerganov

Copy link
Copy Markdown
Member Author

There is one remaining issue - some quantization implementations use the Q8_1 to quantize the activations. The block_q8_1 computes F16 block-wise sums. These sums can still overflow, even if the individual values do not overflow.

@jeffbolznv Let me know if you have any thoughts. For now I think I'll adjust the scaling to be more conservative.

voidstackloop added a commit to voidstackloop/llama.cpp that referenced this pull request Oct 21, 2026
Resolve the conflict with 8baabd5 (llama : scale moe down projection per
expert token, ggml-org#30861): master replaced the MISTRAL4/MELLUM F32 precision
override on the MoE down projection with an amax rescale of src1. Move that
rescale into the PR's build_experts lambda, the single path every expert
computation goes through (plain, cache, hybrid hot/cold, mailbox), in place
of the old ggml_prec_set_src there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeffbolznv

Copy link
Copy Markdown
Contributor

I think there was some proposal to use bf16 for the q8_1 scale factor, maybe we should do that?

@ggerganov

Copy link
Copy Markdown
Member Author

I think there was some proposal to use bf16 for the q8_1 scale factor, maybe we should do that?

Ok, I'll look into that.

adderek pushed a commit to adderek/llama.cpp that referenced this pull request Oct 21, 2026
297 upstream commits. Conflicts and how they were resolved:

- ggml ops: upstream added GGML_OP_TOP_K_MASK next to the fork's
  GGML_OP_TURBO_WHT; both are kept, upstream's first, GGML_OP_COUNT 103.
- MoE down projection: upstream replaced the MISTRAL4 F32 src1 precision
  hint (its own, ggml-org#29029) with a per-row amax rescale (ggml-org#30861) for MISTRAL4
  and MELLUM. It now sits in build_moe_ffn_exps, so every tiered call
  (hot, cold, each arena window) rescales its own rows; rows skipped by a
  partial mul_mat_id are zero, so their scale is 1.
- MoE post_exp_norm (new upstream): applied in build_moe_ffn after the
  hot/cold merge and before the router weights; the two halves fill
  disjoint expert slots, so the norm sees whole rows.
- build_lora_mm_id: upstream's Hadamard input transform (prism) after the
  fork's partial/slots assert. Expert tensors split by LLAMA_MOE_HOT are
  not in the Hadamard map, so prism and tiering do not stack on one layer.
- kv-cache attention rotation: the fork's default (off unless
  LLAMA_ATTN_ROT_DISABLE=0) is kept; upstream's llama_model_set_attn_rot
  can still turn it off per model.
- model loader: hugetlb per-tensor populate kept before upstream's
  multi-buffer lookup of the mmap buffer.
- llama-model: MoE arena setup and upstream's load_tensors_hadamard both
  run at the end of load_tensors.
- qwen4exp: turbo Q pre-rotation kept; the selection mask is now `sel`.
- server: upstream added draft-token logprobs (ggml-org#27196) read back from the
  batch logits, without post_sampling_probs. The fork's on_sample path,
  which records them during verification and covers post_sampling_probs,
  is kept; upstream's test_draft_token_probs passes with it. Upstream's
  local spec_i_batch is taken.
- mmq: fork's IQ1_M MMQ gains upstream's prec_src1 parameter.
- fattn-tile: the fork's D=640 case and HIP guard kept, upstream's
  default-case style.
- argsort.cu: upstream's CCCL guard; the fork's hipcub arm was dead since
  HIP does not define GGML_CUDA_USE_CUB.
- tests/CMakeLists.txt: fork tests kept, upstream's LINK and consolidated
  test-llama-context taken.

Tested on gfx1100: ctest 78/80 (the two failures are environmental: the
ggml-vocabs clone has git-lfs pointers, and system python has no jinja2).
All fork tests pass (turbo-kv x8, moe-tier, fattn-turbo4, fattn-long-kv,
mmvq-src1-cache, fusions, mul-mat-f16-range, top-k-graph-capture,
op-offload-devices). test-llama-archs 390/390. Server
test_speculative.py 10/10, completion/probs pytests 14/14. Needle at 20.5k
tokens, Qwen3.8-27B IQ4_NL, turbo4 K/V: correct, prefill 925 tok/s vs 909
before the merge (single run).
anemicsoftware added a commit to anemicsoftware/mx-llama.cpp that referenced this pull request Oct 23, 2026
…#30861)

Cherry-pick of upstream 8baabd5, minus the MoE-cache slots argument mx does not have.

The F32 src1 precision on the mistral4 down projection (from ggml-org#29029) made the CUDA backend reject the
MUL_MAT_ID, so every layer's down projection ran on CPU. With layer split that staged each layer's
expert weights in a ~101 GiB host buffer, and copying out of the repacked buffer (no get_tensor)
segfaulted. The graph now rescales src1 per expert token instead and the op stays on the GPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +777 to +783
auto handle_pool_1d = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0) {
return {GGML_BACKEND_SPLIT_AXIS_MIRRORED, {0}, {1}, 1};
}
return src_ss[0];
};

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.

I don't think this is correct. Let's say we have data like [0, 1, 2, 3]. If you do a max pooling with 2 elements each you would get [1, 3]. If you have a split along axis 0 you would go from backend0=[0, 1], backend1=[2, 3] to backend0=[1], backend1=[3]. That is still a split along axis 0 and not mirrored because the data per backend is not the same.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for flagging. So my understanding of the meta split states was a bit incorrect and looking in more details, I now realize that the implemented max pool is technically wrong with -sm tensor. The activations before the down projection are indeed split along axis 0 and we want each device to compute the max along the entire rows - not just its own split.

I don't see an easy fix for that, so probably I'll disable -sm tensor for the arches that need this correction. The amax scaling is a temporary workaround either way, so eventually it won't be necessary.

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 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.

4 participants