Skip to content

Commit bc00b82

Browse files
committed
feat: L2-L5 kernels + CMake + 16 tests + CI + fix test import
1 parent 01eb415 commit bc00b82

53 files changed

Lines changed: 11951 additions & 12 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# ─── BitNet CPU kernel CI ──────────────────────────────────────────────────────
2+
#
3+
# Builds the bitnet.cpp project with all L2-L5 math kernels enabled and runs
4+
# the kernel unit test suite. No model download (full smoke/perplexity happens
5+
# locally or in a separate nightly workflow).
6+
#
7+
# Why this exists:
8+
# - Clang ≥ 18 is required for SIMD kernels (per CLAUDE.md).
9+
# - 3rdparty/llama.cpp is a fork (branch `merge-dev`); submodule init is
10+
# critical for the build.
11+
# - GCC 14 may not be installed in the runner image; we explicitly install
12+
# libstdc++-14-dev so Clang 18 can find its system C++ headers.
13+
#
14+
# Trigger: every push to main, every PR.
15+
16+
name: kernel-ci
17+
18+
on:
19+
push:
20+
branches: [main]
21+
pull_request:
22+
branches: [main]
23+
workflow_dispatch:
24+
25+
jobs:
26+
build-and-test:
27+
name: build + test (Ubuntu, clang-18)
28+
runs-on: ubuntu-24.04
29+
timeout-minutes: 30
30+
31+
steps:
32+
- name: Checkout (with submodules)
33+
uses: actions/checkout@v4
34+
with:
35+
submodules: recursive
36+
fetch-depth: 1
37+
38+
- name: Apply dispatch patch (combined 05)
39+
run: |
40+
echo "Applying combined patch 05 (L3 ACDC + L5 HRR + L4 K_i8 cache + FaseIII rect + LLaMA gate)..."
41+
chmod +x ./scripts/apply-dispatch-patches.sh
42+
./scripts/apply-dispatch-patches.sh
43+
echo "Verifying idempotence..."
44+
./scripts/apply-dispatch-patches.sh --check
45+
shell: bash
46+
47+
- name: Install build dependencies
48+
run: |
49+
sudo apt-get update
50+
sudo apt-get install -y \
51+
clang-18 \
52+
cmake \
53+
ninja-build \
54+
libstdc++-14-dev \
55+
python3 \
56+
python3-pip \
57+
python3-venv
58+
59+
- name: Create Python venv and install test dependencies
60+
# Use an isolated venv to avoid PEP-668 conflicts between apt numpy/scipy
61+
# and PyPI packages (safetensors has no numpy dep; still isolate for safety).
62+
run: |
63+
python3 -m venv .venv
64+
.venv/bin/pip install --no-cache-dir numpy scipy safetensors
65+
66+
- name: Configure (Release, all kernels + ACDC_RECT)
67+
# BITNET_ENABLE_ACDC_RECT defaults ON → 16 tests in CI.
68+
# Python3_EXECUTABLE points to the venv so test_extract_acdc_diagonal
69+
# finds the installed numpy/safetensors.
70+
run: |
71+
cmake -B build -G Ninja \
72+
-DCMAKE_C_COMPILER=clang-18 \
73+
-DCMAKE_CXX_COMPILER=clang++-18 \
74+
-DCMAKE_BUILD_TYPE=Release \
75+
-DBITNET_L2_WHT=ON \
76+
-DBITNET_L3_ACDC=ON \
77+
-DBITNET_L4_TROPICAL=ON \
78+
-DBITNET_L5_HRR=ON \
79+
-DBITNET_L6_RAG=ON \
80+
-DBITNET_BUILD_TESTS=ON \
81+
-DPython3_EXECUTABLE=$(pwd)/.venv/bin/python3
82+
83+
- name: Build (compiles L1 + L2-L6 + all test targets)
84+
# Single build step — cmake discovers all targets from CMakeLists.txt.
85+
# No hardcoded --target list: avoids breakage when targets are added/renamed.
86+
run: cmake --build build --config Release -j$(nproc)
87+
88+
- name: ctest — 16/16 kernel unit tests
89+
# BITNET_ENABLE_ACDC_RECT=ON (default) adds test_acdc_rect → 16 tests.
90+
# -j$(nproc): parallel execution; --output-on-failure: full log on fail.
91+
# PYTHON3_EXECUTABLE env var ensures the venv Python is used for
92+
# test_extract_acdc_diagonal (the add_test() COMMAND is cmake-resolved).
93+
run: |
94+
ctest --test-dir build \
95+
--output-on-failure \
96+
-j$(nproc) \
97+
--timeout 120
98+
99+
- name: NO-06 — telemetry audit (zero hits required)
100+
# Persona D4: binário nunca envia dados a endpoints externos.
101+
# Any match = CI failure.
102+
run: |
103+
HITS=$(grep -rn \
104+
"telemetry\|upload_data\|send_metrics\|POST.*http" \
105+
src/ utils/ run_inference*.py setup_env.py 2>/dev/null | \
106+
grep -v "^Binary\|\.pyc" || true)
107+
if [ -n "$HITS" ]; then
108+
echo "::error::NO-06 FAIL — telemetry code found:"
109+
echo "$HITS"
110+
exit 1
111+
fi
112+
echo "NO-06 PASS — 0 telemetry hits"
113+
114+
- name: NO-07 — cloud URL audit (zero hits in production code)
115+
# Ensures no hard-coded HTTP endpoints in C/C++ production sources.
116+
# URLs in comments (// http) and docs are excluded.
117+
run: |
118+
HITS=$(grep -rn "http://\|https://" \
119+
src/ include/ \
120+
--include="*.cpp" --include="*.h" | \
121+
grep -v "//.*http\|/\*.*http\| \* http" || true)
122+
if [ -n "$HITS" ]; then
123+
echo "::error::NO-07 FAIL — cloud URLs in production code:"
124+
echo "$HITS"
125+
exit 1
126+
fi
127+
echo "NO-07 PASS — 0 cloud URL hits"
128+
129+
- name: Cross-validation C ↔ Python (L3/L4/L5)
130+
# Verifies that the Python reference implementations match the C kernels
131+
# to rtol=1e-5, atol=1e-7. No model required.
132+
# --build-dir points to the cmake output dir (build/tests/), not the
133+
# local development build (build_tests/).
134+
run: |
135+
.venv/bin/python3 tests/cross_validation.py \
136+
--all \
137+
--build-dir build/tests
138+
echo "Cross-validation: PASS"
139+
140+
- name: Air-gapped boot test (AC-11)
141+
# Verifies that the built llama-cli binary runs without making any
142+
# network syscalls. This enforces persona D4 (no telemetry, no cloud)
143+
# at the CI level. The script is in tests/test_air_gapped_boot.sh;
144+
# it auto-skips if no model file is provided (which is the case in CI).
145+
# Result: SKIPPED is acceptable in CI; PASS requires a real model.
146+
run: |
147+
chmod +x tests/test_air_gapped_boot.sh
148+
bash tests/test_air_gapped_boot.sh 2>&1 | tee /tmp/air_gapped.log
149+
rc=${PIPESTATUS[0]}
150+
if [ $rc -ne 0 ]; then
151+
echo "::error::AC-11 air-gapped boot FAILED (rc=$rc)"
152+
cat /tmp/air_gapped.log
153+
exit $rc
154+
fi

