Conversation
Bonsai 2 ships weights that were folded with a normalized Sylvester Walsh-Hadamard rotation, so the activation must be rotated by the same transform before every folded matmul. Without it the model loads and silently produces garbage. - llama-model: parse the prism.hadamard.* metadata block, validate it, and build one F32 rotation per (block size, buffer type) plus the per-width sign vectors in the same buffer as the folded weights - llama-graph: apply sign flip and rotation to the activation in build_lora_mm / build_lora_mm_id, memoized per (activation, rotation), and apply the inverse after the token-embedding lookup - llama-context: one-time graph check that every folded weight is consumed through its transform, turning a silent wrong-math path into a load error The generic pieces (GGML_HINT_SRC0_IS_HADAMARD, llama_mul_mat_hadamard, the CUDA FWHT kernel) were already present upstream; only the model-side glue was missing. Verified on Ternary-Bonsai-2-27B Q2_0 (group 64, 402 folded weights) on an RTX 5060 Ti: coherent output and 46 t/s decode, where the same file on the previous build returns gibberish.
The Bonsai 2 ggufs published on the main PrismML repo use two quant types that are not in upstream ggml: PQ2_0 (type 142, group 128, 2.13 bpw) and PTQ1_0 (type 143, group 128, 1.75 bpw, trits packed base-3 five per byte). Only the dev-repo Q2_0 file (type 42, group 64) loaded before this change. Ported from the PrismML fork: - block layouts, traits and reference quantize/dequantize - CPU vec_dot: generic path plus the x86 VNNI and ARM SIMD variants, with the usual arch-fallback aliases for every other target - CUDA: dequantize, convert, get_rows, MMVQ and MMQ kernels including the PTQ1_0 multi-column path that shares one 128-trit decode across up to three columns, plus the Ampere MMQ tuning table - ftype plumbing: gguf, model loader, quantize tool and gguf-py PTQ1_0 has no SIMD vec_dot on any arch upstream, so it aliases the generic one everywhere; PQ2_0 keeps the x86/ARM implementations. gguf.cpp also learns to recognise a Q2_0 file that is really in the legacy group-128 layout and point at the PQ2_0 build instead of failing with a bare tensor-offset mismatch. Not ported: the DGX Spark (GB10) tile double-buffering and L2 prefetch paths, which have no counterpart here, and the Metal/Vulkan/CDNA/RDNA backends. Verified on a 5060 Ti (sm_120, CUDA 12.8) with Ternary-Bonsai-2-27B-PQ2_0: test-backend-ops MUL_MAT 94/94 and GET_ROWS 8/8 against the CPU reference, correct generation at 47.3 t/s, and no regression on the existing type-42 file.
|
Tested #169 on an RTX 5060 Ti 16 GB with Bee: Early results are solid. Correctness
No quality regression observed from the port. PerformanceBack-to-back, same GGUF and flags:
Bee is ~15% slower on short prefill and ~9% slower on decode, but uses ~457 MiB less VRAM. Reasoning controlThis is the main gain for my workload. With unbounded reasoning, Bonsai had several cases that consumed very large reasoning budgets without returning a usable final answer. Under Bee with an 8K reasoning budget, the same 50-question Spatial/Zebra set finished 50/50:
The prior unbounded Bonsai result on the same set was 98.0%. One representative case went from ~414s without a usable answer to ~66s with a correct answer. So far #169 looks like a clean Bonsai 2 port, with the main practical benefit being Bee's reasoning controls rather than raw throughput. |
The MTP graph does its own row lookup on the embedding table and never restores the primal basis, while the trunk (build_inp_embd) does. On a prism.hadamard model that mismatch trips the graph check: Hadamard-latent table 'token_embd.weight' is read without the inverse transform so a Bonsai 2 gguf with an embedded MTP block refuses to load at all. Factor the inverse out of build_inp_embd into build_hadamard_inverse_embd and call it from both paths, against whichever table was actually read (layer.nextn.embed_tokens, else model.tok_embd). Models without folded weights have an empty inverse map, so this is a no-op for them. Same fix as PrismML-Eng#205, adapted to this tree. Measured on Ternary-Bonsai-2-27B-PQ2_0-MTP-Q8_0 (RTX 5060 Ti, sm_120, CUDA 12.8, greedy, 4K context): the file now loads, and decode goes from 37.4 t/s with the drafter off to 71.1 t/s at --spec-draft-n-max 8, with draft acceptance 0.247 and mean draft length 2.98. The vendor default of 2 leaves ~13% on the table here (61.7 t/s); the curve peaks at 8 and falls off by 16 (52.9 t/s). Speculative decoding verifies every draft against the target, so output is unchanged.
What
Makes PrismML's Bonsai 2 models run on BeeLlama. Two independent pieces are needed, one per commit:
prism.hadamardfolded weights. Bonsai 2 ships weights pre-folded with a normalized Sylvester–Walsh–Hadamard rotation, so the activation has to be rotated by the same transform before every folded matmul. Without it the model loads happily and produces garbage.PQ2_0/PTQ1_0quant types. The ggufs on the main PrismML repo use two types that are not in upstream ggml:PQ2_0(id 142, group 128, 2.13 bpw) andPTQ1_0(id 143, group 128, 1.75 bpw, trits packed base-3 five per byte). Before this only the dev-repoQ2_0file (id 42, group 64, supported since CUDA: add Q2_0 (ternary, group-64) weight support #96) would load.Provenance
Ported from PrismML's own llama.cpp fork, the same way #96 was done. Kernel and format design credit is theirs; this adapts it to the v0.4.6 tree.
Changes
Hadamard (
126fa16)llama-model: parse and validate theprism.hadamard.*metadata block; build one F32 rotation per (block size, buffer type) plus the per-width sign vectors, in the same buffer as the folded weights.llama-graph: apply sign flip and rotation to the activation inbuild_lora_mm/build_lora_mm_id, memoized per (activation, rotation); apply the inverse after the token-embedding lookup.llama-context: a one-time graph check that every folded weight is consumed through its transform — turns a silent wrong-math path into a load error.Quant types (
dd614c2)vec_dot: generic path plus the x86 VNNI and ARM SIMD variants forPQ2_0, with arch-fallback aliases for every other target.PTQ1_0has no SIMD variant on any arch in the source fork either, so it aliases the generic one everywhere.dequantize,convert,get_rows, MMVQ and MMQ kernels — including thePTQ1_0multi-column path that shares one 128-trit decode across up to three columns — plus the Ampere MMQ tuning table and the two template instances.gguf, model loader,quantizetool,gguf-py.gguf.cppalso recognises aQ2_0file that is really in the legacy group-128 layout and points at thePQ2_0build, instead of failing with a bare tensor-offset mismatch.Deliberately not ported: the DGX Spark (GB10) tile double-buffering and L2-prefetch paths, which have no counterpart in this tree, and the Metal / Vulkan / CDNA / RDNA backends. CUDA only, same scope as #96.
Verification
RTX 5060 Ti (sm_120), CUDA 12.8,
-DGGML_CUDA_FA_ALL_QUANTS=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON.test-backend-ops -o MUL_MAT: 94 OK / 0 FAIL for the two new types against the CPU reference. The remaining cases aretype_b=f16, reportednot supportedby both CUDA and CPU — the same split the existingQ2_0shows (47 OK / 33 not supported per type).test-backend-ops -o GET_ROWS: 8/8 OK.Ternary-Bonsai-2-27B-PQ2_0.gguf(7.21 GB, 402 tensors of type 142): loads asPQ2_0 - 2.13 bpw (group 128),loaded 402 Hadamard-folded weight(s), answers correctly at 302 t/s prefill / 47.3 t/s generation.Ternary-Bonsai-2-27B-PTQ1_0.gguf(5.95 GB, 402 tensors of type 143): loads asPTQ1_0 - 1.75 bpw (group 128), answers correctly on both a short prompt (171 t/s prefill / 42.4 t/s generation, MMVQ path) and a ~380-token one (357 t/s prefill / 41.1 t/s generation, MMQ path, which this type takes at every batch size).Qwen3.8-27B-UD-Q3_K_XLscores 0.950 vs 0.750 here. The gap sits almost entirely inmms_issue(5/6 vs 2/6); both are 6/6 onservice_issueand within one task onmobile_data_issue. That is the quantisation/model trade, not a porting defect — kernels match the CPU reference exactly (see above).smallsplit, where every task has one fault per task. The splits behind published leaderboard numbers are much harder (baseaverages 4.25 faults per task,test4.08), so these are useful for comparing the two local models under identical conditions, not for ranking against the leaderboard.MTP fix (
477afefd)Hadamard-latent table 'token_embd.weight' is read without the inverse transform. The inverse is factored out ofbuild_inp_embdintobuild_hadamard_inverse_embdand called from both paths, against whichever table was actually read. Same change as qwen35: apply the Hadamard inverse to the MTP token-embedding lookup PrismML-Eng/llama.cpp#205. No-op for models without folded weights.ProCreations/Ternary-Bonsai-2-27B-MTPloads and decode goes 37.4 → 71.1 t/s (--spec-draft-n-max 8, acceptance 0.247, mean draft length 2.98). The upstream launcher's default of 2 gives only 61.7 t/s here.