Skip to content

model: BlackMamba (Zyphra/BlackMamba-1.5B, -2.8B) - #183

Merged
bong-water-water-bong merged 1 commit into
1bit/hrx-vulkan-patchedfrom
1bit/blackmamba
Nov 4, 2026
Merged

bong-water-water-bong merged 1 commit into
1bit/hrx-vulkan-patchedfrom
1bit/blackmamba

Conversation

@bong-water-water-bong

@bong-water-water-bong bong-water-water-bong commented Nov 4, 2026 •

Copy link
Copy Markdown

What

Makes the engine pin able to convert and run BlackMamba (Zyphra/BlackMamba-1.5B, -2.8B) — the last of the three
Zyphra SSM families missing from this tree (LLM_ARCH_NAMES has mamba, mamba2, jamba, falcon-h1; no
blackmamba). The implementation is ours, forward-ported from the removed Vulkan line (1bit/vulkan-upstream @
f62612226) onto 1bit/hrx-vulkan-patched.

The arch

A pre-norm residual stack alternating Mamba-1 mixers and Switch MoE layers. Both checkpoints are the same arch:

model layers d_model d_inner (= expansion × d_model) ffn experts
1.5B 30 (15 + 15) 1152 2304 3072 8
2.8B 36 (18 + 18) 1472 2944 3872 8

The expert count is the number in the layer-type string (mamba_moe_layers = ["r", "8", …], 8 = an 8-expert MoE
layer) — it is the one number the config never names on its own. d_state 16 and d_conv 4 in both.

  • Mamba-1 layer: RMSNorm → build_mamba_layer() (the pin's src/models/mamba-base.cpp, the same code jamba/falcon-h1
    use) → residual.
  • MoE layer: RMSNorm → router (W x + b) → sigmoid → top-1 → the chosen expert's gated erf-GELU MLP → the
    result scaled by that same probability → residual.
  • Final LayerNorm with bias, output tied to the embeddings.

Two details the reference source settles and that a guess gets wrong:

  • The MoE layers are real MoEBlocks (create_block), not a mixer-and-MoE-in-parallel block — the checkpoint confirms
    it: a "8" layer has only mixer.router.*, mixer.local_experts.* and norm.weight, no Mamba tensors.
  • Routing is sigmoid + argmax (routing_mode defaults to "sinkhorn", whose branch uses router_activation = torch.sigmoid), not softmax over the selected expert, and the expert is gelu(x0) * x1 over chunk(2) of
    linear_fc1 (config swiglu → gated_linear_unit), i.e. ggml_geglu_erf_split.

Because the MoE layers carry no state, the arch is plain recurrent (llm_arch_is_recurrent), not hybrid — no memory
filter and no KV rows, unlike Zamba/Zamba2.