.gitmodules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
path = 3rdparty/llama.cpp
33
url = https://github.com/Eddie-Wang1120/llama.cpp.git
44
branch = merge-dev
5+
ignore = dirty

CMakeLists.txt

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,22 @@ endif()
1111

1212
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
1313

14-
# option list
15-
option(BITNET_ARM_TL1 "bitnet.cpp: use tl1 on arm platform" OFF)
16-
option(BITNET_X86_TL2 "bitnet.cpp: use tl2 on x86 platform" OFF)
17-
14+
# ─── Level 1: kernel format ──────────────────────────────────────────────────
15+
option(BITNET_ARM_TL1 "bitnet.cpp: use TL1 lookup-table kernel (ARM64)" OFF)
16+
option(BITNET_X86_TL2 "bitnet.cpp: use TL2 lookup-table kernel (x86_64)" OFF)
17+
18+
# ─── Level 2-5: math research kernels ────────────────────────────────────────
19+
option(BITNET_L2_WHT "bitnet.cpp: WHT zero-mul GEMV (Level 2)" ON)
20+
option(BITNET_L3_ACDC "bitnet.cpp: FWHT+ACDC O(n log n) layers (Level 3)" ON)
21+
option(BITNET_L4_TROPICAL "bitnet.cpp: Tropical attention (max,+) (Level 4)" ON)
22+
option(BITNET_L5_HRR "bitnet.cpp: Holographic memory HRR (Level 5)" ON)
23+
option(BITNET_L6_RAG "bitnet.cpp: CPU-RAG flat-index ANN engine (Level 6)" ON)
24+
option(BITNET_RAG_SHARED "bitnet.cpp: build bitnet_rag as a shared lib (ctypes)" OFF)
25+
option(BITNET_BUILD_TESTS "bitnet.cpp: build kernel unit tests" ON)
26+
# FWHT parallel (OpenMP): opt-in. Default OFF so the ggml inference path (which
27+
# runs inside a ggml thread-pool callback) is never affected. Enable only for
28+
# standalone benchmarks / extraction tools that run outside ggml.
29+
option(BITNET_FWHT_OMP "bitnet.cpp: OpenMP-parallel fwht_f32_parallel() (benchmark use)" OFF)
1830

