Performance optimising `ggml_vec_dot_i2_i8_s` with AVX-512 VNNI
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 40.3k
- Forks
- 3.7k
- PR merge metrics
- No merged PRs in 30d
Description
While profiling BitNet inference (single-threaded run_inference), I observed that the function ggml_vec_dot_i2_i8_s (which performs a multiply-accumulate on 1.58-bit (ternary) and 8-bit data) dominates the runtime (I dont recall precisely, but it was about 80% of total CPU time).
I rewrote ggml_vec_dot_i2_i8_s using AVX-512 VNNI intrinsics, collapsing the original 85-line implementation into one short loop. AVX-512 VNNI (Vector Neural Network Instructions) provides specialised dot-product instructions for integer data (e.g. VPDPBUSD for 8-bit multiplies).
inline float ggml_vec_dot_i2_i8_s_avx512_vnni(int n, const uint8_t* x, const int8_t* y) {
const int number_of_blocks = n / 128;
const __m512i mask = _mm512_set1_epi8(0x03);
__m512i accu_1 = _mm512_setzero_si512();
__m512i accu_2 = _mm512_setzero_si512();
for (int j = 0; j < number_of_blocks; ++j) {
const __m256i xq = _mm256_loadu_si256((const __m256i*)(x + (j * 32)));
const __m512i xqa = _mm512_inserti64x4(_mm512_castsi256_si512(_mm256_srli_epi16(xq, 2)), xq, 1);
const __m512i x0 = _mm512_and_si512(_mm512_srli_epi16(xqa, 4), mask);
const __m512i x2 = _mm512_and_si512(xqa, mask);
const __m512i y0 = _mm512_loadu_si512((const __m512i*)(y + j * 128 + (0 * 64)));
const __m512i y2 = _mm512_loadu_si512((const __m512i*)(y + j * 128 + (1 * 64)));
accu_1 = _mm512_dpbusd_epi32(accu_1, x0, y0);
accu_2 = _mm512_dpbusd_epi32(accu_2, x2, y2);
}
return static_cast<float>(hsum_i32_16(_mm512_add_epi32(accu_1, accu_2)));
}
In did some naive tests of run_inference.py, this rewrite yielded roughly a 40% speedup (same prompt and settings).
Surprisingly, the standard end-to-end benchmark script (e2e_benchmark.py) shows no performance improvement after the changes. For example, running:
python utils/e2e_benchmark.py -m /path/to/model.gguf -n 200 -p 256 -t 1
This suggests the bottleneck in these benchmarks might lie elsewhere, such as, thread synchronisation, spinlock contention or other overhead could be masking the gains.
Questions:
-
Has anyone else experimented with rewriting
ggml_vec_dot_i2_i8_susing newer AVX instructions in BitNet? What speedups did you observe? -
Does anyone have insight into why
e2e_benchmarkmight not reflect these low-level optimisations? Could the threading model, locking or other overhead in BitNet be limiting throughput in the end-to-end benchmark?
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ggml_vec_dot_i2_i8_s in src/ggml-bitnet-mad.cpp and compare its current implementation with the AVX-512 VNNI version linked in the issue. Run run_inference.py and utils/e2e_benchmark.py using the stated prompt, model, and thread settings. Done means explaining whether the low-level change improves end-to-end performance and identifying any benchmark overhead that masks it.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- performance
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100