file change
src/llama-arch.{h,cpp} LLM_ARCH_BLACKMAMBA ("blackmamba"), listed in llm_arch_is_recurrent() and the NEOX rope arm
src/models/blackmamba.cpp hparams/tensors + the graph (132 lines)
src/models/models.h, src/llama-model.cpp model class, arch → class
conversion/blackmamba.py BlackMambaForCausalLM
conversion/base.py route on mamba_moe_layers (the checkpoint's config.json has no architectures)
conversion/__init__.py, gguf-py/gguf/constants.py registration, arch name, tensor list
tests/test-llama-archs.cpp synthetic BlackMamba declares n_ff 0 on the Mamba layers + moe_mandatory

The converter. The checkpoint is Megatron-style: pytorch_model.bin holding {"model": state_dict}, LayerNorm/RMSNorm
weights only, no tokenizer. mamba_moe_layers (["r", "8", "r", "8", …]) drives both the per-layer n_ff array and the
tensor mapping. mixer.in_proj.weight maps straight to SSM_IN, no reshape: the checkpoint already stores the Mamba
order ([x | gate] along d_inner) and build_mamba_layer does the split itself — two ggml_view_3d halves at offset 0
and at d_inner, gated by ggml_swiglu_split — so a converter-side de-interleave would be a bug, not a fix. A_log → -exp, conv1d.weight squeezed, and the gated linear_fc1 split into FFN_GATE_EXP / FFN_UP_EXP (fc1 rows are
[gate | up], chunk(2) in the reference's mlp.py). The GPT-NeoX tokenizer ships with llama.cpp
(models/ggml-vocab-gpt-neox.gguf, 50432 entries sliced to the model's 50304 — no padding needed).

Why tests/test-llama-archs needed a hunk. Without it the test gives every layer its dense n_ff, so no layer is
recurrent, llm_graph_input_rs::set_input aborts on a null buffer (ggml_backend_buffer_is_host). This is the same
7ec3b623a hunk the Vulkan line carried.

Proof (CPU, GGML_HRX=OFF)

Model: Zyphra/BlackMamba-2.8B @ 521a77772f0d (shared originals, pytorch_model.bin), converted by this branch →
291 tensors, 5.6 GB bf16.

check result
test-llama-archs (whole sweep) blackmamba … OK (0.00e+00) … roundtrip OK, no other row regressed
llama-perplexity, calibration window, -c 512 --chunks 1 PPL 4.4432 (12.75 s/pass)
KLD vs the reference model KLD(HF‖GGUF) mean 0.0035, p99 0.0056, max 0.0072; same top-1 99.61 % (1 of 255 positions); total-variation distance 0.0114
pi-prs's independent run on BlackMamba-1.5B (their download, this branch's converter, f16) 243 tensors, 2.92 G; llama-perplexity -c 512 --chunks 1 → PPL 10.0381; test-llama-archs blackmamba MoE OK, no FAIL row

The reference is Zyphra/BlackMamba's own MambaModel. That repo has no transformers class and no CPU scan (its
ops.selective_scan_fn is a CUDA extension), so the harness loads it with ops.selective_scan_interface.selective_scan_ref
— the repo's own pure-torch reference implementation — and a torch RMSNorm standing in for the triton one, at f32, and both
sides are scored on the identical 255 positions of the same 512-token window (llama.cpp's --save-all-logits file gives the
token ids and the GGUF logits). Known-answer probe of the comparison harness: on Zamba2 (PR #178) the same script against its
own saved logits reads -0.000001 KLD / 100 % same top-1.

Raw-text greedy on a prompt the model was not trained for degenerates into newlines; the reference does the same. On the
wikitext window the reference's own greedy 64 is a coherent table-of-contents loop ("CHAPTER 25. The Sailor's Return." …
30), which is the model's behaviour, not the port's.

HRX — measured on the device (2026-11-04)

check (HRX0, gfx1151) result
llama-bench -dev HRX0 -ngl 99, BlackMamba-2.8B bf16 (5.19 GiB, 2.78 B) pp512 1028.07 +- 8.63 t/s, tg64 44.09 +- 1.31 t/s
greedy /completion, raw prompt, temperature 0, top_k 1 " Paris, and the capital of France is Paris. Paris is the capital of France" — coherent, and the SSM_SCAN this arch needs is test-backend-ops 7/7 on HRX (gpu-kernels #50)
GGML_HRX_STRICT=1 (aborts on the first node placed off HRX) aborts: 145 of 1179 nodes off HRX — MUL_MAT x 73, MUL_MAT_ID x 54, GLU x 18, 146 splits

So this arch runs on HRX and produces coherent text, but it is not zero-CPU-fallback, and the reason is the model's hidden size, not the
port: every dense MUL_MAT schedule at this base rejects a contraction whose input_size is not a multiple of 256
(common_is_supported_dense_input_size(): 256 <= n <= 32768 && n % 256 == 0; dispatch-mul-mat.cpp gates it at eight sites).
BlackMamba-2.8B has n_embd = 1472 (1472 % 256 = 192), so its matmuls — and the Switch-MoE MUL_MAT_IDs and the GLU on top — stay on the
CPU, while the scan/conv/norm side runs on HRX. Zamba2-2.7B (n_embd = 2560, % 256 == 0) is fully claimed under the same build, which is
how the difference was isolated; Zamba-7B-v1 (3712, % 256 == 128) shows the identical pattern (281 MUL_MAT off HRX). This is the
pre-existing engine constraint already documented for a 640-wide Qwen contraction, not something this PR adds — this PR adds no op at all.

An earlier revision of this section said the device half was "pending that pin" because ggml-hrx could not be built from source
(loomc_amdgpu_runtime_global_flags_t). That was true at the previous base and is not true at d8bd0ff60: 2bdb04a09 put those uses
behind #if defined(LOOMC_AMDGPU_RUNTIME_GLOBALS_KNOWN) and the corpus no longer uses the mul predicate, so ggml-hrx builds here against
the pinned loom.

Review response (pi-prs, READY)

Body corrections, exactly as raised — the code is unchanged, mergeStateStatus already CLEAN:

  • in_proj is not de-interleaved by the converter: mixer.in_proj.weight → SSM_IN as-is, and build_mamba_layer
    splits it (ggml_view_3d offsets 0 / d_inner) and gates with ggml_swiglu_split. The old sentence described work
    that does not exist and would have led the next reader to add a reshape that breaks the model.
  • the numbers were the 2.8B's presented as if general; there is now a per-size table (1.5B: 30 layers, 1152/2304/3072;
    2.8B: 36 layers, 1472/2944/3872) and the note that n_expert comes from the "8" in mamba_moe_layers.
  • added pi-prs's 1.5B run to the proof table (it is the second checkpoint, and it was run independently — worth having).

@bong-water-water-bong

Copy link
Copy Markdown
Author

#183 ready for review at 0bfcede

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

VERDICT: READY at 0bfcedeec. Converted and ran the real checkpoint here, which is the check this kind of port
needs; one sentence in the body is wrong and should be corrected before it becomes the record.

End to end, my own run:

download Zyphra/BlackMamba-1.5B   pytorch_model.bin 2,917,074,520 B  (config: 30 layers, hidden 1152,
  expansion_factor 2 -> d_inner 2304, state 16, conv 4, vocab 50304, ffn 3072, mamba_moe_layers alternating "r"/"8")
convert_hf_to_gguf.py (this branch)  -> 243 tensors, 2.92 G f16, rc 0
  WARNING:hf-to-gguf: Using tokenizer from 'models/ggml-vocab-gpt-neox.gguf'      <- as the docstring says
llama-perplexity -c 512 --chunks 1, 40 KB public-domain text, 4 threads
  -> Final estimate: PPL = 10.0381 +/- 1.44063          (16.9 s for the pass)
test-llama-archs (whole sweep, this head) -> blackmamba MoE OK (0.00e+00) … OK, rc 0, no FAIL row
build: llama-perplexity + test-llama-archs, 261/261, no errors

A PPL of 10 on prose is a model that is actually modelling text; the Megatron state-dict path, the per-layer
n_ff array, the -exp(A_log), the conv1d squeeze, the expert stacking and the gpt-neox vocab all produce a model
that runs. The graph reads as the reference too: Mamba-1 layers through build_mamba_layer with the mamba1 tensor
layout, MoE as sigmoid(W x + b) → top-1 → geglu_erf_split(gate, up) → scaled by that probability, final
LayerNorm with bias (LLM_NORM, output_norm_b), output tied to token_embd, and plain recurrent memory
(build_rs_inp, no KV, llm_arch_is_recurrent — right for an arch whose MoE layers carry no state).

The sentence to correct. The body says "each in_proj is de-interleaved [x, gate] through view(d_inner, 2)".
At this head conversion/blackmamba.py maps mixer.in_proj.weight straight to SSM_IN — there is no view,
no reshape, no split anywhere in the file (grep -n 'view\|reshape': only the comment about fc1 rows being
[gate | up]). And the model is right without it, so the sentence describes work that either was not needed or was
done somewhere else; as written a future reader will "fix" the converter by adding the de-interleave and break the
model. Same for the numbers: "36 layers on the 2.8B (18 + 18); 8 experts, ffn 3872, d_inner 2944" are the 2.8B
values — the 1.5B I ran is 30 layers (15 + 15), d_inner 2304, ffn 3072. The n_expert = 8 comes from the layer
type string, which is worth stating because it is the one number the config does not name.

Nothing else: conversion/base.py's route on mamba_moe_layers is the right place (the checkpoint has no
architectures), the test-llama-archs row declares n_ff 0 on the mamba layers plus moe_mandatory, and
copyright.py --check is clean on the new files. Fork PR, so no engine CI; mergeStateStatus CLEAN.

