Recovered source transcript
1/**
2 * @file compact.hpp
3 * @brief Memory-Efficient Representations for Gate5000
4 *
5 * ═══════════════════════════════════════════════════════════════════════════════
6 * I. QUANTIZATION
7 * ═══════════════════════════════════════════════════════════════════════════════
8 *
9 * INT8 QUANTIZATION:
10 * q = round((x - zero_point) / scale)
11 * x = q * scale + zero_point
12 *
13 * INT4 QUANTIZATION:
14 * 4-bit weights packed 2 per byte
15 * 16 levels: [-7, 7] or [0, 15]
16 *
17 * BINARY QUANTIZATION:
18 * w ∈ {-1, +1}
19 * sign(w) stored as single bit
20 *
21 * ═══════════════════════════════════════════════════════════════════════════════
22 * II. SPARSE REPRESENTATIONS
23 * ═══════════════════════════════════════════════════════════════════════════════
24 *
25 * CSR FORMAT:
26 * Compressed Sparse Row for matrices
27 *
28 * TOP-K SPARSITY:
29 * Only store k largest values
30 *
31 * BLOOM FILTERS:
32 * Probabilistic membership testing
33 *
34 * ═══════════════════════════════════════════════════════════════════════════════
35 * III. COMPRESSION
36 * ═══════════════════════════════════════════════════════════════════════════════
37 *
38 * DELTA ENCODING:
39 * Store differences from previous value
40 *
41 * RUN-LENGTH ENCODING:
42 * Compress repeated values
43 *
44 * HUFFMAN-LIKE:
45 * Variable-length coding for frequent values
46 *
47 * ═══════════════════════════════════════════════════════════════════════════════
48 */
49
50#pragma once
51
52#include <cstdint>
53#include <array>
54#include <algorithm>
55#include <cstring>
56#include "constants.hpp"
57#include "fast_math.hpp"
58
59namespace mind_farm {
60namespace compact {
61
62// ═══════════════════════════════════════════════════════════════════════════════
63// I. QUANTIZATION
64// ═══════════════════════════════════════════════════════════════════════════════
65
66/**
67 * @brief INT8 quantization parameters
68 */
69struct QuantParams {
70 float scale;
71 float zero_point;
72 float inv_scale;
73
74 QuantParams() : scale(1.0f / 127.0f), zero_point(0.0f), inv_scale(127.0f) {}
75
76 QuantParams(float min_val, float max_val) {
77 scale = (max_val - min_val) / 254.0f;
78 zero_point = min_val;
79 inv_scale = fast::fast_rcp(scale);
80 }
81};
82
83/**
84 * @brief Quantize float to int8
85 */
86inline int8_t quantize_int8(float x, const QuantParams& p) {
87 float scaled = (x - p.zero_point) * p.inv_scale;
88 int val = static_cast<int>(scaled + 0.5f);
89 return static_cast<int8_t>(std::max(-127, std::min(127, val)));
90}
91
92/**
93 * @brief Dequantize int8 to float
94 */
95inline float dequantize_int8(int8_t q, const QuantParams& p) {
96 return q * p.scale + p.zero_point;
97}
98
99/**
100 * @brief INT8 quantized vector (16 elements, 16 bytes)
101 */
102struct QuantVec16 {
103 alignas(16) std::array<int8_t, 16> data;
104 QuantParams params;
105
106 void quantize(const float* input) {
107 // Find range
108 float min_val = input[0], max_val = input[0];
109 for (int i = 1; i < 16; ++i) {
110 min_val = std::min(min_val, input[i]);
111 max_val = std::max(max_val, input[i]);
112 }
113 params = QuantParams(min_val, max_val);
114
115 // Quantize
116 for (int i = 0; i < 16; ++i) {
117 data[i] = quantize_int8(input[i], params);
118 }
119 }
120
121 void dequantize(float* output) const {
122 for (int i = 0; i < 16; ++i) {
123 output[i] = dequantize_int8(data[i], params);
124 }
125 }
126
127 /**
128 * @brief Fast dot product (stays in int domain)
129 */
130 int32_t dot_int(const QuantVec16& other) const {
131 int32_t sum = 0;
132 for (int i = 0; i < 16; ++i) {
133 sum += static_cast<int32_t>(data[i]) * static_cast<int32_t>(other.data[i]);
134 }
135 return sum;
136 }
137
138 /**
139 * @brief Dot product with dequantization
140 */
141 float dot(const QuantVec16& other) const {
142 int32_t isum = dot_int(other);
143 return isum * params.scale * other.params.scale;
144 }
145};
146
147/**
148 * @brief INT4 quantized vector (16 elements, 8 bytes!)
149 */
150struct QuantVec16_Int4 {
151 alignas(8) std::array<uint8_t, 8> data; // 2 values per byte
152 float scale;
153 float zero_point;
154
155 void quantize(const float* input) {
156 // Find range
157 float min_val = input[0], max_val = input[0];
158 for (int i = 1; i < 16; ++i) {
159 min_val = std::min(min_val, input[i]);
160 max_val = std::max(max_val, input[i]);
161 }
162 scale = (max_val - min_val) / 15.0f;
163 zero_point = min_val;
164 float inv_scale = fast::fast_rcp(scale + 1e-10f);
165
166 // Pack 2 values per byte
167 for (int i = 0; i < 8; ++i) {
168 int q0 = static_cast<int>((input[2*i] - zero_point) * inv_scale + 0.5f);
169 int q1 = static_cast<int>((input[2*i+1] - zero_point) * inv_scale + 0.5f);
170 q0 = std::max(0, std::min(15, q0));
171 q1 = std::max(0, std::min(15, q1));
172 data[i] = static_cast<uint8_t>((q0 << 4) | q1);
173 }
174 }
175
176 void dequantize(float* output) const {
177 for (int i = 0; i < 8; ++i) {
178 output[2*i] = ((data[i] >> 4) & 0xF) * scale + zero_point;
179 output[2*i+1] = (data[i] & 0xF) * scale + zero_point;
180 }
181 }
182
183 // Size: 8 bytes data + 8 bytes params = 16 bytes total vs 64 bytes for float[16]
184};
185
186/**
187 * @brief Binary quantized vector (16 elements, 2 bytes!)
188 */
189struct BinaryVec16 {
190 uint16_t signs; // Sign bits
191 float magnitude; // Shared magnitude
192
193 void quantize(const float* input) {
194 signs = 0;
195 magnitude = 0.0f;
196
197 for (int i = 0; i < 16; ++i) {
198 magnitude += fast::fast_abs(input[i]);
199 if (input[i] >= 0) {
200 signs |= (1u << i);
201 }
202 }
203 magnitude /= 16.0f;
204 }
205
206 void dequantize(float* output) const {
207 for (int i = 0; i < 16; ++i) {
208 output[i] = (signs & (1u << i)) ? magnitude : -magnitude;
209 }
210 }
211
212 /**
213 * @brief Ultra-fast binary dot product (popcount)
214 */
215 float dot(const BinaryVec16& other) const {
216 uint16_t xnor = ~(signs ^ other.signs); // XNOR = same sign
217 int same = __builtin_popcount(xnor);
218 int diff = 16 - same;
219 return magnitude * other.magnitude * (same - diff);
220 }
221
222 // Size: 2 bytes + 4 bytes = 6 bytes vs 64 bytes for float[16]!
223};
224
225// ═══════════════════════════════════════════════════════════════════════════════
226// II. SPARSE REPRESENTATIONS
227// ═══════════════════════════════════════════════════════════════════════════════
228
229/**
230 * @brief Sparse vector with top-k storage
231 */
232template<int K = 4>
233struct SparseVec16 {
234 std::array<uint8_t, K> indices;
235 std::array<float, K> values;
236
237 void from_dense(const float* input) {
238 // Find top-k by magnitude
239 std::array<std::pair<float, int>, 16> pairs;
240 for (int i = 0; i < 16; ++i) {
241 pairs[i] = {fast::fast_abs(input[i]), i};
242 }
243 std::partial_sort(pairs.begin(), pairs.begin() + K, pairs.end(),
244 [](auto& a, auto& b) { return a.first > b.first; });
245
246 for (int i = 0; i < K; ++i) {
247 indices[i] = static_cast<uint8_t>(pairs[i].second);
248 values[i] = input[pairs[i].second];
249 }
250 }
251
252 void to_dense(float* output) const {
253 for (int i = 0; i < 16; ++i) {
254 output[i] = 0.0f;
255 }
256 for (int i = 0; i < K; ++i) {
257 output[indices[i]] = values[i];
258 }
259 }
260
261 float dot(const SparseVec16<K>& other) const {
262 float sum = 0.0f;
263 for (int i = 0; i < K; ++i) {
264 for (int j = 0; j < K; ++j) {
265 if (indices[i] == other.indices[j]) {
266 sum += values[i] * other.values[j];
267 }
268 }
269 }
270 return sum;
271 }
272
273 // Size: K bytes + K*4 bytes = 5K bytes (K=4: 20 bytes vs 64)
274};
275
276/**
277 * @brief Bloom filter for set membership (64-bit, ~4% false positive)
278 */
279struct BloomFilter64 {
280 uint64_t bits;
281 static constexpr int K = 4; // Number of hash functions
282
283 BloomFilter64() : bits(0) {}
284
285 uint64_t hash(uint32_t x, int seed) const {
286 x ^= seed * 0x9E3779B9;
287 x = ((x >> 16) ^ x) * 0x45d9f3b;
288 x = ((x >> 16) ^ x) * 0x45d9f3b;
289 return (x >> 16) ^ x;
290 }
291
292 void add(uint32_t item) {
293 for (int i = 0; i < K; ++i) {
294 bits |= (1ULL << (hash(item, i) & 63));
295 }
296 }
297
298 bool maybe_contains(uint32_t item) const {
299 for (int i = 0; i < K; ++i) {
300 if (!(bits & (1ULL << (hash(item, i) & 63)))) {
301 return false;
302 }
303 }
304 return true;
305 }
306
307 void clear() { bits = 0; }
308 int count() const { return __builtin_popcountll(bits); }
309};
310
311// ═══════════════════════════════════════════════════════════════════════════════
312// III. COMPRESSION
313// ═══════════════════════════════════════════════════════════════════════════════
314
315/**
316 * @brief Delta-encoded vector
317 */
318struct DeltaVec16 {
319 float base;
320 std::array<int8_t, 15> deltas; // 15 deltas (first value is base)
321 float scale;
322
323 void encode(const float* input) {
324 base = input[0];
325
326 // Find max delta for scaling
327 float max_delta = 0.0f;
328 for (int i = 1; i < 16; ++i) {
329 max_delta = std::max(max_delta, fast::fast_abs(input[i] - input[i-1]));
330 }
331 scale = max_delta / 127.0f;
332 float inv_scale = fast::fast_rcp(scale + 1e-10f);
333
334 // Encode deltas
335 for (int i = 1; i < 16; ++i) {
336 float delta = input[i] - input[i-1];
337 int q = static_cast<int>(delta * inv_scale + 0.5f);
338 deltas[i-1] = static_cast<int8_t>(std::max(-127, std::min(127, q)));
339 }
340 }
341
342 void decode(float* output) const {
343 output[0] = base;
344 for (int i = 1; i < 16; ++i) {
345 output[i] = output[i-1] + deltas[i-1] * scale;
346 }
347 }
348
349 // Size: 4 + 15 + 4 = 23 bytes (vs 64)
350};
351
352/**
353 * @brief Run-length encoded binary vector (for sparse binary patterns)
354 */
355struct RLEBinary {
356 std::array<uint8_t, 16> runs; // Alternating run lengths
357 uint8_t num_runs;
358 bool starts_with_one;
359
360 void encode(const uint64_t* state, size_t num_words) {
361 num_runs = 0;
362 uint8_t current_run = 0;
363 bool current_bit = (state[0] & 1);
364 starts_with_one = current_bit;
365
366 for (size_t w = 0; w < num_words && num_runs < 16; ++w) {
367 for (int b = 0; b < 64 && num_runs < 16; ++b) {
368 bool bit = (state[w] >> b) & 1;
369 if (bit == current_bit) {
370 if (current_run < 255) ++current_run;
371 } else {
372 runs[num_runs++] = current_run;
373 current_bit = bit;
374 current_run = 1;
375 }
376 }
377 }
378 if (num_runs < 16) {
379 runs[num_runs++] = current_run;
380 }
381 }
382
383 // Useful for compressing sparse activation patterns
384};
385
386// ═══════════════════════════════════════════════════════════════════════════════
387// IV. COMPACT WEIGHT MATRICES
388// ═══════════════════════════════════════════════════════════════════════════════
389
390/**
391 * @brief Compact 16x16 matrix using int8 quantization (256 bytes vs 1024)
392 */
393struct CompactMatrix16x16 {
394 alignas(64) std::array<int8_t, 256> data;
395 QuantParams row_params[16]; // Per-row quantization
396
397 void quantize(const float* input) {
398 for (int i = 0; i < 16; ++i) {
399 // Find row range
400 float min_val = input[i * 16], max_val = input[i * 16];
401 for (int j = 1; j < 16; ++j) {
402 min_val = std::min(min_val, input[i * 16 + j]);
403 max_val = std::max(max_val, input[i * 16 + j]);
404 }
405 row_params[i] = QuantParams(min_val, max_val);
406
407 // Quantize row
408 for (int j = 0; j < 16; ++j) {
409 data[i * 16 + j] = quantize_int8(input[i * 16 + j], row_params[i]);
410 }
411 }
412 }
413
414 /**
415 * @brief Matrix-vector multiply with dequantization
416 */
417 void matvec(const float* x, float* y) const {
418 for (int i = 0; i < 16; ++i) {
419 int32_t sum = 0;
420 for (int j = 0; j < 16; ++j) {
421 // Approximate: assume x is roughly in [-1, 1]
422 sum += static_cast<int32_t>(data[i * 16 + j]) *
423 static_cast<int32_t>(x[j] * 127.0f);
424 }
425 y[i] = sum * row_params[i].scale / 127.0f + row_params[i].zero_point;
426 }
427 }
428
429 // Size: 256 + 16*8 = 384 bytes (vs 1024 for float)
430};
431
432/**
433 * @brief Ultra-compact binary matrix (256 bits = 32 bytes!)
434 */
435struct BinaryMatrix16x16 {
436 std::array<uint16_t, 16> rows; // Each row is 16 bits
437
438 void from_signs(const float* input) {
439 for (int i = 0; i < 16; ++i) {
440 rows[i] = 0;
441 for (int j = 0; j < 16; ++j) {
442 if (input[i * 16 + j] >= 0) {
443 rows[i] |= (1u << j);
444 }
445 }
446 }
447 }
448
449 /**
450 * @brief Binary matrix-vector multiply (XNOR + popcount)
451 */
452 void matvec_binary(uint16_t x_signs, int8_t* y) const {
453 for (int i = 0; i < 16; ++i) {
454 uint16_t xnor = ~(rows[i] ^ x_signs);
455 int same = __builtin_popcount(xnor);
456 y[i] = static_cast<int8_t>(2 * same - 16); // Range: [-16, 16]
457 }
458 }
459
460 // Size: 32 bytes (vs 1024 for float!)
461};
462
463// ═══════════════════════════════════════════════════════════════════════════════
464// V. MEMORY POOL FOR COMPACT ALLOCATIONS
465// ═══════════════════════════════════════════════════════════════════════════════
466
467/**
468 * @brief Simple bump allocator for compact structures
469 */
470template<size_t SIZE = 4096>
471struct CompactPool {
472 alignas(64) std::array<uint8_t, SIZE> data;
473 size_t offset;
474
475 CompactPool() : offset(0) {}
476
477 void* alloc(size_t bytes, size_t align = 8) {
478 // Align offset
479 offset = (offset + align - 1) & ~(align - 1);
480 if (offset + bytes > SIZE) return nullptr;
481 void* ptr = &data[offset];
482 offset += bytes;
483 return ptr;
484 }
485
486 template<typename T>
487 T* alloc_type() {
488 return static_cast<T*>(alloc(sizeof(T), alignof(T)));
489 }
490
491 void reset() { offset = 0; }
492 size_t used() const { return offset; }
493 size_t available() const { return SIZE - offset; }
494};
495
496} // namespace compact
497} // namespace mind_farm
498