Spaces:
Running on Zero
Running on Zero
File size: 6,954 Bytes
2cf2375 | 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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | #pragma once
#include <vector>
#include <string>
#include <iostream>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <atomic>
#include "StateIndex.h"
#include "Compressor.h"
namespace Bestemshe {
enum class GameValue : uint8_t {
UNKNOWN = 0,
WIN = 1,
LOSS = 2,
DRAW = 3
};
class InferenceEngine {
private:
struct RawLayerCacheEntry {
std::vector<uint8_t> win_bits;
std::vector<uint8_t> draw_bits;
std::atomic<int> loaded{0};
RawLayerCacheEntry() = default;
RawLayerCacheEntry(RawLayerCacheEntry&& other) noexcept {
win_bits = std::move(other.win_bits);
draw_bits = std::move(other.draw_bits);
loaded.store(other.loaded.load(std::memory_order_relaxed), std::memory_order_relaxed);
}
RawLayerCacheEntry& operator=(RawLayerCacheEntry&& other) noexcept {
if (this != &other) {
win_bits = std::move(other.win_bits);
draw_bits = std::move(other.draw_bits);
loaded.store(other.loaded.load(std::memory_order_relaxed), std::memory_order_relaxed);
}
return *this;
}
RawLayerCacheEntry(const RawLayerCacheEntry&) = delete;
RawLayerCacheEntry& operator=(const RawLayerCacheEntry&) = delete;
};
// THE BREAKTHROUGH: Flat 2D Array Cache. No std::string, no std::unordered_map.
// M ranges from 0 to 48. K2 ranges from 0 to 24.
// We size to [50][26] to safely cover bounds without math operations.
RawLayerCacheEntry fast_cache[50][26];
std::mutex cache_mutex;
// Hardcoded ZSTD configuration
static constexpr size_t ZSTD_BLOCK_SIZE_BITS = 33554432;
public:
InferenceEngine() = default;
// Evicts all preloaded data from memory
void clear_cache() {
std::lock_guard<std::mutex> lock(cache_mutex);
for (int m = 0; m < 50; ++m) {
for (int k2 = 0; k2 < 26; ++k2) {
fast_cache[m][k2].win_bits.clear();
fast_cache[m][k2].win_bits.shrink_to_fit();
fast_cache[m][k2].draw_bits.clear();
fast_cache[m][k2].draw_bits.shrink_to_fit();
fast_cache[m][k2].loaded.store(0, std::memory_order_relaxed);
}
}
}
// Preloads ONLY the higher micro-layers strictly reachable from pair (K1, K2).
bool preload_pair(uint16_t K1, uint16_t K2, uint8_t layer_M) {
clear_cache();
std::cout << "[INFO] InferenceEngine: Preloading selective reach set for Pair ("
<< K1 << "," << K2 << ") in Layer M=" << static_cast<int>(layer_M) << "...\n";
bool ok = true;
// 1. (K2, j) where j is even and K1 < j <= 24
for (int j = K1 + 2; j <= 24; j += 2) {
uint8_t M_next = K2 + j;
if (M_next <= 48 && !preload_uncompressed_layer(M_next, static_cast<uint8_t>(j))) ok = false;
}
// 2. (K1, j) where j is even and K2 < j <= 24
for (int j = K2 + 2; j <= 24; j += 2) {
uint8_t M_next = K1 + j;
if (M_next <= 48 && !preload_uncompressed_layer(M_next, static_cast<uint8_t>(j))) ok = false;
}
return ok;
}
// High-performance $O(1)$ single-state query. Zero heap allocations.
inline GameValue query_state(uint8_t M, uint8_t k2, uint64_t state_index) {
if (k2 > M) return GameValue::UNKNOWN;
const auto& entry = fast_cache[M][k2];
if (entry.loaded.load(std::memory_order_acquire) == 0) {
// Lazy load fallback (should rarely hit during active sweep if preloaded correctly)
if (!preload_uncompressed_layer(M, k2)) return GameValue::UNKNOWN;
}
int R = 50 - static_cast<int>(M);
uint64_t b_count = StateIndex::nCr(R + 9, 9);
uint64_t local_idx = state_index % b_count;
if (extract_bit(entry.draw_bits, local_idx)) return GameValue::DRAW;
if (extract_bit(entry.win_bits, local_idx)) return GameValue::WIN;
return GameValue::LOSS;
}
private:
bool preload_uncompressed_layer(uint8_t M, uint8_t k2) {
// Fast path: Check without lock
if (fast_cache[M][k2].loaded.load(std::memory_order_acquire) == 1) return true;
std::lock_guard<std::mutex> lock(cache_mutex);
// Double check with lock
if (fast_cache[M][k2].loaded.load(std::memory_order_relaxed) == 1) return true;
uint16_t k1 = static_cast<uint16_t>(M) - static_cast<uint16_t>(k2);
std::string raw_win = "layers/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_win.raw";
std::string raw_draw = "layers/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_draw.raw";
std::string comp_win = "layers/compressed/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_win.bin";
std::string comp_draw = "layers/compressed/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_draw.bin";
std::vector<uint8_t> win_bits;
std::vector<uint8_t> draw_bits;
// Try RAW files first (if we are actively generating/verifying)
if (std::filesystem::exists(raw_win) && std::filesystem::exists(raw_draw)) {
auto read_raw = [](const std::string& path) -> std::vector<uint8_t> {
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f) return {};
size_t sz = f.tellg();
f.seekg(0);
std::vector<uint8_t> data(sz);
f.read(reinterpret_cast<char*>(data.data()), sz);
return data;
};
win_bits = read_raw(raw_win);
draw_bits = read_raw(raw_draw);
}
// Fallback to ZSTD COMPRESSED files
else if (std::filesystem::exists(comp_win) && std::filesystem::exists(comp_draw)) {
int R = 50 - M;
uint64_t b_count = StateIndex::nCr(R + 9, 9);
uint64_t expected_bytes = (b_count + 7) / 8;
win_bits = Compressor::DecompressMicroLayer(comp_win, ZSTD_BLOCK_SIZE_BITS / 8, expected_bytes);
draw_bits = Compressor::DecompressMicroLayer(comp_draw, ZSTD_BLOCK_SIZE_BITS / 8, expected_bytes);
} else {
std::cerr << "[FATAL] Dependencies missing for Pair (" << k1 << "," << (int)k2
<< ") in M=" << (int)M << ". Ensure .raw or .bin files exist.\n";
return false;
}
if (win_bits.empty() || draw_bits.empty()) return false;
fast_cache[M][k2].win_bits = std::move(win_bits);
fast_cache[M][k2].draw_bits = std::move(draw_bits);
fast_cache[M][k2].loaded.store(1, std::memory_order_release);
return true;
}
inline bool extract_bit(const std::vector<uint8_t>& block_data, size_t offset) const {
return (block_data[offset / 8] >> (offset % 8)) & 1;
}
};
} // namespace Bestemshe |