1931
set(CMAKE_CXX_STANDARD_REQUIRED true)
2032
set(CMAKE_C_STANDARD 11)
@@ -38,10 +50,33 @@ endif()
3850

3951
find_package(Threads REQUIRED)
4052

53+
# ─── src/ ─────────────────────────────────────────────────────────────────────
54+
# Compiles L2-L5 into the bitnet_math OBJECT library.
55+
# Sets BITNET_MATH_TARGET in this scope (empty string if no levels enabled).
4156
add_subdirectory(src)
57+
58+
# ─── 3rdparty/llama.cpp ───────────────────────────────────────────────────────
59+
# Defines the ggml target (which already contains L1 kernels via hardcoded paths).
4260
set(LLAMA_BUILD_SERVER ON CACHE BOOL "Build llama.cpp server" FORCE)
4361
add_subdirectory(3rdparty/llama.cpp)
4462

63+
# ─── Wire L2-L5 into ggml ────────────────────────────────────────────────────
64+
# After both subdirectories are processed, both `bitnet_math` and `ggml` exist.
65+
# We add the OBJECT library to ggml so L2-L5 symbols are available in all
66+
# llama.cpp binaries (llama-cli, llama-server, llama-bench, etc.)
67+
# without any extra linker flags on the caller side.
68+
if (BITNET_MATH_TARGET)
69+
target_link_libraries(ggml PUBLIC ${BITNET_MATH_TARGET})
70+
message(STATUS "BitNet: L2-L5 kernels linked into ggml target")
71+
endif()
72+
73+
# ─── Tests ────────────────────────────────────────────────────────────────────
74+
# Standalone unit tests for L2-L5 kernels. Add -DBITNET_BUILD_TESTS=OFF to skip.
75+
if (BITNET_BUILD_TESTS)
76+
enable_testing()
77+
add_subdirectory(tests)
78+
endif()
79+
4580
# install
4681

4782
include(GNUInstallDirs)

include/bitnet-lut-kernels.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* bitnet-lut-kernels.h — Lookup-table GEMM kernel stubs
3+
*
4+
* This file is normally generated by:
5+
* python utils/codegen_tl1.py (ARM64 TL1 kernels)
6+
* python utils/codegen_tl2.py (x86_64 TL2 kernels)
7+
*
8+
* Or automatically via:
9+
* python setup_env.py -md <model_dir> -q tl1
10+
* python setup_env.py -md <model_dir> -q tl2
11+
*
12+
* This stub allows cmake to configure and build with I2_S kernels (default)
13+
* without running codegen first. TL1/TL2 functionality is disabled when
14+
* neither GGML_BITNET_ARM_TL1 nor GGML_BITNET_X86_TL2 is defined.
15+
*/
16+
17+
#pragma once
18+
19+
#if defined(GGML_BITNET_ARM_TL1)
20+
#error "TL1 kernels not generated yet. Run: python utils/codegen_tl1.py"
21+
#endif
22+
23+
#if defined(GGML_BITNET_X86_TL2)
24+
#error "TL2 kernels not generated yet. Run: python utils/codegen_tl2.py"
25+
#endif

