Repository navigation
fix: I2_S ARM kernel layout + squared-ReLU FFN - passes the gate on the official 2B4T GGUF (#3) [stacked on #2 PR] - #7
Merged
Conversation
ggml.c (libggml-base) references quantize_i2_s and dequantize_row_i2_s,
which live in ggml-cpu/quants.c (libggml-cpu). ld64 rejects undefined
symbols in a shared library, so the default shared build fails at
libggml-base with:
Undefined symbols for architecture arm64:
"_dequantize_row_i2_s", referenced from: _type_traits in ggml.c.o
"_quantize_i2_s", referenced from: _ggml_quantize_chunk in ggml.c.o
Default BUILD_SHARED_LIBS to OFF on Apple when the user did not set it.
Refs microsoft#682, microsoft#641, microsoft#635 (reproduced on Apple M6 /
Apple clang 21.0.0, see #2).
…d_mul_mat ggml-cpu.c #undefs GGML_USE_LLAMAFILE when __ARM_FEATURE_MATMUL_INT8 or __ARM_FEATURE_SVE is defined, which compiles out the only declaration of src1_cont. The I2_S GEMV/GEMM fast path uses it unconditionally, so every arm64 CPU with i8mm (M2 and later, Graviton3, ...) fails with: ggml-cpu.c:1495:40: error: use of undeclared identifier 'src1_cont' The submodule (isHuangXin/llama.cpp) is not ours to modify, so carry the fix as patches/llama.cpp/0001-*.patch and apply it at configure time with cmake/ApplyLlamaPatches.cmake (idempotent; BITNET_APPLY_LLAMA_PATCHES=OFF to disable). The same one-line change is in isHuangXin/llama.cpp#1 and #6. Refs microsoft#618, microsoft#682, microsoft#641 (reproduced on Apple M6 / Apple clang 21.0.0, see #2).
…S backend)
bitnet.cpp's I2_S/TL1 kernels exist only for the CPU backend, but on macOS
ggml also builds the Metal and Accelerate (BLAS) backends, and both
interfere with I2_S models even when the user passes -ngl 0:
* Metal built, BLAS not: creating the llama_context aborts in
ggml_backend_sched_split_graph:
ggml.c:1789: GGML_ASSERT(view_src == NULL || data_size == 0 ||
data_size + view_offs <= ggml_nbytes(view_src)) failed
* BLAS built: ggml_backend_blas_device_supports_op accepts MUL_MAT whose
src0 has a to_float() (I2_S has one) once ne0, ne1 and ne10 are >= 32, so
every prompt batch of >= 32 tokens is routed to ggml_backend_blas_graph_compute
and the process dies with SIGSEGV (backtrace in the evidence log).
Default GGML_METAL and GGML_BLAS to OFF on Apple unless the user sets them.
Measured on an Apple M6 with a synthetic I2_S model: Metal+BLAS (old default)
crashes for >= 31-token batches; Metal OFF + BLAS OFF runs 1..300 tokens at
1 and 4 threads.
Refs microsoft#682 (SIGSEGV during prompt processing), microsoft#641 (Metal
crash on I2_S), #2
3 tasks
Metal+BLAS: SIGSEGV in ggml_backend_blas_graph_compute (>= 31 tokens); Metal only: GGML_ASSERT at context creation; BLAS only: no crash, but I2_S batches >= 32 tokens return garbage (perplexity 4.4e27 vs 3.7e5 on the synthetic model with the corrected kernels). The previous docs text implied a SIGSEGV for every BLAS build. Refs #2
…ling conventions Carries the quants.c hunk of isHuangXin/llama.cpp#6 (Scottcjn:fix-arm-i2s-dotprod) verbatim as patches/llama.cpp/0002. On ARM the pinned submodule used the scalar fallback of ggml_vec_dot_i2_i8_s, which (a) decodes weights sequentially (byte i/4, field i%4) instead of the interleaved 128-weight / 32-byte layout used by dequantize_row_i2_s and the AVX2 kernel, (b) maps codes to -1/0/1 although the callers subtract the activation sum (double correction), and (c) implements only the by==0 calling convention, so ggml_gemm_i2_i8_s (prompt processing, >= 4 columns) leaves most of its output uninitialised (1e25..1e35 and NaN in the L2 test). Measured on Apple M6 (kernel tests of test/i2s-correctness-gate): 3 passed / 55 failed before, 55 passed / 3 failed after (the 3 are quantize_i2_s layout, untouched by this patch). L1 gemv/gemm and L2 mul_mat are exact against an independent reference (rel. error ~1e-7). Refs #3, microsoft#585, microsoft#598, microsoft#600, microsoft#610, microsoft#641
The shared bitnet graph hard-codes LLM_FFN_SILU. BitNet b1.58 2B4T's config.json has hidden_act = relu2. Measured on Apple M6 with the official ggml-model-i2_s.gguf against mlx_lm's bitnet model (float32), on a fixed 199-token text: with the I2_S kernel fixed but SiLU, perplexity is 43.99 vs 7.15 (mean KL 1.08 nats); with relu2 it is 7.1785 vs 7.1532 (+0.35 %, mean KL 0.0026). LIMITATION: selecting on arch == bitnet-b1.58 is not a complete design; 1bitLLM checkpoints convert to the same arch and use SiLU. The config-driven key proposed in isHuangXin/llama.cpp#4 (read hidden_activation from the GGUF) is the proper fix and needs the converter to write it; this patch is the minimal change that makes the official 2B4T GGUF correct. Refs #3, microsoft#588, microsoft#602
eejd
force-pushed
the
fix/i2s-arm-kernel-relu2
branch
from
October 31, 2026 13:52
fd9bc97 to
f3bb8c0
Compare
5 tasks
quantize_i2_s packed sequentially while dequantize_row_i2_s and the kernels read the 128-weight/32-byte interleaved layout; llama-quantize had no I2_S ftype (invalid ftype 'I2_S'). Also: one call per tensor (the scale is per tensor, after the packed data), zeroed 32-byte trailer, validate_row_data accepts I2_S. Refs #3. Kernel tests: 58/58; 14/14 synthetic tensors byte-identical to the canonical packing at default, 1 and 4 threads; gate PASS on the quantized file.
…out tokenizer.ggml.pre (patch 0005) Guarded: arch bitnet-b1.58, 128256 tokens, 280147 merges (merge table identical to Llama-3.2's), no tokenizer.ggml.pre. Any other file keeps the 'missing pre-tokenizer' warning. Gate on the unmodified official GGUF: tokenizer parity 5/5 (was 3/5), GATE PASS without --override-pre. Refs #3.
eejd
marked this pull request as ready for review
November 1, 2026 20:19
This was referenced Nov 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft candidate for #3; stacked on
fix/macos-arm64-build(the first 5 commits are that PR). Only the last five commits are new.ggml-cpu: I2_S NEON kernel for the canonical layout–patches/llama.cpp/0002, thequants.chunk of ggml-cpu: fix ARM i2_s dot product producing constant output isHuangXin/llama.cpp#6 verbatim. The pinned submodule used a scalar fallback on ARM that (a) decodes weights sequentially instead of the 128/32 interleaved layout, (b) maps codes to −1/0/+1 although callers subtract the activation sum, (c) implements only theby==0calling convention so prompt-processing GEMM leaves most outputs unwritten.bitnet-b1.58: squared-ReLU FFN–patches/llama.cpp/0003;hidden_actof 2B4T isrelu2, the graph hard-codes SiLU ([Bug]: BitNet FFN uses SILU instead of ReLU² — perplexity 99.8 vs 17.1 on every CPU backend microsoft/BitNet#588/bitnet-b1.58-2B-4T runs FFN with SiLU instead of relu2 - wrong logits on every backend microsoft/BitNet#602). Selecting on arch is not a complete design (1bitLLM checkpoints share the arch but use SiLU); the config-driven key of feat: support config-based FFN activation for BitNet isHuangXin/llama.cpp#4 is the proper fix.ggml: llama-quantize I2_S emits the canonical layout–patches/llama.cpp/0004.llama-quantize … I2_S(whatsetup_env.pycalls) wasinvalid ftype 'I2_S', andquantize_i2_swrote the sequential layout. Now:I2_Sftype registered; canonical 128/32 layout; one call per tensor (the scale is per tensor, stored after the packed data, so no row chunking/threads); the 28 unused trailer bytes are zeroed;ggml_validate_row_dataaccepts I2_S.llama-vocab: assume llama-bpe for the official bitnet-b1.58 GGUF–patches/llama.cpp/0005. The official GGUF has the Llama-3 BPE vocabulary but notokenizer.ggml.pre, so llama.cpp used the genericdefaultregexes and split e.g.(n):\ndifferently from Llama-3 (def fibonacci(n):\n→ ids7,77,1680,198instead of1471,997). The fallback applies only when arch isbitnet-b1.58, the vocab has 128256 tokens and 280147 merges (merge-table sha identical to Llama-3.2-1B's) and the key is missing; any other file keeps the old warning (checked with a Llama-3.2 GGUF whose key was blanked). The converter already writesllama-bpefor this tokenizer (its check-hash equals Meta-Llama-3's; verified on the HF tokenizer), so only existing files needed this.Results (Apple M6,
-ngl 0, officialggml-model-i2_s.ggufvs mlx_lm float32mlx-community/bitnet-b1.58-2B-4T, gate from thetest/i2s-correctness-gatePR)--override-pre llama-bpe)The capital of France is→Paris. Paris is a city that is known for its rich history, culture,(identical to the reference); 3 of 5 greedy continuations are identical over 16 tokens, the other two diverge at reference near-ties (margins 0.028 and 0.174, threshold 0.464). Same result with 1, 4, 8 threads, and with a build without dotprod/i8mm (mean KL 0.0021, ppl 7.1780).Full run at this head (clean configure + build with the default recipe, patches 0001–0005): kernel tests (linked libggml-cpu) 58 / 58; gate unit tests 24 / 24; synthetic gate PASS; real-model gate PASS with and without the pre-tokenizer override;
llama-quantize … I2_Son the synthetic f32 model: all 14 tensors byte-identical to the canonical packing at default/1/4 threads, gate PASS on the quantized file.Known gaps – not fixed here
llama-quantize … I2_Swas verified on the synthetic model only; no HF master weights of the real model were on disk, so a locally converted 2B4T GGUF has not been run through the gate.quantize_i2_skeeps the BitNet convention (scale = first non-zero |w|), so inputs must already be ternary × scale. Rows must be divisible by 128 (asserted).--madvariants of the kernel test (src/ggml-bitnet-mad.cpp, not compiled by the CMake build) still fail 0 / 29: dead code with the old kernels.tolerances.jsonhistory).Files