You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
UB in to_float callback for I2_S (missing scale param) + 3 more bugs on ARM Ampere Altra #468
NEON = 1 | ARM_FMA = 1 | SVE = 0 | MATMUL_INT8 = 0 | (all x86 = 0)
Problem
With -b 1 (gemv path): model produces real words but completely incoherent output.
With -b 32+ (gemm path for prompt): model produces GGGG garbage for all generated tokens.
# gemv path (-b 1, default in run_inference.py)
$ python run_inference.py -m models/BitNet-b1.58-2B-4T-gguf/ggml-model-i2_s.gguf \
-p "The capital of France is" -n 30 -t 4
The capital of France is offline officially.ai happy collective lower sl ruled m unit proven-for...
# gemm path (-b 32)
$ ./build/bin/llama-cli -m models/BitNet-b1.58-2B-4T-gguf/ggml-model-i2_s.gguf \
-p "Hello, my name is" -n 50 -t 4 -b 32
Hello, my name isGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
Speed is correct (~18 tok/s on 4 ARM cores), model loads without errors.
Bug 1: to_float callback — type mismatch (confirmed, patched)
In 3rdparty/llama.cpp/ggml/src/ggml.c, the type table for GGML_TYPE_I2_S registers dequantize_row_i2_s as a ggml_to_float_t callback, but the signatures do not match:
// Typedef (3 params):typedefvoid (*ggml_to_float_t)(constvoid*x, float*y, int64_tn);
// Actual function (4 params — 4th is the i2 scale):voiddequantize_row_i2_s(constuint8_t*x, float*y, int64_tn, constfloati2_scale);
// Bug in type table (ggml.c ~line 1185):
.to_float= (ggml_to_float_t) dequantize_row_i2_s, // scale never passed → UB
The cast silences the compiler but causes undefined behavior — on ARM, the 4th argument register is garbage → i2_scale is random → dequantized weights are garbage.
Applied fix — wrapper in the same translation unit:
// At top of ggml.c:staticvoiddequantize_row_i2_s_wrapper(constvoid*vx, float*y, int64_tn) {
constuint8_t*x= (constuint8_t*) vx;
constfloati2_scale= ((constfloat*)(x+ (n / 4)))[0];
dequantize_row_i2_s(x, y, n, i2_scale);
}
// In type table:
.to_float=dequantize_row_i2_s_wrapper,
Effect: without this fix → GGGG for all configs. With fix → real words with -b 1 (but still incoherent, see Bug 3).
Bug 2: +dotprod not enabled in CMakeLists for ARM (build issue)
The build system generates -march=armv8.2-a without +dotprod. On ARM Ampere Altra:
CPU has asimddp (dotprod SDOT/UDOT) in /proc/cpuinfo
Without +dotprod, __ARM_FEATURE_DOTPROD is not defined by clang
ggml-bitnet-mad.cpp falls to the #else path using vmlal_s8 (int16 accumulator)
Suggested fix: add +dotprod to CMakeLists.txt for AArch64 targets when CPU supports it, or document it as a required flag.
Bug 3: gemm path (ne11 > 3) produces GGGG on ARM — root cause unknown
When the batch has more than 3 tokens (prompt evaluation with -b 32), the code takes the ggml_gemm_i2_i8_s path instead of ggml_gemv_i2_i8_s. On ARM, this path produces completely wrong output:
# Relevant code path in ggml.c (~line 13268):
if (gemm && (ne11 > 3)) {
if (src0->type == GGML_TYPE_I2_S) {
// gemm path — produces GGGG on ARM
const float * scale = (float *)((uint8_t*)(src0->data) + (ne00 * ne01 / 4));
gemm(...);
for (...) {
tmp[...] = (tmp[...] - act_sums[col]) / (act_scales[col]) * (*scale);
}
}
}
Since prompt tokens are processed via gemm and corrupt the KV cache, all subsequent generated tokens are also garbage.
Workaround: use -b 1 to force all computation through the gemv path (avoids the bug but is slower and still produces incoherent text).
Bug 4: incoherent output even with gemv path (remaining issue)
Even with -b 1 + Bug 1 fix + dotprod enabled, output is semantically incoherent. The model generates real vocabulary tokens but with no semantic coherence:
"The capital of France is offline officially.ai happy collective..."
"Hello, my name is Freedom reduce Dispatch linear Dahl tent corps..."
This persists even with temperature=0 (greedy decoding), suggesting the issue is in the logit distribution rather than sampling. Possible causes:
act_sums (activation zero-point correction) computed incorrectly for ARM
Scale offset wrong for GQA tensors (K/V: [2560, 640] with 5 KV heads vs Q: [2560, 2560])
Tested exclusively on Oracle Cloud Compute A1 Flex (ARM Ampere Altra).
Reports from Reddit/r/LocalLLaMA indicate M1/M2/M3 Mac users achieve coherent output — suggesting the issue may be ARM Ampere-specific or related to the dotprod/vmlal path difference.
I started working on a fix for Bug 1. Since the change is in the vendored llama.cpp submodule, should I open the PR against this repo or upstream it to llama.cpp first?
Thanks for working on the fix for Bug 1! 🙌
Since it's in the vendored llama.cpp submodule, please open the PR directly against this repo (microsoft/BitNet). We can upstream it to llama.cpp later if needed
Activity
Mindev27 commentedon Mar 15, 2026
I started working on a fix for Bug 1. Since the change is in the vendored llama.cpp submodule, should I open the PR against this repo or upstream it to llama.cpp first?
betovildoza commentedon Mar 15, 2026
Thanks for working on the fix for Bug 1! 🙌
Since it's in the vendored llama.cpp submodule, please open the PR directly against this repo (microsoft/BitNet). We can upstream it to llama.cpp later if needed
fix(i2_s): remove unsafe to_float cast + guard BLAS from I2_S generic…
fix(i2_s): resolve to_float UB + guard BLAS I2_S routing
fix(i2_s): resolve to_float UB + guard BLAS I2_S routing
fix(i2_s): resolve to_float UB + guard BLAS I2_S routing
fix(i2_s): resolve to_float UB + guard BLAS I2_S routing
fix(i2_s): resolve to_float UB + guard BLAS I2_S routing
fix(i2_s): resolve to_float UB + guard BLAS I2_S routing