include/ggml-bitnet-common.h

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* ggml-bitnet-common.h — Shared utilities across L2-L5 math kernels
3+
*
4+
* ─────────────────────────────────────────────────────────────────────────
5+
* WHY THIS HEADER IS SMALL
6+
* ─────────────────────────────────────────────────────────────────────────
7+
*
8+
* The natural impulse when seeing three "butterfly" implementations
9+
* (L2 WHT, L3 FWHT, L5 FFT) is to extract a shared `butterfly_step()`
10+
* abstraction. After actually reading all three, that abstraction is
11+
* *not* a clean win — see the taxonomy below.
12+
*
13+
* The only piece that genuinely duplicates across kernels is the
14+
* "smallest power of 2 ≥ n" rounding utility (needed by L3 FWHT and
15+
* L5 FFT to pad their input vectors to a power of 2). Extracting
16+
* that, plus a few other small bits, is the right scope for a
17+
* "shared common" header. The butterfly operations themselves stay
18+
* per-kernel for clarity and to allow per-algorithm SIMD tricks
19+
* (e.g. L3 processes 8 float32 pairs at once in pure AVX2 add/sub;
20+
* L5 needs twiddle multiplications and complex number handling).
21+
*
22+
* ─────────────────────────────────────────────────────────────────────────
23+
* ALGORITHM TAXONOMY (L2 / L3 / L5)
24+
* ─────────────────────────────────────────────────────────────────────────
25+
*
26+
* L2 WHT (src/ggml-bitnet-wht.cpp)
27+
* Algorithm: selection-mask dot product on I2_S packed bytes.
28+
* NOT a Cooley-Tukey butterfly. The "Hadamard domain"
29+
* trick is: H·x with H ∈ {±1} is computed via
30+
* `(w==+1 ? x : 0) − (w==−1 ? x : 0)` per element, with
31+
* 32-wide AVX2 compare/select on packed bytes.
32+
* Zero muls, no bit-reversal, in-place.
33+
*
34+
* L3 FWHT (src/ggml-bitnet-fwht.cpp)
35+
* Algorithm: in-order Cooley-Tukey radix-2 butterfly, real-valued.
36+
* Twiddles are always ±1 (Hadamard matrix), so the inner operation
37+
* is pure (a+b, a-b) — no multiplications.
38+
* In-order (no bit-reversal — only the DIF variant of FFT
39+
* needs it; L3 uses a DIT-like structure because the input
40+
* order is the natural one for the final-form H matrix).
41+
* Variants: f32 and i32, scalar + AVX2 + NEON.
42+
*
43+
* L5 FFT (src/ggml-bitnet-hrr.cpp)
44+
* Algorithm: Cooley-Tukey radix-2 DIF, complex-valued, with
45+
* twiddle factors exp(−2πi·k/N). Bit-reversal permutation on
46+
* input (Decimation In Frequency requires input in bit-reversed
47+
* order for the output to be in natural order).
48+
* Twiddles require complex multiplications (4 mults + 2 adds
49+
* per butterfly, or 3 mults + 3 adds with the standard trick).
50+
* The first log₂(N) stages have twiddles in {±1, ±i} and could
51+
* avoid multiplications, but we don't bother (FMAs are cheap).
52+
*
53+
* Conclusion: there is no common butterfly() to share. L2 is
54+
* fundamentally different (selection mask, not butterfly), and L3/L5
55+
* differ on twiddle handling, value type (real vs complex), and
56+
* permutation (in-order vs bit-reversed). Forcing a shared API
57+
* would obscure the math more than it would simplify the code.
58+
*
59+
* ─────────────────────────────────────────────────────────────────────────
60+
* WHAT IS SHARED
61+
* ─────────────────────────────────────────────────────────────────────────
62+
*
63+
* - bitnet_next_pow2: smallest power of 2 ≥ n (used by L3, L5 to pad)
64+
* - BITNET_L* build-flag summary (re-exported here for convenience)
65+
* - The taxonomy comment above (so future agents don't make the
66+
* same "let's extract a butterfly" mistake)
67+
*/
68+
69+
#pragma once
70+
71+
#include <stdint.h>
72+
73+
#ifdef __cplusplus
74+
extern "C" {
75+
#endif
76+
77+
/* ── bitnet_next_pow2 ────────────────────────────────────────────────────
78+
*
79+
* Returns the smallest power of 2 that is ≥ n. For n ≤ 1, returns 1.
80+
*
81+
* Used by:
82+
* - L3 FWHT (src/ggml-bitnet-fwht.cpp): pads activation vectors
83+
* to power-of-2 length before applying the butterfly.
84+
* - L5 FFT (src/ggml-bitnet-hrr.cpp): pads HRR vectors to power-of-2
85+
* length for the radix-2 Cooley-Tukey FFT.
86+
*
87+
* L2 WHT does NOT use this (operates on fixed QK block size).
88+
* L4 tropical does NOT use this (operates per-token, not on fixed FFT blocks).
89+
*/
90+
int bitnet_next_pow2(int n);
91+
92+
#ifdef __cplusplus
93+
}
94+
#endif

0 commit comments

Comments
 (0)