microsoft / microsoft/BitNet

[Bug]: i2_s quantization produces garbage output on x86 CPUs without AVX2 (Missing fallback)

Open
#547 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
40.3k
Forks
3.7k
PR merge metrics
No merged PRs in 30d

Description

Description

When running inference using the i2_s quantization format on an x86 CPU that lacks AVX2 support (e.g., Ivy Bridge architecture), the model produces garbage output, specifically an infinite loop of the space token (e.g., GGGGGG...).

This issue does not occur with standard quantizations like Q2_K on the same exact build and hardware, which successfully outputs coherent text at ~8 tokens/s using AVX1.

Environment
  • OS: Void Linux
  • CPU: Intel Core i5-3317U (Ivy Bridge)
  • Instruction Set: AVX (AVX1), SSE4.2 (Strictly NO AVX2, AVX-512, or FMA)
  • Build Flags: cmake .. -DGGML_AVX2=OFF -DGGML_FMA=OFF -DCMAKE_BUILD_TYPE=Release
Steps to Reproduce
  1. Clone the main branch of microsoft/BitNet.
  2. Build the project forcing AVX2 off (simulating/matching older x86 hardware).
  3. Run inference with an i2_s model (e.g., bitnet_b1_58-large or 2B):
    ./build/bin/llama-cli -m ggml-model-i2_s.gguf -p "Hello" -n 50
  4. The system info correctly reports: AVX = 1 | AVX2 = 0 | MATMUL_INT8 = 0.
  5. The output gets stuck in an infinite loop of G (or the equivalent space token for the loaded vocabulary).
Root Cause Analysis

After investigating the codebase, it appears the i2_s format is missing a functional fallback for x86 architectures without AVX2:

  1. In ggml.c, the function ggml_cpu_has_matmul_int8() returns 0 for all non-ARM architectures.
  2. In src/ggml-bitnet-mad.cpp (e.g., around ggml_vec_dot_i2_i8_s_1x1), the i2_s dot product kernels are wrapped entirely in #if defined(__AVX2__).
  3. There is no #elif defined(__AVX__) fallback using 128-bit intrinsics, nor a standard #else scalar fallback that correctly handles the ternary bit-unpacking for x86.
  4. Testing note: Manually injecting a standard C++ scalar fallback loop into ggml-bitnet-mad.cpp resulted in the exact same garbage output (just significantly slower), suggesting the baseline ternary unpacking logic for non-AVX2 x86 paths might be entirely missing or failing silently.
Suggested Fix

To ensure compatibility with legacy x86 hardware (similar to how ggml gracefully handles Q2_K), the i2_s kernels in src/ggml-bitnet-mad.cpp need:

  1. An #elif defined(__AVX__) path utilizing 128-bit intrinsics (similar to the pattern used for Q2_K in ggml-quants.c).
  2. A robust #else scalar fallback to ensure correct bit-unpacking when no SIMD instructions are available.

Thank you for your incredible work on the 1-bit LLM architecture! I'm available to test any potential patches on my hardware if needed.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/ggml-bitnet-mad.cpp at the i2_s dot-product kernels, especially ggml_vec_dot_i2_i8_s_1x1, and compare the AVX2 path with the Q2_K fallback patterns in ggml-quants.c. Reproduce with AVX2 and FMA disabled using the provided llama-cli command and an i2_s model. Done means coherent output on AVX-only x86 while retaining correct AVX2 behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
cmake, cpp
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.