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