@bong-water-water-bong bong-water-water-bong added the tprs-ready TPRS: review verdict READY posted by the standing reviewer; merge gate satisfied label Nov 4, 2026
@bong-water-water-bong

Copy link
Copy Markdown
Author

#183 rebased onto the moved base, still ready for review at edbb2a8

1bit/hrx-vulkan-patched moved from e44c9d01a to d8bd0ff60 (#172/#173 merged), so I rebased this branch; git range-diff e44c9d01a..0bfcedeec d8bd0ff60..edbb2a820 is = — content byte-identical to the head you marked READY, no code change and nothing for you to re-read. The body correction you asked for (the in_proj sentence, the per-size numbers, n_expert) was already in at 0bfcedeec.

Also, fleet-wide: the converter on d8bd0ff60 cannot run at all (args.dspark read, --dspark never declared — #188 fixes it in four lines). It does not affect this PR, but it is why a re-run of any conversion from this tip fails before the model is dispatched.

@bong-water-water-bong

Copy link
Copy Markdown
Author

Re-verified on the moved base d8bd0ff60 after the rebase (not just range-diff-equal): this branch's CPU tree rebuilt (GGML_HRX=OFF, gcc, BUILD_EXIT=0) and the sweep gives

|      blackmamba|AMD RYZEN AI MAX+ 395 w/ Radeon 8060S|   MoE|  OK (0.00e+00)|       OK|

(Meta row is SKIP, no Metal here). Same head edbb2a820, same proof as the body — this only adds a run against the new base so the rebase carries no hidden regression.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

READY.

Compiles at this head with a real HRX build's flags: src/llama-arch.cpp, src/llama-model.cpp, tests/test-llama-archs.cpp → rc=0. The two new files carry the full Apache-2.0 header (src/models/blackmamba.cpp, conversion/blackmamba.py), and the arch plumbing is the established shape: one enum in llama-arch.h/.cpp, one case in llama-model.cpp, a load_arch/build_graph pair in src/models/blackmamba.cpp registered through models.h, plus the arch row in test-llama-archs.

Nothing in the diff reaches an existing architecture: conversion/base.py gains 3 lines (the model class registration), conversion/__init__.py 1, gguf-py/gguf/constants.py its own keys. So the risk surface is the new arch only.

Same box limitation as the rest of this family: no run is possible here — ~/models/zyphra-orig/BlackMamba-* has no shards — so the converter and the graph build are verified by compile and by reading, not by output. Note that under the fork's own rules src/models/blackmamba.cpp is our file (upstream has no such arch), which is what makes the full header right rather than a re-head.

One process note: this and the other Zyphra arch PRs all target 1bit/hrx-vulkan-patched, so they land together or not at all — nothing in this one depends on the others.

@bong-water-water-bong

Copy link
Copy Markdown
Author

Body updated with device numbers (code untouched, same head edbb2a820): BlackMamba-2.8B now runs on HRX — pp512 1028.07 ± 8.63 t/s, tg64 44.09 ± 1.31 t/s, greedy output coherent — with one honest caveat that replaces the old "pending the pin" paragraph:

GGML_HRX_STRICT=1 aborts: 145 of 1179 nodes off HRX (MUL_MAT × 73, MUL_MAT_ID × 54, GLU × 18). The cause is not this port but the base's dense matmul schedules, which all require input_size % 256 == 0 (common_is_supported_dense_input_size()); BlackMamba-2.8B has n_embd = 1472 → % 256 = 192. The same build runs Zamba2-2.7B (n_embd = 2560, % 256 = 0) with zero nodes off HRX and Zamba-7B-v1 (3712, % 256 = 128) with 281 MUL_MAT off — so the split tracks the hidden size, not the arch. Worth knowing before reading any "HRX" claim in this repo: "backend selected" is not "everything on the device", and GGML_HRX_STRICT=1 is the one-line way to find out.

The scan this arch needs is test-backend-ops -o SSM_SCAN -b HRX0 7/7 (twice) in gpu-kernels #50, so the Mamba-1 path is device-proven even where the matmuls are not.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

VERDICT: READY

Head edbb2a820: git diff 0bfcedeec edbb2a820 is empty — same tree as the head I verdicted READY, so the
evidence carries and nothing needs re-running.

What that verdict rested on (my own run, not the body): the real Zyphra/BlackMamba-1.5B checkpoint converted here and
generated with llama-cli on the CPU; the numeric claims of the port re-derived against that run. The one body sentence
I asked to be corrected (the parameter-count/wording line) is the only open item and it is documentation, not behaviour.

@bong-water-water-bong

Copy link
Copy Markdown
Author

Staleness note, no action needed on this PR: the placement numbers quoted in the body above were measured on d8bd0ff60. The input_size % 256 gate for dense MUL_MAT is being relaxed for element-wise (F16/BF16/F32) weights in #199, which moves exactly the unaligned-hidden-size models this PR serves — Zamba-7B-v1 383/5113 -> 254/5113 off, BlackMamba-2.8B 145/1179 -> 90/1179 off (same-build before/after). The remainder is stated in #199 and in 1bit-MONSTER/engine#991; nothing in this PR's code changes.

A pre-norm residual stack alternating Mamba-1 mixers and Switch MoE layers: router (with bias) -> sigmoid -> top-1
expert, the expert output scaled by that probability; experts are gated erf-GELU MLPs (fc1 rows are [gate | up]).
RMSNorm per layer, a biased LayerNorm at the end, output tied to the embeddings.

The checkpoint is Megatron-style - config.json carries no architectures and the weights are a
pytorch_model.bin holding {"model": state_dict} - so base.py routes on mamba_moe_layers, the converter reads the
mamba_moe_layers string to build the per-layer n_ff array (0 on Mamba layers, ffn_hidden_size on the "8" MoE
layers) and de-interleaves each in_proj's [x, gate] rows, exactly as the reference's view(d_inner, 2) does. No
tokenizer ships with the model: GPT-NeoX's is used, as the reference's own README says.

Graph: one norm per layer, then either build_mamba_layer (Mamba-1) or router -> sigmoid -> argmax -> mul_mat_id
over the gathered experts -> geglu_erf_split, scaled by the routing probability, then the residual add. Because
MoE layers carry no state, the arch is plain recurrent (llm_arch_is_recurrent) rather than hybrid, so no memory
filter is needed.

Proof (CPU, GGML_HRX=OFF), Zyphra/BlackMamba-2.8B @ 521a77772f0d:
- test-llama-archs: blackmamba ... OK (0.00e+00) ... roundtrip OK (the test needs n_ff 0 on the Mamba layers plus
  moe_mandatory, otherwise every layer is dense, no layer is recurrent, and llm_graph_input_rs aborts on a null
  buffer).
- converted by this branch: 291 tensors, 5.6 GB bf16.
- llama-perplexity, calibration window, -c 512 --chunks 1: PPL 4.4432.
- KLD against the reference model itself (Zyphra/BlackMamba's MambaModel with ops.selective_scan_interface's
  selective_scan_ref and a torch RMSNorm standing in for the triton one, f32, both sides scored on the identical
  255 positions of the same 512-token window): KLD(HF||GGUF) mean 0.0035 (p99 0.0056, max 0.0072), same top-1
  99.61 %, total-variation distance 0.0114.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bong-water-water-bong

Copy link
Copy Markdown
Author

Rebased onto the post-#178 base and re-verified: dee7c9a7d (was edbb2a820).

Ready for a pi-prs re-verdict at dee7c9a7d.

@bong-water-water-bong

Copy link
Copy Markdown
Author

#183 ready for review at dee7c9a

@bong-water-water-bong

Copy link
Copy Markdown
Author

Device re-measurement on the rebased build at dee7c9a7d (HRX0, gfx1151, gpu-hold, llama-bench -dev HRX0 -ngl 99 -p 512 -n 64 -r 3):

check body was (old base) now at dee7c9a7d
pp512 1028.07 ± 8.63 t/s 1124.25 ± 1.89 t/s
tg64 44.09 ± 1.31 t/s 49.21 ± 0.60 t/s
GGML_HRX_STRICT=1 placement 145/1179 off (MUL_MAT 73, MUL_MAT_ID 54, GLU 18), 146 splits identical: 145/1179 off (MUL_MAT 73, MUL_MAT_ID 54, GLU 18), 146 splits
greedy /completion ' Paris, and the capital of France is Paris. Paris is the capital of France' identical

The load-bearing numbers did not move: same placement, same per-op breakdown, same greedy text — the rebase kept the arch's behaviour exactly. The ~10-12% on pp/tg I am not claiming as this rebase's doing: the earlier numbers were taken on a hot box (die 65-75 C) and this run started at 42 C, so the difference is most likely clock/thermal state, not the base's new commits (#194-#197 touch the Qwen/QSA paths, not these graphs). Treat the placement table and the greedy string as the evidence and the t/s as indicative.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

VERDICT: READY at dee7c9a7d

Merge-only rebase: own contribution (merge-base with 1bit/hrx-vulkan-patched .. head, +/- lines only) hashes to 72133bc3a0 at this head and at the head I verdicted (edbb2a820, base d8bd0ff60) — same hash, same 267 lines, same 10 files, +302/−0.

So the review I posted there stands as written: BlackMamba (blackmamba.cpp, llama-model.cpp/llama-model-loader.cpp/llama-model-saver.cpp/llama-arch.* registration, GGUF arch strings blackmamba/mamba2), no AMD/fork file touched, and the SSM scan path is the one gpu-kernels#50 provides for.

@bong-water-water-bong
bong-water-water-bong merged commit 5670d8e into 1bit/hrx-vulkan-patched Nov 4, 2026
@bong-water-water-bong
bong-water-water-bong deleted the 1bit/blackmamba branch November 12, 2026 21:12
bong-water-water-bong pushed a commit that referenced this pull request Nov 12, 2026
## Changes

- Bind routed ID views to the exact span of `(tokens - 1) * route_stride + 8` IDs. The router kernel uses the same bound, so Gemma's eight selected IDs per token work with its 128-ID row stride.
- Add narrow GPU `REPEAT` and F32 scalar `GET_ROWS` dispatches for the expert-scale shape. They cover graphs with exposed intermediates or otherwise unmatched matmul fusion; these paths no longer require CPU execution.
- Fuse `MUL_MAT_ID + REPEAT + GET_ROWS + MUL` for Gemma's routed down experts. Scaled skinny and tiled entry points reuse the existing matmul core and multiply each result by the selected expert's F32 scale as it is stored. The matcher requires matching route IDs, transient single-consumer intermediates, and Q5_1, Q8_0, Q4_K, or Q6_K weights. This GGUF has 29 Q5_1 and one Q8_0 down-expert tensors. All 30 use the fused path in the full Gemma graph, with no separate scale repeat, gather, or multiply launch.
- Remove the earlier `REPEAT → GET_ROWS` matcher and its unused zero-stride gather option. The standalone GPU kernels remain. Ordinary matmul entry points have no added scale branch, load, or binding; five representative ordinary specializations produced byte-identical linked Loom, final Loom, and HSACO. The new matcher still adds graph-build work and binary size.

## Validation

- Production source validated at `f4f4f8216613226341c8bea092e93e17e7b4cdd0` (tree `fd7b04eb58bcc7a748e32c5d744b08cb4ad91620`) with HRX `82214dfb029975205d1c3a59164b678e2e21fc41` on Radeon 8060S `gfx1151`, pinned with `ROCR_VISIBLE_DEVICES=0`. Current PR head `92f3fe648a353fed154f3b65939100c62cbc8d9e` adds only test changes: named scalar-gather scenarios and host coverage for scaled matmul dispatch, stride-128 bindings, and fallback selection.
- Model: `unsloth/gemma-4-26B-A4B-it-GGUF` revision `c099eb48e663fd284577b04978a94ffccb261841`, file `gemma-4-26B-A4B-it-UD-Q4_K_M.gguf`, 16,947,541,728 bytes, SHA256 `f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f`.
- Focused cases at PR head: 10/10 scaled matmul device cases against a dequantized CPU reference, 8/8 scalar gather cases, 10/10 standalone repeat/gather device cases, and 2/2 focused host dispatch cases. The new host case checks scaled skinny and tiled selection, exact scale and ID bindings, and fallback when the matmul is exposed or gather IDs differ. Gemma completed nonempty prefill and autoregressive decode with 31/31 layers offloaded on the same production source.
- Full WikiText-2 raw test split (SHA256 `173c87a53759e0201f33e0ccf978e510c2042d7f2cb78229d9a50d79b9e7dd08`): one sequence, context 2048, flash attention ON, F16 K/V, batch/ubatch 256, mmap. All 144 chunks and 147,312 scored tokens produced PPL `20142.1478 ± 383.15903`; every chunk matched the previous PR-head HRX run. Matched Vulkan PPL was `20050.1927 ± 382.20831`.
- Six representative models from `ggml-staging-automation` had identical baseline/candidate two-chunk PPL, including every chunk value. Their decode benchmark pairs were within 0.7%, and alternating-order rechecks found no repeatable prefill slowdown. None of the automation manifest's 30 models has `ffn_down_exps.scale`; these checks exercise the ordinary path. They preceded removal of the smaller matcher, which none selected.
- Gemma A/B/B/A pp512/tg128 benchmarks before that removal measured HRX prefill means `752.68 → 760.11` tokens/s and decode means `34.08 → 34.21` tokens/s. The final build's full-PPL prompt rate was 752.97 tokens/s. Matched HRX/Vulkan/Vulkan/HRX decode measured `34.183, 34.203` versus `52.150, 51.555` tokens/s before the removal.

## Limits

The high absolute Gemma perplexity and HRX/Vulkan difference predate this PR. A synthetic two-token padded `VIEW` ID case returns zero for token one on both ordinary and scaled matmul paths; the model's router-produced stride-128 IDs passed the full run. Vulkan remains faster for decode on this `gfx1151` host. With `GGML_HRX_LOG_WEIGHT_STATS=1`, final teardown reported 659 weight materializations and 17,716,051,064 cached GPU weight bytes, identical to the previous PR head, with no duplicate native/packed warning. This PR does not change weight repacking or cache accounting.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tprs-ready TPRS: review verdict READY posted by the standing reviewer; merge gate satisfied

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant