File size: 5,886 Bytes
a98f915 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | // satu1_avx512.cpp β IKNN-Rl1-A1 β SatU1 AVX-512 Kernel (Native VPOPCNTDQ)
// Version: v1.0
// Created: 2026-09-03T16:55:00+07:00
// Last Updated: 2026-09-03T16:55:00+07:00
// Status: PUBLISHABLE β EN ONLY β M1 Kernel Validation
// Repo: IKNN-Rl1-A1 β Integrated Knowledge-phase Neural Network β Recursive Language Iteration 1 β Architecture 1
// Hardware Target: Intel Xeon / Sapphire Rapids β AVX512F, BW, VL, VBMI2, VNNI, BITALG, VPOPCNTDQ (this sandbox)
// β 2 vCPU, 54MB L3, DDR 50-90GB/s, 8GB swap at /home/user/.cache/swap-8gb (excluded)
// Description: SatU1 1-bit XNOR+POPCOUNT, 512 connections per cycle per lane, 4 cycles per 512-bit
// Canonical mapping: bit 0 = -1, bit 1 = +1, XNOR = NOT(A XOR B), S = popcount(XNOR), Y = (2S-K)*alpha
// DRSS: direct-register streaming, no intermediate buffer
// RoPE handling: RoPE applied on FP16 before Sign (see 5.4 step 3a)
#include "satu1_common.h"
#include <cstdint>
#include <immintrin.h>
namespace iknn {
namespace satu1 {
namespace avx512 {
// AVX-512 native kernel β 8x uint64_t = 512 bits = 512 weights per iteration
// Requires: AVX512F + AVX512BW + AVX512_VPOPCNTDQ + AVX512_BITALG (for _mm512_xnor? we emulate via xor+not)
inline float compute_satu1_block_avx512(
const uint64_t* __restrict__ activation_bits,
const uint64_t* __restrict__ weight_bits,
float alpha,
int block_size_words) // block_size_words = number of 64-bit words, must be multiple of 8 for 512-bit
{
// Accumulate popcount in 64-bit lanes
__m512i total_pop = _mm512_setzero_si512();
int i = 0;
// Process 8 words = 512 bits per iteration
for (; i + 7 < block_size_words; i += 8) {
__m512i a = _mm512_loadu_si512(reinterpret_cast<const __m512i*>(&activation_bits[i]));
__m512i w = _mm512_loadu_si512(reinterpret_cast<const __m512i*>(&weight_bits[i]));
// XNOR = NOT(A XOR B)
__m512i x = _mm512_xor_si512(a, w);
__m512i xnor = _mm512_xor_si512(x, _mm512_set1_epi64(-1)); // NOT via XOR with all 1s (BITALG: _mm512_xnor would be better if available)
// Native popcount per 64-bit lane β VPOPCNTDQ β 1 cycle
__m512i cnt = _mm512_popcnt_epi64(xnor);
total_pop = _mm512_add_epi64(total_pop, cnt);
}
// Tail handling for remaining words (<8)
int32_t tail_pop = 0;
for (; i < block_size_words; ++i) {
uint64_t xnor = ~(activation_bits[i] ^ weight_bits[i]);
tail_pop += __builtin_popcountll(xnor);
}
// Horizontal sum of 8x 64-bit lanes
// _mm512_reduce_add_epi64 requires AVX-512, use manual extract
alignas(64) uint64_t pop_array[8];
_mm512_store_si512(reinterpret_cast<__m512i*>(pop_array), total_pop);
int64_t total = tail_pop;
for (int j = 0; j < 8; ++j) total += pop_array[j];
int64_t signed_sum = (2 * total) - (static_cast<int64_t>(block_size_words) * 64);
return static_cast<float>(signed_sum) * alpha;
}
// Variant for 512-bit block (8 words) β most efficient, 1 ZMM = 512 weights
inline float compute_satu1_zmm512(
const __m512i& activation_zmm, // 512 bits = 8x64 bits packed signs
const __m512i& weight_zmm,
float alpha)
{
__m512i x = _mm512_xor_si512(activation_zmm, weight_zmm);
__m512i xnor = _mm512_xor_si512(x, _mm512_set1_epi64(-1));
__m512i cnt = _mm512_popcnt_epi64(xnor);
// reduce
int64_t total = _mm512_reduce_add_epi64(cnt);
int64_t signed_sum = (2 * total) - 512;
return static_cast<float>(signed_sum) * alpha;
}
} // namespace avx512
} // namespace satu1
} // namespace iknn
// Standalone test harness (when compiled as executable)
#ifdef SATU1_AVX512_TEST
#include <random>
#include <iostream>
#include <chrono>
#include <cmath>
int main() {
using namespace iknn::satu1;
using namespace iknn::satu1::avx512;
std::cout << "[SatU1 AVX-512 Test] " << __DATE__ << " " << __TIME__ << std::endl;
std::cout << "Hardware: Xeon AVX-512 VPOPCNTDQ, VBMI2" << std::endl;
// Correctness test
std::mt19937_64 rng(42);
const int WORDS = 8; // 512 bits
uint64_t act[WORDS], w[WORDS];
for (int i = 0; i < WORDS; ++i) {
act[i] = rng();
w[i] = rng();
}
float alpha = 0.5f;
float naive = compute_satu1_block_naive(act, w, alpha, WORDS);
float avx512_res = compute_satu1_block_avx512(act, w, alpha, WORDS);
std::cout << "Naive: " << naive << " AVX-512: " << avx512_res << std::endl;
if (std::abs(naive - avx512_res) < 1e-3f) {
std::cout << "[PASS] Correctness" << std::endl;
} else {
std::cout << "[FAIL] Correctness mismatch!" << std::endl;
return 1;
}
// Throughput benchmark
const int ITERS = 1000000;
auto start = std::chrono::high_resolution_clock::now();
float sum = 0;
for (int it = 0; it < ITERS; ++it) {
// Vary data to prevent optimization
act[0] ^= it;
sum += compute_satu1_block_avx512(act, w, alpha, WORDS);
}
auto end = std::chrono::high_resolution_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
double giga_pop = (double)ITERS * WORDS * 64 / 1e9;
double sec = ms / 1000.0;
double giga_per_sec = giga_pop / sec;
double tps_equiv = (double)ITERS / sec; // tokens per second if 1 block = 1 token (simplified)
std::cout << "[BENCH] Iters: " << ITERS << " Time: " << ms << " ms" << std::endl;
std::cout << "[BENCH] Giga-popcnt/s: " << giga_per_sec << std::endl;
std::cout << "[BENCH] TPS equiv (512 weights): " << tps_equiv << std::endl;
std::cout << "[BENCH] Sum (prevent opt): " << sum << std::endl;
// Expected: On Xeon 2.6GHz with VPOPCNTDQ, ~4 cycles per 512 bits => ~0.65e9 popcnt per core per GHz? Rough.
// Realistic target: >2 Giga-popcnt/s per core
return 0;
}
#endif
|