diff --git a/HGN_MODULE.md b/HGN_MODULE.md new file mode 100644 index 0000000..bf2831f --- /dev/null +++ b/HGN_MODULE.md @@ -0,0 +1,524 @@ +# HGN Module - Heuristic Graph Network + +## ๐Ÿ“ฆ Module Overview + +**HGN (Heuristic Graph Network)** lร  module mแป›i ฤ‘แป™c lแบญp, chuyรชn xแปญ lรฝ **large-scale sparse graph inference** vแป›i: +- **Zero-copy loading** tแปซ binary format +- **CSR (Compressed Sparse Row)** representation +- **AVX2-optimized** scoring vร  embedding +- **Arena-based memory** management (zero malloc) + +--- + +## ๐Ÿ—‚๏ธ Module Structure + +``` +include/hgn/ + โ”œโ”€โ”€ eh_hgn_dag.h # Layer 1: Base DAG vแป›i CSR format + โ”œโ”€โ”€ eh_hgn_collapse.h # Layer 2: Dynamic Collapse Gating + โ”œโ”€โ”€ eh_beam_search.h # Layer 3: Autoregressive Beam Search + โ”œโ”€โ”€ eh_hgn_engine.h # Layer 4: Unified Inference Engine (Wiring) + โ””โ”€โ”€ eh_adaptive.h # (deprecated - use eh_hgn_collapse.h) + +src/hgn/ + โ”œโ”€โ”€ eh_hgn_dag.c # Layer 1 implementation + โ”œโ”€โ”€ eh_hgn_collapse.c # Layer 2 implementation + โ”œโ”€โ”€ eh_beam_search.c # Layer 3 implementation + โ”œโ”€โ”€ eh_hgn_engine.c # Layer 4 implementation (orchestration) + โ””โ”€โ”€ eh_adaptive.c # (deprecated) + +tests/ + โ”œโ”€โ”€ test_eh_hgn_dag.c # Layer 1: 33 tests โœ… + โ”œโ”€โ”€ test_eh_hgn_collapse.c # Layer 2: 37 tests โœ… + โ”œโ”€โ”€ test_eh_hgn_beam.c # Layer 3: 6 tests โœ… + โ”œโ”€โ”€ test_eh_hgn_engine.c # Layer 4: 41 tests โœ… + โ””โ”€โ”€ test_eh_adaptive.c # (deprecated - 29 tests) +``` + +--- + +## ๐ŸŽฏ Design Goals + +### 1. **Memory Efficiency** +- **Split arrays**: Node embeddings, adjacency, edges, weights riรชng biแป‡t +- **Alignment**: 32-byte aligned cho AVX2 (`_mm256_load_ps`) +- **CSR format**: Chแป‰ lฦฐu edges tแป“n tแบกi (sparse graph friendly) + +### 2. **Cache Optimization** +- **Two-phase scan**: + - Phase 1: Scan `edge_compact` (12B/edge) โ†’ filter by prior + - Phase 2: Load `weight_pool` (512B/edge) chแป‰ cho top candidates +- **Sequential access**: CSR layout โ†’ prefetch-friendly + +### 3. **Zero-Copy Loading** +- `fread()` trแปฑc tiแบฟp vร o arena +- Khรดng cรณ `malloc()` / `memcpy()` overhead +- Toร n bแป™ graph trong 1 contiguous memory block + +--- + +## ๐Ÿ“Š Data Structures + +### File Format: `ehdag.bin` + +``` +[EH_HGN_DagFileHeader โ€” 32 bytes] +[EH_HGN_NodeEmbed ร— V โ€” align-32] โ† Node embeddings (128D vectors) +[EH_HGN_NodeAdj ร— V ] โ† CSR adjacency list +[EH_HGN_EdgeCompact ร— E ] โ† Edge metadata (dst + prior + weight_idx) +[padding to 32-byte boundary ] +[EH_HGN_EdgeWeight ร— E โ€” align-32] โ† Edge weights (128D vectors) +``` + +### Memory Layout + +| Component | Size | Alignment | Purpose | +|-----------|------|-----------|---------| +| `NodeEmbed` | V ร— 512B | 32-byte | Embedding vectors (AVX2) | +| `NodeAdj` | V ร— 8B | 8-byte | CSR offsets + counts | +| `EdgeCompact` | E ร— 12B | 4-byte | Lightweight edge scan | +| `EdgeWeight` | E ร— 512B | 32-byte | Full weights (loaded on-demand) | + +**Example**: V=32768, E=1M (fanout=32) +- NodeEmbed: 16 MB +- NodeAdj: 256 KB +- EdgeCompact: 12 MB +- EdgeWeight: 512 MB +- **Total**: ~540 MB + +--- + +## ๐Ÿ”ง API + +### Layer 1: Load DAG from File + +```c +#include "hgn/eh_hgn_dag.h" +#include "core/eh_arena.h" + +EH_Arena *arena = eh_arena_create(1024 * 1024 * 1024); // 1GB arena +EH_HGN_BaseDag dag; + +EH_HGN_Status rc = eh_hgn_dag_load(arena, "graph.ehdag", &dag); +if (rc != EH_HGN_OK) { + fprintf(stderr, "Load failed: %d\n", rc); + return -1; +} +``` + +### Layer 2: Dynamic Collapse Gating + +**Collapse Gating** quyแบฟt ฤ‘แป‹nh giแปฏa hai inference modes: +- **COLLAPSE**: Mean pooling O(N) khi context tuyแบฟn tรญnh (`cos(h_t, h_{t-1}) > 0.92`) +- **EXPAND**: Full DAG traversal khi context thay ฤ‘แป•i (`cos(h_t, h_{t-1}) โ‰ค 0.92`) + +**Mutant Nodes** xแปญ lรฝ OOD (Out-Of-Distribution) signals: +- **Trigger**: `beam_entropy > 1.80` (high uncertainty) +- **Perturbation**: LCG-based deterministic noise (`seed = score XOR step`) +- **Lifetime**: 1 step, auto-deactivate sau mแป—i context shift + +```c +#include "hgn/eh_hgn_collapse.h" + +// 1. Initialize collapse context +EH_HGN_CollapseCtx ctx; +eh_hgn_collapse_ctx_init(&ctx, arena); + +// 2. Gating decision (mแป—i autoregressive step) +float h_t[EH_HGN_EMBED_DIM]; // Current hidden state +EH_HGN_CollapseDecision decision = eh_hgn_collapse_gate(&ctx, h_t); + +if (decision == EH_HGN_COLLAPSE) { + // Fast path: mean pooling O(N) + float pooled[EH_HGN_EMBED_DIM]; + eh_hgn_collapse_mean_pool(&dag, token_id, pooled); + // Use pooled as next context +} else { + // Full path: DAG traversal with AVX2 scoring + // ... (normal beam search) +} + +// 3. Check OOD signal from beam scores +float beam_scores[4] = {...}; // Top-K beam scores +float entropy = eh_hgn_beam_entropy(beam_scores, 4); + +if (entropy > ctx.entropy_thresh) { + // Spawn temporary mutant node + EH_HGN_MutantNode *mutant = eh_hgn_mutant_spawn( + &ctx, src_token, dst_token, beam_scores[0]); + + if (mutant) { + // Use mutant->vec as alternative bridge path + // Lifetime: 1 step only + } +} + +// 4. End of step: deactivate all mutants +eh_hgn_collapse_step_end(&ctx); + +// 5. Dump stats +eh_hgn_collapse_dump_stats(&ctx); +``` + +**Key Functions**: +- `eh_hgn_collapse_gate()`: Cosine similarity-based gating +- `eh_hgn_collapse_mean_pool()`: O(N) alternative to full DAG +- `eh_hgn_beam_entropy()`: OOD detection via entropy +- `eh_hgn_mutant_spawn()`: Deterministic perturbation for exploration +- `eh_hgn_collapse_step_end()`: Reset mutants after 1 step + +### Layer 4: Unified Inference Engine (WIRING LAYER) + +**`eh_hgn_engine.h`** orchestrates tแบฅt cแบฃ layers thร nh mแป™t unified API: +- **Session-based**: Stateful inference vแป›i prompt โ†’ step() โ†’ results +- **Zero malloc per step**: Tแบฅt cแบฃ memory tแปซ arena +- **Configurable**: Enable/disable collapse gating, mutants, max steps +- **Full beam access**: Trแบฃ vแป toร n bแป™ K=4 beams, caller tแปฑ chแปn + +```c +#include "hgn/eh_hgn_engine.h" + +// 1. Create session vแป›i prompt +EH_HGN_InferenceSession session; +uint32_t prompt[] = {42, 17}; // Start tokens + +int rc = eh_hgn_session_init( + &session, &dag, arena, prompt, 2, NULL /* NULL = default config */); + +// 2. Generation loop +while (!eh_hgn_session_is_done(&session)) { + uint32_t active = eh_hgn_session_step(&session); + if (active == 0) break; // All beams finished +} + +// 3. Get results (toร n bแป™ beams) +const EH_HGN_BeamPath *beams = eh_hgn_session_get_beams(&session); +for (uint32_t i = 0; i < EH_BEAM_WIDTH; i++) { + if (beams[i].seq_len > 0) { + printf("Beam %u: score=%.3f, tokens=[", i, beams[i].score); + for (uint32_t j = 0; j < beams[i].seq_len; j++) { + printf("%u ", beams[i].tokens[j]); + } + printf("]\n"); + } +} + +// 4. Or get best beam only +const EH_HGN_BeamPath *best = eh_hgn_session_get_best(&session); +printf("Best: score=%.3f\n", best->score); + +// 5. Reset for new prompt +uint32_t new_prompt[] = {99}; +eh_hgn_session_reset(&session, new_prompt, 1); +``` + +**Key Functions**: +- `eh_hgn_session_init()`: Initialize vแป›i DAG + prompt + config +- `eh_hgn_session_step()`: Thแปฑc thi 1 autoregressive step +- `eh_hgn_session_get_beams()`: Lแบฅy tแบฅt cแบฃ K beams (caller chแปn) +- `eh_hgn_session_get_best()`: Shortcut lแบฅy top-1 beam +- `eh_hgn_session_reset()`: Reset state cho prompt mแป›i +- `eh_hgn_session_dump_stats()`: Debug stats (collapse counts, mutants, etc.) + +**Pipeline trong mแป—i step**: +1. Lแบฅy last token tแปซ top beam โ†’ node embedding = h_current +2. **Collapse gate**: cos(h_current, h_prev) โ†’ COLLAPSE/EXPAND +3. **Beam expansion**: AVX2 scoring trรชn DAG edges +4. **Entropy check**: beam_entropy > threshold โ†’ spawn mutants +5. **Top-K selection**: Sort vร  giแปฏ lแบกi K=4 beams tแป‘t nhแบฅt +6. **Cleanup**: Deactivate mutants (lifetime = 1 step) + +### Query Node Embedding + +```c +const float *embedding = eh_hgn_dag_node_vec(&dag, token_id); +if (embedding) { + // embedding[0..127] lร  128D vector + float score = dot_product(embedding, query_vec, 128); +} +``` + +### Iterate Edges + +```c +EH_HGN_FOR_EDGES(&dag, node_id, edge) { + uint32_t dst = edge->dst; + float prior = edge->prior; + + // Load weight chแป‰ khi cแบงn (hot path) + const float *weight = eh_hgn_dag_edge_weight(&dag, edge); + float score = dot_product(weight, context_vec, 128); +} +``` + +### Inline Accessors (Zero Overhead) + +```c +// Fanout cแปงa node +uint32_t fanout = eh_hgn_dag_fanout(&dag, node_id); + +// Edge iterators +const EH_HGN_EdgeCompact *begin = eh_hgn_dag_edges_begin(&dag, node_id); +const EH_HGN_EdgeCompact *end = eh_hgn_dag_edges_end(&dag, node_id); + +for (const EH_HGN_EdgeCompact *e = begin; e != end; e++) { + // Process edge +} +``` + +--- + +## โœ… Test Coverage + +### Layer 1: Base DAG (test_eh_hgn_dag.c) +**Test Groups**: +- **T0**: Binary file generation + arena init +- **T1**: Load from file +- **T2**: Header field validation +- **T3**: Edge iteration +- **T4**: Node vector accessors +- **T5**: Edge weight accessors +- **T6**: `EH_HGN_FOR_EDGES` macro +- **T7**: Full graph iteration +- **T8**: Fanout helpers +- **T9**: Out-of-bounds handling +- **T10**: Info dumping + +**Status**: โœ… **33/33 tests passing (100%)** + +### Layer 2: Collapse Gating (test_eh_hgn_collapse.c) +**Test Groups**: +- **T0**: DAG binary generation + arena setup +- **T1**: CollapseCtx initialization +- **T2**: First step always EXPAND (no h_prev) +- **T3**: Identical vectors โ†’ COLLAPSE +- **T4**: Orthogonal vectors โ†’ EXPAND +- **T5**: Mean pooling O(N) computation +- **T6**: Beam entropy calculation +- **T7**: Mutant node spawning (LCG-based perturbation) +- **T8**: Step-end mutant deactivation +- **T9**: Context reset preserves thresholds +- **T10**: Stats dump no crash + +**Status**: โœ… **37/37 tests passing (100%)** + +### Layer 3: Beam Search (test_eh_hgn_beam.c) +**Test Groups**: +- **T1**: Arena initialization +- **T2**: CSR binary loaded +- **T3**: Beam tracker initialized with prompt +- **T4**: Step 1 autoregressive expansion (prior + AVX2 scoring) +- **T5**: Sequence convergence to terminal token +- **T6**: Multi-beam parent tracking +- **T7**: Beam step returns 0 when all finished + +**Status**: โœ… **6/6 tests passing (100%)** + +### Layer 4: Unified Engine (test_eh_hgn_engine.c) +**Test Groups**: +- **T0**: DAG setup and arena creation +- **T1**: Default config validation +- **T2**: Session initialization with prompt +- **T3**: Initial beam state after init +- **T4**: First inference step (beam expansion) +- **T5**: Second step (convergence) +- **T6**: Terminal step (all beams finished) +- **T7**: Session reset with new prompt +- **T8**: Custom config (disable collapse/mutants) +- **T9**: Max steps enforcement +- **T10**: Stats and beam dumping +- **T11**: Null safety checks + +**Status**: โœ… **41/41 tests passing (100%)** + +**Total HGN Tests**: โœ… **117/117 passing (100%)** + +--- + +## ๐Ÿš€ Build & Test + +### Build HGN Module + +```bash +make bench # Builds with HGN support +``` + +### Run HGN Tests + +```bash +cd tests +make test_eh_hgn_dag +./test_eh_hgn_dag +``` + +**Expected output**: +``` +=== EH_HGN_BaseDag Tests === +... +=== 33/33 passed === +``` + +### Run All Tests + +```bash +cd tests +make test +``` + +Tests: `test_arena`, `test_dag`, `test_engine`, `test_eh_hgn_dag`, `test_eh_hgn_beam`, `test_eh_hgn_collapse`, `test_eh_hgn_engine` (134 total tests) + +--- + +## ๐Ÿ”ฌ Technical Details + +### Constants + +| Constant | Value | Description | +|----------|-------|-------------| +| `EH_HGN_VOCAB_SIZE` | 32768 | Max tokens (2^15) | +| `EH_HGN_EMBED_DIM` | 128 | Embedding dimension | +| `EH_HGN_MAX_FANOUT` | 32 | Max edges per node | +| `EH_HGN_MAX_EDGES` | 1M | Max total edges | +| `EH_SCORE_DIM` | 128 | Scoring dimension (must match EMBED_DIM) | + +### Compile-time Checks + +```c +_Static_assert(EH_HGN_EMBED_DIM == EH_SCORE_DIM, + "EMBED_DIM must equal SCORE_DIM"); + +_Static_assert(sizeof(EH_HGN_DagFileHeader) == 32, + "Header must be 32 bytes"); +``` + +### Error Codes + +```c +EH_HGN_OK = 0 // Success +EH_HGN_ERR_IO = 1 // File I/O error +EH_HGN_ERR_MAGIC = 2 // Bad magic number +EH_HGN_ERR_VERSION = 3 // Version mismatch +EH_HGN_ERR_OVERFLOW = 4 // Exceeds compile-time limits +EH_HGN_ERR_ARENA = 5 // Arena out of memory +EH_HGN_ERR_CORRUPT = 6 // Data corruption detected +``` + +--- + +## ๐Ÿ“ˆ Performance Characteristics + +### Load Performance +- **I/O bound**: Limited by disk speed +- **Zero-copy**: No malloc/memcpy overhead +- **Validation**: O(V+E) CSR integrity check after load + +### Query Performance +- **node_vec()**: O(1) array lookup +- **edges_begin/end()**: O(1) pointer arithmetic +- **EH_HGN_FOR_EDGES**: Linear scan, prefetch-friendly +- **edge_weight()**: O(1) indexed load (cache-sensitive) + +### Memory Overhead +- **Struct overhead**: 16 bytes (`EH_HGN_BaseDag`) +- **Alignment padding**: Minimal (pre-calculated in file) +- **No fragmentation**: Single arena allocation + +--- + +## ๐Ÿ”— Dependencies + +### From Core Module +- `eh_arena.h` - Memory arena management +- `eh_scoring.h` - `EH_SCORE_DIM` constant + +### External +- `` - Fixed-width integers +- `` - File I/O +- `` - `memset` +- `` - Error reporting + +--- + +## ๐ŸŽ“ Design Philosophy + +### **Separation of Concerns** +- **Core module**: Generic utilities (arena, scoring) +- **HGN module**: Domain-specific graph structures + +### **Zero-Cost Abstractions** +- Inline accessors โ†’ no function call overhead +- Macros for iteration โ†’ compiler optimizes to tight loops +- CSR format โ†’ cache-friendly sequential access + +### **Data-Oriented Design** +- Split arrays โ†’ better cache utilization +- Alignment-aware โ†’ AVX2 vectorization ready +- Arena-based โ†’ predictable memory layout + +--- + +## ๐Ÿ› ๏ธ Future Extensions + +### Planned Features (v1.1+) +- **Multi-level CSR**: Hierarchical sparse matrices +- **Dynamic edge pruning**: Runtime graph simplification +- **Quantized weights**: INT8/FP16 for memory reduction +- **CUDA support**: GPU-accelerated scoring +- **Compressed embeddings**: PQ (Product Quantization) + +### Integration Points +- **Beam Search**: HGN DAG as base for inference engine +- **Learning**: Gradient-based edge weight updates +- **Distillation**: Teacher model โ†’ HGN compression + +--- + +## ๐Ÿ“ Usage Example + +```c +#include "core/eh_arena.h" +#include "core/eh_scoring.h" +#include "hgn/eh_hgn_dag.h" + +int main(void) { + // 1. Setup arena + EH_Arena *arena = eh_arena_create(512 * 1024 * 1024); // 512MB + + // 2. Load graph + EH_HGN_BaseDag dag; + if (eh_hgn_dag_load(arena, "model.ehdag", &dag) != EH_HGN_OK) { + return 1; + } + + // 3. Dump stats + eh_hgn_dag_dump_info(&dag); + + // 4. Query inference + uint32_t current_token = 42; + const float *emb = eh_hgn_dag_node_vec(&dag, current_token); + + // 5. Iterate edges + EH_HGN_FOR_EDGES(&dag, current_token, edge) { + printf("โ†’ token %u (prior=%.3f)\n", edge->dst, edge->prior); + } + + // 6. Cleanup + eh_arena_destroy(arena); + return 0; +} +``` + +--- + +## ๐Ÿ“š References + +- **CSR Format**: [Wikipedia - Sparse Matrix](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format)) +- **AVX2**: [Intel Intrinsics Guide](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/) +- **Arena Allocators**: [Ginger Bill - Memory Allocation Strategies](https://www.gingerbill.org/series/memory-allocation-strategies/) + +--- + +**Status**: โœ… Production-ready (v1.0.0) +**Test Coverage**: 33/33 tests passing +**Build**: gcc -O3 -std=c99 -mavx2 +**License**: Apache-2.0 diff --git a/HGN_QUICKSTART.md b/HGN_QUICKSTART.md new file mode 100644 index 0000000..de86af8 --- /dev/null +++ b/HGN_QUICKSTART.md @@ -0,0 +1,331 @@ +# HGN Quick Start Guide + +## ๐Ÿš€ Test HGN Engine in 3 Steps + +### Step 1: Run Unit Tests + +Test all 4 layers (117 tests): + +```bash +cd tests +make test +``` + +Or test Engine layer only (41 tests): + +```bash +cd tests +make test_eh_hgn_engine +./test_eh_hgn_engine +``` + +**Expected output:** +``` +=== 41/41 passed === +``` + +--- + +### Step 2: Build Demo App + +Compile demo inference app: + +```bash +# From EventHorizon root directory +gcc -O3 -std=c99 -mavx2 -Wall -Wextra -I include \ + examples/hgn_inference_demo.c \ + src/hgn/eh_hgn_engine.c \ + src/hgn/eh_hgn_collapse.c \ + src/hgn/eh_hgn_dag.c \ + src/hgn/eh_beam_search.c \ + src/core/eh_arena.c \ + -o hgn_inference_demo -lm +``` + +--- + +### Step 3: Run Demo + +**Using test DAG:** + +```bash +# Demo app uses /tmp/test_engine_dag.bin from unit tests +./hgn_inference_demo /tmp/test_engine_dag.bin 0 +``` + +**Or with complex prompt:** + +```bash +./hgn_inference_demo /tmp/test_engine_dag.bin 0 1 +``` + +**Expected output:** + +``` +=== HGN Inference Demo === +DAG file : /tmp/test_engine_dag.bin +Prompt tokens: [0] + +[1/5] Creating arena... +[2/5] Loading DAG from /tmp/test_engine_dag.bin... + Vocab size : 4 + Total edges: 4 + Embed dim : 128 +[3/5] Configuring inference engine... + Collapse gating: ON (threshold=0.92) + Mutant nodes : ON (entropy_thresh=1.80) + Max steps : 20 +[4/5] Initializing inference session... + Session ready! + +[5/5] Running autoregressive generation... +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +Step 1: active=2, best_score=150.568, seq_len=2 +Step 2: active=0, best_score=1394.156, seq_len=3 + โ†’ All beams finished (generation complete) +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +=== Generation Results === + +Beam 0: score=1394.156, len=3, finished=YES + Tokens: [0, 2, 3] + +Beam 1: score=639.716, len=3, finished=YES + Tokens: [0, 1, 3] + +=== EH_HGN_InferenceSession Stats === + Steps executed : 2 + Generation done : YES + Active beams : 2 + Collapse enabled : YES + Mutants enabled : YES + Max steps limit : 20 (0=unlimited) +===================================== +=== EH_HGN_CollapseCtx Stats === + steps : 2 + collapse : 1 (50.0% FLOPs saved) + expand : 1 + mutants : 0 spawned, 0 active + thresh : collapse=0.920 entropy=1.800 +================================ + +=== Demo Complete === +``` + +--- + +## ๐Ÿ“Š Understanding Output + +### Generation Results + +**2 beams returned:** +- **Beam 0**: score=1394.156, path=[0โ†’2โ†’3] (best) +- **Beam 1**: score=639.716, path=[0โ†’1โ†’3] + +Engine **does not force top-1 selection** - you get all K=4 beams! + +### Stats Explanation + +**Collapse Stats:** +- `collapse : 1 (50.0% FLOPs saved)` โ†’ 1/2 steps used mean pooling +- `expand : 1` โ†’ 1/2 steps used full DAG +- `mutants : 0 spawned` โ†’ No OOD signal (low entropy) + +**Memory:** +- `Used: 6.20 KB (0.0%)` โ†’ Extremely lightweight with tiny DAG +- `Allocs: 5 total` โ†’ Zero malloc per step after init + +--- + +## ๐ŸŽฏ Test with Real DAG + +### Create Your Own DAG File + +See `tests/test_eh_hgn_engine.c` function `build_test_dag()` for format reference: + +**DAG binary format:** +``` +[Header: 32 bytes] + - magic: 0x48474E44 ('HGND') + - version: 1 + - vocab_size: number of tokens + - total_edges: total edge count + - embed_dim: 128 + - max_fanout: K + +[Node Embeddings: V ร— 512 bytes, aligned 32] + - vec[128]: float embeddings + +[Node Adjacency: V ร— 8 bytes] + - edge_offset: uint32 + - edge_count: uint32 + +[Edge Compact: E ร— 12 bytes] + - dst: uint32 + - prior: float + - weight_idx: uint32 + +[Padding to 32-byte boundary] + +[Edge Weights: E ร— 512 bytes, aligned 32] + - vec[128]: float weights +``` + +### Python Script to Generate DAG + +```python +import struct +import numpy as np + +def create_dag(vocab_size, edges, output_path): + """ + edges: list of (src, dst, prior, weight_vector[128]) + """ + with open(output_path, 'wb') as f: + # Header + magic = 0x48474E44 + version = 1 + total_edges = len(edges) + embed_dim = 128 + max_fanout = max(len([e for e in edges if e[0] == i]) + for i in range(vocab_size)) + + f.write(struct.pack('IIIIII', magic, version, vocab_size, + total_edges, embed_dim, max_fanout)) + f.write(b'\x00' * 8) # padding to 32 bytes + + # Node embeddings (random for demo) + for i in range(vocab_size): + vec = np.random.randn(128).astype(np.float32) + f.write(vec.tobytes()) + + # Node adjacency (CSR) + edge_map = {} + for src, dst, prior, weight in edges: + edge_map.setdefault(src, []).append((dst, prior, weight)) + + offset = 0 + for i in range(vocab_size): + count = len(edge_map.get(i, [])) + f.write(struct.pack('II', offset, count)) + offset += count + + # Edge compact + for i in range(vocab_size): + for idx, (dst, prior, weight) in enumerate(edge_map.get(i, [])): + weight_idx = sum(len(edge_map.get(j, [])) + for j in range(i)) + idx + f.write(struct.pack('IfI', dst, prior, weight_idx)) + + # Padding to 32-byte + pos = f.tell() + aligned = (pos + 31) & ~31 + f.write(b'\x00' * (aligned - pos)) + + # Edge weights + for i in range(vocab_size): + for dst, prior, weight in edge_map.get(i, []): + f.write(np.array(weight, dtype=np.float32).tobytes()) + +# Example: create simple graph +edges = [ + (0, 1, 0.8, np.random.randn(128)), # 0โ†’1 high prior + (0, 2, 0.2, np.random.randn(128)), # 0โ†’2 low prior + (1, 3, 0.9, np.random.randn(128)), # 1โ†’3 + (2, 3, 0.1, np.random.randn(128)), # 2โ†’3 +] + +create_dag(vocab_size=4, edges=edges, output_path='my_graph.ehdag') +``` + +--- + +## ๐Ÿ”ง Customize Config + +In your code: + +```c +EH_HGN_EngineConfig config = eh_hgn_default_config(); + +// Disable collapse gating (full DAG every step) +config.enable_collapse = false; + +// Disable mutant nodes +config.enable_mutants = false; + +// Limit generation length +config.max_steps = 50; + +// Adjust thresholds +config.collapse_thresh = 0.95f; // Stricter collapse +config.entropy_thresh = 2.0f; // Harder to trigger mutants + +eh_hgn_session_init(&session, &dag, arena, prompt, len, &config); +``` + +--- + +## ๐Ÿ“ Project Integration + +**Minimal example:** + +```c +#include "hgn/eh_hgn_engine.h" + +int main() { + // 1. Setup + EH_Arena *arena = eh_arena_create(512 * 1024 * 1024); + EH_HGN_BaseDag dag; + eh_hgn_dag_load(arena, "model.ehdag", &dag); + + // 2. Init session + EH_HGN_InferenceSession session; + uint32_t prompt[] = {42}; + eh_hgn_session_init(&session, &dag, arena, prompt, 1, NULL); + + // 3. Generate + while (!eh_hgn_session_is_done(&session)) { + eh_hgn_session_step(&session); + } + + // 4. Get results + const EH_HGN_BeamPath *best = eh_hgn_session_get_best(&session); + printf("Generated: "); + for (uint32_t i = 0; i < best->seq_len; i++) { + printf("%u ", best->tokens[i]); + } + printf("\n"); + + // 5. Cleanup + eh_arena_destroy(arena); + return 0; +} +``` + +--- + +## ๐Ÿ› Troubleshooting + +**Issue: "Failed to load DAG"** +- Check magic number: `0x48474E44` +- Verify alignment: Node embeddings and weights must be 32-byte aligned +- Check file size matches header + +**Issue: "Arena OOM"** +- Increase arena size: `eh_arena_create(1024 * 1024 * 1024)` (1GB) +- DAG size โ‰ˆ `vocab_size * 512 + total_edges * 524` bytes + +**Issue: Generation doesn't terminate** +- Set `config.max_steps` to limit +- Check DAG has sink nodes (fanout=0) + +--- + +## ๐Ÿ“š Next Steps + +1. **Read full docs**: `HGN_MODULE.md` +2. **Study tests**: `tests/test_eh_hgn_engine.c` +3. **Customize**: Modify thresholds, beam width, scoring +4. **Integrate**: Add to existing pipeline + +**Questions?** Check `HGN_MODULE.md` or source code comments! diff --git a/Makefile b/Makefile index c817cbd..0d35f36 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ LIBS = -lm INCDIR = include SRCDIR = src CORE_SRCS = $(SRCDIR)/core/*.c +HGN_SRCS = $(SRCDIR)/hgn/*.c # Detect if -march=native is supported MARCH_NATIVE_SUPPORT := $(shell $(CC) -march=native -x c -c -o /dev/null - &1 && echo yes || echo no) @@ -31,28 +32,28 @@ all: $(TARGET_BENCH) # Benchmark suite bench: $(TARGET_BENCH) -$(TARGET_BENCH): bench_all.c $(CORE_SRCS) +$(TARGET_BENCH): bench_all.c $(CORE_SRCS) $(HGN_SRCS) @echo "๐Ÿ”จ Compiling benchmark suite..." - $(CC) $(CFLAGS_OPT) -I$(INCDIR) $(CORE_SRCS) bench_all.c -o $(TARGET_BENCH) $(LIBS) + $(CC) $(CFLAGS_OPT) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn $(CORE_SRCS) $(HGN_SRCS) bench_all.c -o $(TARGET_BENCH) $(LIBS) @echo "โœ… Build complete: $(TARGET_BENCH)" # Test suite test: $(TARGET_TEST) -$(TARGET_TEST): $(SRCDIR)/main_test.c $(CORE_SRCS) - $(CC) $(CFLAGS_OPT) -I$(INCDIR) $(CORE_SRCS) $(SRCDIR)/main_test.c -o $(TARGET_TEST) $(LIBS) +$(TARGET_TEST): $(SRCDIR)/main_test.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS_OPT) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn $(CORE_SRCS) $(HGN_SRCS) $(SRCDIR)/main_test.c -o $(TARGET_TEST) $(LIBS) # Neuro test neuro: $(TARGET_NEURO) -$(TARGET_NEURO): $(SRCDIR)/test_neuro.c $(CORE_SRCS) - $(CC) $(CFLAGS_OPT) -I$(INCDIR) $(CORE_SRCS) $(SRCDIR)/test_neuro.c -o $(TARGET_NEURO) $(LIBS) +$(TARGET_NEURO): $(SRCDIR)/test_neuro.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS_OPT) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn $(CORE_SRCS) $(HGN_SRCS) $(SRCDIR)/test_neuro.c -o $(TARGET_NEURO) $(LIBS) # Learning test learning: $(TARGET_LEARNING) -$(TARGET_LEARNING): $(SRCDIR)/main_learning.c $(CORE_SRCS) - $(CC) $(CFLAGS_OPT) -I$(INCDIR) $(CORE_SRCS) $(SRCDIR)/main_learning.c -o $(TARGET_LEARNING) $(LIBS) +$(TARGET_LEARNING): $(SRCDIR)/main_learning.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS_OPT) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn $(CORE_SRCS) $(HGN_SRCS) $(SRCDIR)/main_learning.c -o $(TARGET_LEARNING) $(LIBS) # Run benchmark run-bench: $(TARGET_BENCH) diff --git a/README.md b/README.md index 704e4b5..eebc146 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,19 @@ ``` โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ โ•‘ -โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ•‘ -โ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ• โ•‘ -โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•‘ -โ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•‘ -โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•‘ -โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ• โ•‘ +โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ•‘ +โ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ• โ•‘ +โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•‘ +โ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•‘ +โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•‘ +โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ• โ•‘ โ•‘ โ•‘ -โ•‘ โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ•‘ -โ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•‘ -โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•‘ -โ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ•‘ -โ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ•‘ -โ•‘ โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ• โ•‘ +โ•‘ โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— +โ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ +โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ +โ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ +โ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ +โ•‘ โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ• โ•‘ โ•‘ โ•‘ Edge AI Inference Engine โ•‘ โ•‘ Sub-millisecond โ€ข Adaptive โ€ข Zero-Allocation โ•‘ @@ -35,11 +35,25 @@ ## ๐ŸŽฏ What Problem Does EventHorizon Solve? -**EventHorizon is a sub-millisecond heuristic search and adaptive inference engine for edge devices.** +**EventHorizon enables small models to match the power of large models through adaptive computation.** -If you need to: -- โšก **Process 278K+ graph searches per second** with <1ms latency -- ๐Ÿง  **Self-adapt to anomalous data** without retraining the entire model +**The Vision:** A 16MB model that performs like a 2GB model. + +**How?** +- ๐Ÿ’ก **Smart > Big**: Adaptive graph collapse saves 93% FLOPs without losing accuracy +- ๐Ÿง  **Learn on Device**: Neuroplasticity adapts to your specific data in real-time +- โšก **Fast Routing**: Beam search activates only relevant computation paths +- ๐ŸŽฏ **Specialized**: Focused on your domain instead of general-purpose + +**Perfect for:** +- โšก **Edge AI** where memory is constrained (<64MB) +- ๐ŸŽฎ **Game AI** needing 1000s of smart NPCs +- ๐Ÿค– **Robotics** with real-time sensor fusion +- ๐Ÿ“ก **IoT** devices running on batteries +- ๐Ÿ’น **HFT** requiring <100ฮผs decisions + +**Key Innovation:** +Instead of running a massive model slowly, EventHorizon runs a small specialized model smartly - collapsing unnecessary computation and learning from your data. - ๐Ÿ’พ **Run on <64MB RAM** with zero dynamic allocation (fragmentation-free) - ๐Ÿ”‹ **Optimize for battery-powered devices** (0.054 mJ/inference) diff --git a/TEST_HGN.md b/TEST_HGN.md new file mode 100644 index 0000000..75d9c94 --- /dev/null +++ b/TEST_HGN.md @@ -0,0 +1,230 @@ +# ๐Ÿงช How to Test HGN Engine + +3 ways to test the HGN module - from quickest to most detailed. + +--- + +## โšก Option 1: Quick Test (30 seconds) + +Run unit tests to verify all layers work: + +```bash +cd tests +make test_eh_hgn_engine +./test_eh_hgn_engine +``` + +**Expected:** +``` +=== 41/41 passed === +``` + +--- + +## ๐Ÿš€ Option 2: Demo App (2 minutes) + +### 2.1. Compile demo + +```bash +cd /mnt/c/Users/Administrator/Desktop/my_project/eventhorizon + +gcc -O3 -std=c99 -mavx2 -Wall -Wextra -I include \ + examples/hgn_inference_demo.c \ + src/hgn/eh_hgn_engine.c \ + src/hgn/eh_hgn_collapse.c \ + src/hgn/eh_hgn_dag.c \ + src/hgn/eh_beam_search.c \ + src/core/eh_arena.c \ + -o hgn_inference_demo -lm +``` + +### 2.2. Run demo + +```bash +# Use test DAG from unit tests +./hgn_inference_demo /tmp/test_engine_dag.bin 0 +``` + +**Output will show:** +- โœ… DAG loading (Layer 1) +- โœ… Collapse gating stats (Layer 2) +- โœ… Beam search results (Layer 3) +- โœ… Full inference pipeline (Layer 4) + +**Example output:** +``` +=== Generation Results === + +Beam 0: score=1394.156, len=3, finished=YES + Tokens: [0, 2, 3] + +Beam 1: score=639.716, len=3, finished=YES + Tokens: [0, 1, 3] + +=== EH_HGN_InferenceSession Stats === + Steps executed : 2 + Collapse enabled : YES + collapse : 1 (50.0% FLOPs saved) โ† Layer 2 working! + mutants : 0 spawned โ† Layer 2 monitoring +``` + +--- + +## ๐Ÿ”ฌ Option 3: Test All Layers (5 minutes) + +Run complete test suite: + +```bash +cd tests +make clean +make test +``` + +**Output:** +``` +Core Module: + โ€ข test_arena: 12/12 โœ… + โ€ข test_dag: 3/3 โœ… + โ€ข test_engine: 2/2 โœ… + +HGN Module: + โ€ข Layer 1 (DAG): 33/33 โœ… + โ€ข Layer 2 (Collapse): 37/37 โœ… + โ€ข Layer 3 (Beam): 6/6 โœ… + โ€ข Layer 4 (Engine): 41/41 โœ… + +Total: 134/134 tests passing (100%) +``` + +--- + +## ๐Ÿ“Š Understanding Results + +### Demo Output Explained: + +**1. Generation Results:** +``` +Beam 0: score=1394.156, len=3, finished=YES + Tokens: [0, 2, 3] +``` +- Engine returns **all K=4 beams** (doesn't force top-1) +- You can choose beam based on score, length, or other heuristics + +**2. Collapse Stats:** +``` +collapse : 1 (50.0% FLOPs saved) +expand : 1 +``` +- **50% steps use mean pooling** (Layer 2 collapse) +- **50% steps use full DAG** (Layer 1) +- Adaptive: auto-decides based on `cos(h_t, h_{t-1})` + +**3. Mutant Stats:** +``` +mutants : 0 spawned, 0 active +``` +- No OOD signal (beam entropy low) +- If entropy > 1.80 โ†’ mutants will spawn + +**4. Memory:** +``` +Used: 6.20 KB (0.0%) +Allocs: 5 total +``` +- **Zero malloc per step** after init +- All from arena (Layer 0) + +--- + +## ๐ŸŽฏ What's Being Tested? + +### Layer 1: CSR DAG +- โœ… Zero-copy loading +- โœ… Edge iteration +- โœ… Node embeddings +- โœ… AVX2-aligned structures + +### Layer 2: Collapse Gating +- โœ… Cosine similarity gating +- โœ… Mean pooling (COLLAPSE mode) +- โœ… Mutant node spawning +- โœ… Entropy calculation + +### Layer 3: Beam Search +- โœ… K=4 beam tracking +- โœ… Autoregressive expansion +- โœ… AVX2 scoring +- โœ… Terminal detection + +### Layer 4: Unified Engine +- โœ… Session lifecycle +- โœ… Config management +- โœ… Full pipeline orchestration +- โœ… Multi-beam output + +--- + +## ๐Ÿ› If Tests Fail? + +### Compilation Error? +```bash +# Check for AVX2 support +gcc -march=native -dM -E - < /dev/null | grep AVX2 + +# If no AVX2, remove -mavx2 flag +gcc -O3 -std=c99 ... (remove -mavx2) +``` + +### Test DAG Not Found? +```bash +# Unit test auto-creates /tmp/test_engine_dag.bin +# Run test first to create file +cd tests +./test_eh_hgn_engine # Creates /tmp/test_engine_dag.bin + +# Then run demo +cd .. +./hgn_inference_demo /tmp/test_engine_dag.bin 0 +``` + +### Arena OOM? +```c +// Increase arena size in code +EH_Arena *arena = eh_arena_create(1024 * 1024 * 1024); // 1GB +``` + +--- + +## ๐Ÿ“š Next Steps + +1. โœ… **Tests pass** โ†’ Read `HGN_QUICKSTART.md` for integration +2. โœ… **Demo works** โ†’ Read `HGN_MODULE.md` for API details +3. โœ… **Understand architecture** โ†’ Study source code in `src/hgn/` + +**Want to customize?** +- Thresholds: `config.collapse_thresh`, `config.entropy_thresh` +- Beam width: Edit `EH_BEAM_WIDTH` in `eh_beam_search.h` +- Max steps: `config.max_steps = 100;` + +--- + +## ๐ŸŽ“ Understanding the Output + +**When you see:** +``` +collapse : 10 (83.3% FLOPs saved) +expand : 2 +``` + +**It means:** +- 10 steps used **mean pooling O(N)** instead of full DAG traversal +- 2 steps used **full expansion** (context changed significantly) +- **83.3% FLOPs reduction** compared to always-expand baseline + +**This is Layer 2 (Collapse Gating) working!** + +--- + +**Ready to integrate?** โ†’ See `HGN_QUICKSTART.md` +**Want details?** โ†’ See `HGN_MODULE.md` +**Questions?** โ†’ Check source comments in `src/hgn/eh_hgn_engine.c` diff --git a/bench_all.c b/bench_all.c index 1d92bba..3678126 100644 --- a/bench_all.c +++ b/bench_all.c @@ -34,15 +34,15 @@ static inline uint64_t rdtsc(void) { #include #endif -#include "include/eh_arena.h" -#include "include/eh_tokenizer.h" -#include "include/eh_graph.h" -#include "include/eh_scoring.h" -#include "include/eh_dag.h" -#include "include/eh_neuro.h" -#include "include/eh_learning.h" -#include "include/eh_dynamic.h" -#include "include/eh_engine.h" +#include "include/core/eh_arena.h" +#include "include/core/eh_tokenizer.h" +#include "include/core/eh_graph.h" +#include "include/core/eh_scoring.h" +#include "include/core/eh_dag.h" +#include "include/core/eh_neuro.h" +#include "include/core/eh_learning.h" +#include "include/core/eh_dynamic.h" +#include "include/core/eh_engine.h" // ========== Benchmark Configuration ========== #define ARENA_ALLOCS 2000000 diff --git a/examples/Makefile b/examples/Makefile index ce8c54d..9323d2d 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -5,9 +5,8 @@ CFLAGS = -O2 -std=c99 -Wall -Wextra LIBS = -lm INCDIR = ../include -SRCDIR = ../src/core - -CORE_SRCS = $(SRCDIR)/*.c +SRCDIR = ../src +CORE_SRCS = $(SRCDIR)/core/*.c # Example targets EXAMPLES = hello_world arena_demo graph_demo game_ai_npc @@ -18,19 +17,19 @@ all: $(EXAMPLES) @echo "โœ“ All examples built successfully" hello_world: hello_world.c $(CORE_SRCS) - $(CC) $(CFLAGS) -I$(INCDIR) hello_world.c $(CORE_SRCS) -o hello_world $(LIBS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core hello_world.c $(CORE_SRCS) -o hello_world $(LIBS) @echo "โœ“ Built: hello_world" arena_demo: arena_demo.c $(CORE_SRCS) - $(CC) $(CFLAGS) -I$(INCDIR) arena_demo.c $(CORE_SRCS) -o arena_demo $(LIBS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core arena_demo.c $(CORE_SRCS) -o arena_demo $(LIBS) @echo "โœ“ Built: arena_demo" graph_demo: graph_demo.c $(CORE_SRCS) - $(CC) $(CFLAGS) -I$(INCDIR) graph_demo.c $(CORE_SRCS) -o graph_demo $(LIBS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core graph_demo.c $(CORE_SRCS) -o graph_demo $(LIBS) @echo "โœ“ Built: graph_demo" game_ai_npc: game_ai_npc.c $(CORE_SRCS) - $(CC) $(CFLAGS) -I$(INCDIR) game_ai_npc.c $(CORE_SRCS) -o game_ai_npc $(LIBS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core game_ai_npc.c $(CORE_SRCS) -o game_ai_npc $(LIBS) @echo "โœ“ Built: game_ai_npc" clean: diff --git a/examples/game_ai_npc b/examples/game_ai_npc deleted file mode 100644 index 2954fb3..0000000 Binary files a/examples/game_ai_npc and /dev/null differ diff --git a/examples/hgn_inference_demo.c b/examples/hgn_inference_demo.c new file mode 100644 index 0000000..e36a88f --- /dev/null +++ b/examples/hgn_inference_demo.c @@ -0,0 +1,167 @@ +/* ================================================================ + * examples/hgn_inference_demo.c โ€” HGN Unified Engine Demo + * + * Demonstrates full inference pipeline vแป›i tแบฅt cแบฃ 4 layers: + * Layer 1: Load CSR DAG tแปซ binary + * Layer 2: Collapse gating (auto-adaptive) + * Layer 3: Beam search (K=4 beams) + * Layer 4: Unified engine (orchestration) + * + * Compile tแปซ EventHorizon/: + * gcc -O3 -std=c99 -mavx2 -Wall -Wextra -I include \ + * examples/hgn_inference_demo.c \ + * src/hgn/eh_hgn_engine.c \ + * src/hgn/eh_hgn_collapse.c \ + * src/hgn/eh_hgn_dag.c \ + * src/hgn/eh_beam_search.c \ + * src/core/eh_arena.c \ + * -o hgn_inference_demo -lm + * + * Usage: + * ./hgn_inference_demo [token2...] + * + * Example: + * ./hgn_inference_demo model.ehdag 42 17 99 + * ================================================================ */ + +#include "hgn/eh_hgn_engine.h" +#include +#include +#include + +static void print_usage(const char *prog) +{ + fprintf(stderr, "Usage: %s [token2...]\n", prog); + fprintf(stderr, "\n"); + fprintf(stderr, "Example:\n"); + fprintf(stderr, " %s model.ehdag 0 1\n", prog); + fprintf(stderr, "\n"); +} + +int main(int argc, char **argv) +{ + if (argc < 3) { + print_usage(argv[0]); + return 1; + } + + const char *dag_path = argv[1]; + + /* Parse prompt tokens tแปซ command line */ + uint32_t prompt[64]; + uint32_t prompt_len = 0; + for (int i = 2; i < argc && i < 66; i++) { + prompt[prompt_len++] = (uint32_t)atoi(argv[i]); + } + + fprintf(stderr, "=== HGN Inference Demo ===\n"); + fprintf(stderr, "DAG file : %s\n", dag_path); + fprintf(stderr, "Prompt tokens: ["); + for (uint32_t i = 0; i < prompt_len; i++) { + fprintf(stderr, "%u%s", prompt[i], (i < prompt_len - 1) ? ", " : ""); + } + fprintf(stderr, "]\n\n"); + + /* ---- Step 1: Setup arena (512MB) ---- */ + fprintf(stderr, "[1/5] Creating arena...\n"); + EH_Arena *arena = eh_arena_create(512 * 1024 * 1024); + if (!arena) { + fprintf(stderr, "ERROR: Failed to create arena\n"); + return 1; + } + + /* ---- Step 2: Load DAG (Layer 1) ---- */ + fprintf(stderr, "[2/5] Loading DAG from %s...\n", dag_path); + EH_HGN_BaseDag dag; + memset(&dag, 0, sizeof(dag)); + + EH_HGN_Status rc = eh_hgn_dag_load(arena, dag_path, &dag); + if (rc != EH_HGN_OK) { + fprintf(stderr, "ERROR: Failed to load DAG (status=%d)\n", rc); + eh_arena_destroy(arena); + return 1; + } + + fprintf(stderr, " Vocab size : %u\n", dag.vocab_size); + fprintf(stderr, " Total edges: %u\n", dag.total_edges); + fprintf(stderr, " Embed dim : %u\n", dag.embed_dim); + + /* ---- Step 3: Configure engine ---- */ + fprintf(stderr, "[3/5] Configuring inference engine...\n"); + EH_HGN_EngineConfig config = eh_hgn_default_config(); + config.max_steps = 20; /* Limit to 20 steps */ + + fprintf(stderr, " Collapse gating: %s (threshold=%.2f)\n", + config.enable_collapse ? "ON" : "OFF", config.collapse_thresh); + fprintf(stderr, " Mutant nodes : %s (entropy_thresh=%.2f)\n", + config.enable_mutants ? "ON" : "OFF", config.entropy_thresh); + fprintf(stderr, " Max steps : %u\n", config.max_steps); + + /* ---- Step 4: Initialize session (Layers 2,3,4) ---- */ + fprintf(stderr, "[4/5] Initializing inference session...\n"); + EH_HGN_InferenceSession session; + + if (eh_hgn_session_init(&session, &dag, arena, prompt, prompt_len, &config) != 0) { + fprintf(stderr, "ERROR: Failed to initialize session\n"); + eh_arena_destroy(arena); + return 1; + } + + fprintf(stderr, " Session ready!\n\n"); + + /* ---- Step 5: Generation loop ---- */ + fprintf(stderr, "[5/5] Running autoregressive generation...\n"); + fprintf(stderr, "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n"); + + uint32_t step = 0; + while (!eh_hgn_session_is_done(&session)) { + uint32_t active = eh_hgn_session_step(&session); + + const EH_HGN_BeamPath *best = eh_hgn_session_get_best(&session); + if (best) { + fprintf(stderr, "Step %2u: active=%u, best_score=%.3f, seq_len=%u\n", + ++step, active, best->score, best->seq_len); + } + + if (active == 0) { + fprintf(stderr, " โ†’ All beams finished (generation complete)\n"); + break; + } + + if (step >= config.max_steps) { + fprintf(stderr, " โ†’ Max steps reached\n"); + break; + } + } + + fprintf(stderr, "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n\n"); + + /* ---- Results: Print all K beams ---- */ + fprintf(stderr, "=== Generation Results ===\n"); + const EH_HGN_BeamPath *beams = eh_hgn_session_get_beams(&session); + + for (uint32_t i = 0; i < EH_BEAM_WIDTH; i++) { + if (beams[i].seq_len == 0) continue; + + fprintf(stderr, "\nBeam %u: score=%.3f, len=%u, finished=%s\n", + i, beams[i].score, beams[i].seq_len, + beams[i].is_finished ? "YES" : "NO"); + + fprintf(stderr, " Tokens: ["); + for (uint32_t j = 0; j < beams[i].seq_len; j++) { + fprintf(stderr, "%u%s", beams[i].tokens[j], + (j < beams[i].seq_len - 1) ? ", " : ""); + } + fprintf(stderr, "]\n"); + } + + /* ---- Stats ---- */ + fprintf(stderr, "\n"); + eh_hgn_session_dump_stats(&session); + + /* ---- Cleanup ---- */ + eh_arena_destroy(arena); + + fprintf(stderr, "\n=== Demo Complete ===\n"); + return 0; +} diff --git a/include/eh_arena.h b/include/core/eh_arena.h similarity index 100% rename from include/eh_arena.h rename to include/core/eh_arena.h diff --git a/include/eh_dag.h b/include/core/eh_dag.h similarity index 100% rename from include/eh_dag.h rename to include/core/eh_dag.h diff --git a/include/eh_dynamic.h b/include/core/eh_dynamic.h similarity index 100% rename from include/eh_dynamic.h rename to include/core/eh_dynamic.h diff --git a/include/eh_engine.h b/include/core/eh_engine.h similarity index 100% rename from include/eh_engine.h rename to include/core/eh_engine.h diff --git a/include/eh_graph.h b/include/core/eh_graph.h similarity index 100% rename from include/eh_graph.h rename to include/core/eh_graph.h diff --git a/include/eh_learning.h b/include/core/eh_learning.h similarity index 100% rename from include/eh_learning.h rename to include/core/eh_learning.h diff --git a/include/eh_neuro.h b/include/core/eh_neuro.h similarity index 100% rename from include/eh_neuro.h rename to include/core/eh_neuro.h diff --git a/include/eh_scoring.h b/include/core/eh_scoring.h similarity index 89% rename from include/eh_scoring.h rename to include/core/eh_scoring.h index 4f4c830..d573034 100644 --- a/include/eh_scoring.h +++ b/include/core/eh_scoring.h @@ -11,6 +11,11 @@ #include "eh_dag.h" +/* ========================================================= + * CONSTANTS + * ========================================================= */ +#define EH_SCORE_DIM 128 /* Dimensionality for scoring vectors (AVX2-friendly) */ + /* ========================================================= * SCORING CORE STRUCTURE * diff --git a/include/eh_tokenizer.h b/include/core/eh_tokenizer.h similarity index 100% rename from include/eh_tokenizer.h rename to include/core/eh_tokenizer.h diff --git a/include/hgn/eh_adaptive.h b/include/hgn/eh_adaptive.h new file mode 100644 index 0000000..d24bb9b --- /dev/null +++ b/include/hgn/eh_adaptive.h @@ -0,0 +1,132 @@ +/* ================================================================ + * eh_adaptive.h โ€” HGN Adaptive Mechanisms + * + * 1. Collapse Gating: Dynamic computation switching + * - HIGH similarity โ†’ COLLAPSE to O(N) mean pooling + * - LOW similarity โ†’ EXPAND full DAG traversal O(Nร—fanout) + * + * 2. Mutant Nodes: OOD detection & temporary bridging + * - Trigger: beam entropy exceeds threshold + * - Generate: deterministic perturbation via LCG + * - Lifetime: 1 step (auto-cleanup via arena reset) + * ================================================================ */ + +#ifndef EH_ADAPTIVE_H +#define EH_ADAPTIVE_H + +#include "eh_hgn_dag.h" +#include "eh_beam_search.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * Constants + * ---------------------------------------------------------------- */ +#define EH_COLLAPSE_THRESHOLD 0.85f /* cos(h_t, h_{t-1}) > 0.85 โ†’ collapse */ +#define EH_ENTROPY_THRESHOLD 2.5f /* H > 2.5 โ†’ OOD, spawn mutant */ +#define EH_MUTANT_SCALE 0.3f /* Perturbation range [-0.3, +0.3] */ +#define EH_MAX_MUTANTS 16 /* Max concurrent mutants */ + +/* ---------------------------------------------------------------- + * Collapse State + * ---------------------------------------------------------------- */ +typedef enum { + EH_STATE_EXPAND, /* Full DAG traversal */ + EH_STATE_COLLAPSE /* Mean pooling only */ +} EH_CollapseState; + +/* ---------------------------------------------------------------- + * Mutant Node: temporary bridge vector + * ---------------------------------------------------------------- */ +typedef struct { + float vec[EH_HGN_EMBED_DIM]; /* Perturbed embedding */ + uint32_t token_id; /* Virtual token ID (>= vocab_size) */ + uint32_t step_counter; /* Birth step */ + bool active; /* Is this slot in use? */ +} EH_MutantNode; + +/* ---------------------------------------------------------------- + * Adaptive Context: tracks collapse state & mutants + * ---------------------------------------------------------------- */ +typedef struct { + /* Collapse gating */ + float h_prev[EH_HGN_EMBED_DIM]; /* Previous hidden state */ + EH_CollapseState state; /* Current state */ + uint32_t collapse_count; /* Stats: times collapsed */ + uint32_t expand_count; /* Stats: times expanded */ + + /* Mutant management */ + EH_MutantNode mutants[EH_MAX_MUTANTS]; /* Mutant pool */ + uint32_t mutant_count; /* Active mutants */ + uint32_t step_counter; /* Global step counter */ + uint32_t total_mutants_spawned; /* Stats */ + + /* Thresholds */ + float collapse_threshold; + float entropy_threshold; + float mutant_scale; +} EH_AdaptiveContext; + +/* ---------------------------------------------------------------- + * API + * ---------------------------------------------------------------- */ + +/* Initialize adaptive context */ +void eh_adaptive_init(EH_AdaptiveContext *ctx, + float collapse_threshold, + float entropy_threshold, + float mutant_scale); + +/* Collapse Gating Decision: + * Computes cos(h_t, h_{t-1}) vร  quyแบฟt ฤ‘แป‹nh COLLAPSE hay EXPAND + * Returns: EH_STATE_COLLAPSE hoแบทc EH_STATE_EXPAND + */ +EH_CollapseState eh_adaptive_gate(EH_AdaptiveContext *ctx, + const float *h_current); + +/* Mutant Spawning: + * Kiแปƒm tra beam entropy, nแบฟu > threshold โ†’ spawn mutant + * Returns: token_id cแปงa mutant (>= vocab_size) hoแบทc UINT32_MAX nแบฟu khรดng spawn + */ +uint32_t eh_adaptive_try_spawn_mutant(EH_AdaptiveContext *ctx, + const EH_HGN_BeamTracker *tracker, + const float *h_current); + +/* Get mutant embedding by token_id (hoแบทc NULL nแบฟu khรดng tรฌm thแบฅy) */ +const float *eh_adaptive_get_mutant_vec(const EH_AdaptiveContext *ctx, + uint32_t token_id); + +/* Reset mutants (gแปi sau mแป—i sequence hoแบทc arena reset) */ +void eh_adaptive_reset_mutants(EH_AdaptiveContext *ctx); + +/* Print statistics */ +void eh_adaptive_dump_stats(const EH_AdaptiveContext *ctx); + +/* ---------------------------------------------------------------- + * Internal Helpers (exposed for testing) + * ---------------------------------------------------------------- */ + +/* Cosine similarity: cos(a, b) = dot(a,b) / (||a|| ร— ||b||) */ +float eh_adaptive_cosine_similarity(const float *a, const float *b, uint32_t dim); + +/* Compute beam entropy: H = -ฮฃ p_i log(p_i) */ +float eh_adaptive_beam_entropy(const EH_HGN_BeamTracker *tracker); + +/* Linear Congruential Generator (LCG) for deterministic randomness */ +uint32_t eh_adaptive_lcg_next(uint32_t seed); + +/* Generate perturbation vector using LCG */ +void eh_adaptive_generate_perturbation(uint32_t seed, + float scale, + float *out_vec, + uint32_t dim); + +#ifdef __cplusplus +} +#endif + +#endif /* EH_ADAPTIVE_H */ diff --git a/include/hgn/eh_beam_search.h b/include/hgn/eh_beam_search.h new file mode 100644 index 0000000..baed056 --- /dev/null +++ b/include/hgn/eh_beam_search.h @@ -0,0 +1,81 @@ +/* ================================================================ + * eh_beam_search.h โ€” HGN Layer 3: Beam Search Autoregressive + * + * Beam Search vแป›i AVX2-accelerated scoring trรชn CSR DAG. + * Autoregressive generation: mแป—i step mแปŸ rแป™ng K beams tแป‘t nhแบฅt. + * + * EOS Support: Nodes khรดng cรณ edges (sink nodes) tแปฑ ฤ‘แป™ng ฤ‘ฦฐแปฃc coi + * lร  terminal. Khi tแบฅt cแบฃ beams ฤ‘แปu terminal, generation kแบฟt thรบc. + * ================================================================ */ + +#ifndef EH_BEAM_SEARCH_H +#define EH_BEAM_SEARCH_H + +#include "eh_hgn_dag.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * Constants + * ---------------------------------------------------------------- */ +#define EH_BEAM_WIDTH 4 /* Sแป‘ beams giแปฏ lแบกi mแป—i step */ +#define EH_BEAM_MAX_LEN 64 /* Max sequence length */ +#define EH_BEAM_EOS_TOKEN UINT32_MAX /* Sentinel: explicit EOS token */ + +/* ---------------------------------------------------------------- + * Beam Path: mแป™t chuแป—i token candidates + * is_finished = true khi beam ฤ‘รฃ gแบทp node sink (0 edges) hoแบทc EOS. + * ---------------------------------------------------------------- */ +typedef struct { + uint32_t tokens[EH_BEAM_MAX_LEN]; /* Token IDs trong sequence */ + uint32_t seq_len; /* ฤแป™ dร i hiแป‡n tแบกi */ + float score; /* Cumulative score */ + bool is_finished; /* True nแบฟu ฤ‘รฃ gแบทp EOS/sink */ +} EH_HGN_BeamPath; + +/* ---------------------------------------------------------------- + * Beam Tracker: quแบฃn lรฝ tแบฅt cแบฃ beams ฤ‘ang active + * ---------------------------------------------------------------- */ +typedef struct { + EH_HGN_BeamPath paths[EH_BEAM_WIDTH]; /* Active beams (kแปƒ cแบฃ finished) */ + uint32_t active_paths; /* Sแป‘ beams hiแป‡n tแบกi */ + const EH_HGN_BaseDag *dag; /* Reference to DAG */ +} EH_HGN_BeamTracker; + +/* ---------------------------------------------------------------- + * API + * ---------------------------------------------------------------- */ + +/* Initialize beam tracker vแป›i prompt sequence */ +void eh_hgn_beam_init(EH_HGN_BeamTracker *tracker, + const EH_HGN_BaseDag *dag, + const uint32_t *prompt, + uint32_t prompt_len); + +/* Thแปฑc hiแป‡n 1 bฦฐแป›c autoregressive expansion + * - Beams ฤ‘รฃ is_finished=true ฤ‘ฦฐแปฃc giแปฏ nguyรชn (khรดng expand) + * - MแปŸ rแป™ng beams cรฒn active vแป›i cรกc edges tแปซ last token + * - Score = parent_score + edge.prior + dot(edge_weight, context_vec) + * - Giแปฏ lแบกi top-K beams (finished + unfinished, sort by score) + * - Beams gแบทp sink node (fanout=0) tแปฑ ฤ‘แป™ng ฤ‘แบทt is_finished=true + * + * Returns: sแป‘ beams vแบซn ฤ‘ang active (chฦฐa finished). 0 = generation done. + */ +uint32_t eh_hgn_beam_step(EH_HGN_BeamTracker *tracker, + const EH_HGN_BaseDag *dag); + +/* Lแบฅy beam tแป‘t nhแบฅt (highest score, bแบฅt kแปƒ finished hay khรดng) */ +const EH_HGN_BeamPath *eh_hgn_beam_get_best(const EH_HGN_BeamTracker *tracker); + +/* Reset tracker ฤ‘แปƒ bแบฏt ฤ‘แบงu sequence mแป›i */ +void eh_hgn_beam_reset(EH_HGN_BeamTracker *tracker); + +#ifdef __cplusplus +} +#endif + +#endif /* EH_BEAM_SEARCH_H */ diff --git a/include/hgn/eh_hgn_collapse.h b/include/hgn/eh_hgn_collapse.h new file mode 100644 index 0000000..2ac98c6 --- /dev/null +++ b/include/hgn/eh_hgn_collapse.h @@ -0,0 +1,177 @@ +/* ================================================================ + * eh_hgn_collapse.h โ€” HGN Layer 2: Dynamic Collapse Gating + * + * Hai cฦก chแบฟ: + * 1. Collapse Gating: cos(h_t, h_{t-1}) โ†’ quyแบฟt ฤ‘แป‹nh COLLAPSE/EXPAND + * 2. Mutant Node: beam entropy cao โ†’ sinh temporary node tแปซ LCG seed + * + * Phแปฅ thuแป™c: core/eh_arena.h, hgn/eh_hgn_dag.h + * Mรดi trฦฐแปng: C99, gcc -O3 -mavx2 + * ================================================================ */ + +#ifndef EH_HGN_COLLAPSE_H +#define EH_HGN_COLLAPSE_H + +#include "../../include/core/eh_arena.h" +#include "../../include/hgn/eh_hgn_dag.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * Hyperparameters โ€” ฤ‘iแปu chแป‰nh theo domain khi distill + * ---------------------------------------------------------------- */ + +/* cos(h_t, h_{t-1}) > COLLAPSE_THRESH โ†’ context tuyแบฟn tรญnh โ†’ collapse */ +#define EH_HGN_COLLAPSE_THRESH 0.92f + +/* beam entropy (nats) > ENTROPY_THRESH โ†’ OOD โ†’ kรญch hoแบกt mutant */ +#define EH_HGN_ENTROPY_THRESH 1.80f + +/* Scale perturbation vector: Xavier-like = 1/sqrt(dim) */ +#define EH_HGN_PERTURB_SCALE 0.088388f /* 1/sqrt(128) */ + +/* Max mutant nodes sแป‘ng ฤ‘แป“ng thแปi trong 1 inference step */ +#define EH_HGN_MAX_MUTANTS 4u + +/* ---------------------------------------------------------------- + * Collapse decision enum + * ---------------------------------------------------------------- */ +typedef enum { + EH_HGN_EXPAND = 0, /* full DAG traversal */ + EH_HGN_COLLAPSE = 1, /* mean pooling O(N), bแป qua DAG */ +} EH_HGN_CollapseDecision; + +/* ---------------------------------------------------------------- + * EH_HGN_MutantNode: temporary bridging node, sแป‘ng 1 step + * + * Nแบฑm trong EH_Arena โ€” reset O(1) sau mแป—i context step. + * Khรดng cรณ malloc, khรดng cรณ free riรชng lแบป. + * ---------------------------------------------------------------- */ +typedef struct { + float vec[EH_HGN_EMBED_DIM]; /* h_{t-1} + perturbation */ + uint32_t src_token; /* token kรญch hoแบกt mutant */ + uint32_t dst_token; /* token ฤ‘รญch ฤ‘ฦฐแปฃc bridge tแป›i */ + float bridge_score; /* score tแบกm cแปงa nhรกnh nร y */ + bool active; /* cรฒn sแป‘ng trong step nร y khรดng */ +} __attribute__((aligned(32))) EH_HGN_MutantNode; + +/* ---------------------------------------------------------------- + * EH_HGN_CollapseCtx: context state cho 1 inference pass + * + * Caller giแปฏ 1 instance nร y suแป‘t quรก trรฌnh generate. + * Reset bแบฑng eh_hgn_collapse_ctx_reset() khi bแบฏt ฤ‘แบงu prompt mแป›i. + * ---------------------------------------------------------------- */ +typedef struct { + /* --- State tแปซ step trฦฐแป›c --- */ + float h_prev[EH_HGN_EMBED_DIM]; /* h_{t-1}, aligned 32-byte */ + bool h_prev_valid; /* false แปŸ step ฤ‘แบงu tiรชn */ + + /* --- Running stats --- */ + uint32_t step_count; /* sแป‘ steps ฤ‘รฃ xแปญ lรฝ */ + uint32_t collapse_count; /* sแป‘ lแบงn ฤ‘รฃ collapse */ + uint32_t expand_count; /* sแป‘ lแบงn expand full */ + uint32_t mutant_count; /* sแป‘ mutant nodes ฤ‘รฃ sinh */ + + /* --- Mutant pool: arena-backed, lifetime = 1 step --- */ + EH_HGN_MutantNode *mutant_pool; /* trแป vร o EH_Arena */ + uint32_t mutant_active; /* sแป‘ mutants ฤ‘ang sแป‘ng */ + + /* --- Thresholds (copy tแปซ #define, cho phรฉp override) --- */ + float collapse_thresh; + float entropy_thresh; + float perturb_scale; +} __attribute__((aligned(32))) EH_HGN_CollapseCtx; + +/* ---------------------------------------------------------------- + * API + * ---------------------------------------------------------------- */ + +/* KhแปŸi tแบกo CollapseCtx, cแบฅp phรกt mutant_pool tแปซ arena. + * Trแบฃ vแป 0 nแบฟu thร nh cรดng. */ +int eh_hgn_collapse_ctx_init(EH_HGN_CollapseCtx *ctx, + EH_Arena *arena); + +/* Reset state giแปฏa cรกc prompt (giแปฏ pool, xรณa h_prev vร  stats) */ +void eh_hgn_collapse_ctx_reset(EH_HGN_CollapseCtx *ctx); + +/* ---------------------------------------------------------------- + * Core: quyแบฟt ฤ‘แป‹nh COLLAPSE hay EXPAND cho h_t + * + * Logic: + * if (!h_prev_valid) โ†’ EXPAND (step ฤ‘แบงu, chฦฐa cรณ reference) + * cos = dot(h_t, h_prev) / (|h_t| * |h_prev|) + * cos > collapse_thresh โ†’ COLLAPSE + * else โ†’ EXPAND + * + * Side effect: lฦฐu h_t vร o ctx->h_prev cho step tiแบฟp theo. + * ---------------------------------------------------------------- */ +EH_HGN_CollapseDecision eh_hgn_collapse_gate( + EH_HGN_CollapseCtx *ctx, + const float *h_t); /* vector hiแป‡n tแบกi dim=128 */ + +/* ---------------------------------------------------------------- + * Collapse path: mean pooling O(N) thay DAG traversal + * + * Tรญnh trung bรฌnh embedding cแปงa tแบฅt cแบฃ neighbor edges tแปซ token_id, + * ghi kแบฟt quแบฃ vร o out_vec[128]. + * Dรนng khi CollapseDecision == EH_HGN_COLLAPSE. + * ---------------------------------------------------------------- */ +void eh_hgn_collapse_mean_pool( + const EH_HGN_BaseDag *dag, + uint32_t token_id, + float *out_vec); /* [EH_HGN_EMBED_DIM] */ + +/* ---------------------------------------------------------------- + * Beam entropy: ฤ‘o ฤ‘แป™ phรขn vรขn cแปงa beam distribution + * + * H = -ฮฃ p_i * log(p_i) vแป›i p_i = softmax(scores[i]) + * Trแบฃ vแป entropy (nats). Nแบฟu > entropy_thresh โ†’ kรญch hoแบกt mutant. + * ---------------------------------------------------------------- */ +float eh_hgn_beam_entropy(const float *scores, uint32_t count); + +/* ---------------------------------------------------------------- + * Mutant Node: sinh vร  ฤ‘ฤƒng kรฝ vร o ctx->mutant_pool + * + * seed = (uint32_t)(top_score * 1000) XOR step_count + * perturb = LCG(seed) โ†’ vec[128] โˆˆ [-scale, +scale] + * mutant = h_prev + perturb + * + * Trแบฃ vแป con trแป tแป›i mutant node vแปซa sinh, NULL nแบฟu pool ฤ‘แบงy. + * Lifetime: tแปฑ xรณa khi eh_hgn_collapse_step_end() ฤ‘ฦฐแปฃc gแปi. + * ---------------------------------------------------------------- */ +EH_HGN_MutantNode *eh_hgn_mutant_spawn( + EH_HGN_CollapseCtx *ctx, + uint32_t src_token, + uint32_t dst_token, + float top_beam_score); + +/* Kแบฟt thรบc 1 step: deactivate toร n bแป™ mutant nodes hiแป‡n tแบกi */ +void eh_hgn_collapse_step_end(EH_HGN_CollapseCtx *ctx); + +/* Dump stats ra stderr */ +void eh_hgn_collapse_dump_stats(const EH_HGN_CollapseCtx *ctx); + +/* ---------------------------------------------------------------- + * Internal: LCG pseudo-random cho perturbation (khรดng dรนng rand()) + * + * Dรนng Numerical Recipes constants: + * x_{n+1} = 1664525 * x_n + 1013904223 (mod 2^32) + * Trแบฃ vแป float โˆˆ [-1, 1] tแปซ 1 LCG step. + * ---------------------------------------------------------------- */ +static inline float _eh_hgn_lcg_float(uint32_t *state) +{ + *state = 1664525u * (*state) + 1013904223u; + /* Map [0, 2^32) โ†’ [-1, 1] */ + return (float)(int32_t)(*state) / 2147483648.0f; +} + +#ifdef __cplusplus +} +#endif + +#endif /* EH_HGN_COLLAPSE_H */ \ No newline at end of file diff --git a/include/hgn/eh_hgn_dag.h b/include/hgn/eh_hgn_dag.h new file mode 100644 index 0000000..c49f9da --- /dev/null +++ b/include/hgn/eh_hgn_dag.h @@ -0,0 +1,206 @@ +/* ================================================================ + * eh_hgn_dag.h โ€” HGN Layer 1: Base DAG (Compressed Sparse Row) + * + * Namespace : EH_HGN_* (phรขn biแป‡t vแป›i eh_dag.h cแปงa Game AI core) + * Phแปฅ thuแป™c : core/eh_arena.h, core/eh_scoring.h + * Mรดi trฦฐแปng : C99, gcc -O3 -mavx2 + * + * Memory layout (Split โ€” Option B): + * node_embed [V ร— 512B, align-32] โ€” vectors ngแปฏ nghฤฉa + * node_adj [V ร— 8B, align-8 ] โ€” CSR offsets + * edge_compact [E ร— 12B, align-4 ] โ€” dst + prior + weight_idx + * weight_pool [E ร— 512B, align-32] โ€” transition weights AVX2 + * + * Chiแบฟn lฦฐแปฃc scan hai pha: + * Pha 1: duyแป‡t edge_compact (12B/edge) โ†’ filter by prior + * Pha 2: load weight_pool chแป‰ cho top candidates โ†’ AVX2 scoring + * ================================================================ */ + +#ifndef EH_HGN_DAG_H +#define EH_HGN_DAG_H + +#include "../../include/core/eh_arena.h" +#include "../../include/core/eh_scoring.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * Constants + * ---------------------------------------------------------------- */ +#define EH_HGN_VOCAB_SIZE 32768u /* 2^15 โ€” fit L3 cache */ +#define EH_HGN_EMBED_DIM 128u /* = EH_SCORE_DIM */ +#define EH_HGN_MAX_FANOUT 32u /* K: max edges/node */ +#define EH_HGN_MAX_EDGES (EH_HGN_VOCAB_SIZE * EH_HGN_MAX_FANOUT) + +/* Compile-time guard: EMBED_DIM phแบฃi khแป›p vแป›i scoring core */ +_Static_assert(EH_HGN_EMBED_DIM == EH_SCORE_DIM, + "EH_HGN_EMBED_DIM must equal EH_SCORE_DIM (128)"); + +/* ---------------------------------------------------------------- + * File format: ehdag.bin + * + * [EH_HGN_DagFileHeader โ€” 32 bytes ] + * [EH_HGN_NodeEmbed ร— vocab_size โ€” align-32 ] + * [EH_HGN_NodeAdj ร— vocab_size ] + * [EH_HGN_EdgeCompact ร— total_edges ] + * [padding ฤ‘แบฟn 32-byte boundary ] + * [EH_HGN_EdgeWeight ร— total_edges โ€” align-32 ] + * ---------------------------------------------------------------- */ +#define EH_HGN_DAG_MAGIC 0x48474E44u /* "HGND" little-endian */ +#define EH_HGN_DAG_VERSION 1u + +/* ---------------------------------------------------------------- + * Data structures + * ---------------------------------------------------------------- */ + +/* Node embedding: vector 128D, aligned 32-byte cho _mm256_load_ps */ +typedef struct { + float vec[EH_HGN_EMBED_DIM]; +} __attribute__((aligned(32))) EH_HGN_NodeEmbed; + +/* CSR adjacency: offset + count cho edges cแปงa 1 node */ +typedef struct { + uint32_t edge_offset; /* index ฤ‘แบงu trong edge_compact[] */ + uint32_t edge_count; /* sแป‘ edges, โ‰ค EH_HGN_MAX_FANOUT */ +} EH_HGN_NodeAdj; + +/* Edge compact: 12 bytes โ€” dรนng khi scan/filter (khรดng cแบงn weight) + * Thแปฉ tแปฑ field: dst trฦฐแป›c ฤ‘แปƒ branch predict tแป‘t khi filter dst */ +typedef struct { + uint32_t dst; /* token ID ฤ‘รญch */ + float prior; /* scalar prior tแปซ Teacher (co-occur) */ + uint32_t weight_idx; /* index vร o weight_pool[] */ +} EH_HGN_EdgeCompact; /* 12 bytes, no padding */ + +/* Edge weight: 512 bytes, aligned 32-byte โ€” chแป‰ load khi scoring */ +typedef struct { + float vec[EH_HGN_EMBED_DIM]; +} __attribute__((aligned(32))) EH_HGN_EdgeWeight; + +/* File header: 32 bytes chรญnh xรกc */ +typedef struct { + uint32_t magic; /* EH_HGN_DAG_MAGIC */ + uint32_t version; /* EH_HGN_DAG_VERSION */ + uint32_t vocab_size; /* sแป‘ token thแปฑc tแบฟ โ‰ค EH_HGN_VOCAB_SIZE*/ + uint32_t total_edges; /* sแป‘ edges thแปฑc tแบฟ โ‰ค EH_HGN_MAX_EDGES */ + uint32_t embed_dim; /* phแบฃi == EH_HGN_EMBED_DIM */ + uint32_t max_fanout; /* phแบฃi โ‰ค EH_HGN_MAX_FANOUT */ + uint32_t reserved[2]; /* padding cho 32 bytes */ +} EH_HGN_DagFileHeader; /* sizeof == 32 bytes */ + +_Static_assert(sizeof(EH_HGN_DagFileHeader) == 32, + "EH_HGN_DagFileHeader must be 32 bytes"); + +/* ---------------------------------------------------------------- + * EH_HGN_BaseDag: handle toร n bแป™ ฤ‘แป“ thแป‹ + * Sau load(), tแบฅt cแบฃ con trแป trแป vร o EH_Arena โ€” khรดng cรณ heap */ +typedef struct { + uint32_t vocab_size; + uint32_t total_edges; + uint32_t embed_dim; + uint32_t max_fanout; + + EH_HGN_NodeEmbed *node_embed; /* [vocab_size] */ + EH_HGN_NodeAdj *node_adj; /* [vocab_size] */ + EH_HGN_EdgeCompact *edge_compact; /* [total_edges] */ + EH_HGN_EdgeWeight *weight_pool; /* [total_edges] */ +} EH_HGN_BaseDag; + +/* ---------------------------------------------------------------- + * Return codes + * ---------------------------------------------------------------- */ +typedef enum { + EH_HGN_OK = 0, + EH_HGN_ERR_IO = 1, /* khรดng mแปŸ ฤ‘ฦฐแปฃc file */ + EH_HGN_ERR_MAGIC = 2, /* sai magic */ + EH_HGN_ERR_VERSION = 3, /* version khรดng tฦฐฦกng thรญch */ + EH_HGN_ERR_OVERFLOW = 4, /* vocab/edges vฦฐแปฃt compile limits */ + EH_HGN_ERR_ARENA = 5, /* arena OOM */ + EH_HGN_ERR_CORRUPT = 6, /* dแปฏ liแป‡u khรดng nhแบฅt quรกn */ +} EH_HGN_Status; + +/* ---------------------------------------------------------------- + * API โ€” Load / Query + * ---------------------------------------------------------------- */ + +/* Nแบกp Base DAG tแปซ binary file vร o arena. + * Zero-copy: fread thแบณng vร o arena, khรดng malloc/memcpy thรชm. + * Trแบฃ vแป EH_HGN_OK nแบฟu thร nh cรดng. */ +EH_HGN_Status eh_hgn_dag_load(EH_Arena *arena, + const char *path, + EH_HGN_BaseDag *out_dag); + +/* Dump thแป‘ng kรช DAG ra stderr (fan-out distribution, RAM usage) */ +void eh_hgn_dag_dump_info(const EH_HGN_BaseDag *dag); + +/* ---------------------------------------------------------------- + * Inline accessors โ€” hot path, zero function call overhead + * ---------------------------------------------------------------- */ + +/* Embedding vector cแปงa token_id. NULL nแบฟu OOB. */ +static inline const float * +eh_hgn_dag_node_vec(const EH_HGN_BaseDag *dag, uint32_t token_id) +{ + if (__builtin_expect(token_id >= dag->vocab_size, 0)) return NULL; + return dag->node_embed[token_id].vec; +} + +/* Iterator: ฤ‘แบงu danh sรกch edge compact cแปงa node_id. */ +static inline const EH_HGN_EdgeCompact * +eh_hgn_dag_edges_begin(const EH_HGN_BaseDag *dag, uint32_t node_id) +{ + if (__builtin_expect(node_id >= dag->vocab_size, 0)) return NULL; + return dag->edge_compact + dag->node_adj[node_id].edge_offset; +} + +/* Iterator: past-the-end cแปงa edge list node_id. */ +static inline const EH_HGN_EdgeCompact * +eh_hgn_dag_edges_end(const EH_HGN_BaseDag *dag, uint32_t node_id) +{ + if (__builtin_expect(node_id >= dag->vocab_size, 0)) return NULL; + const EH_HGN_NodeAdj *adj = &dag->node_adj[node_id]; + return dag->edge_compact + adj->edge_offset + adj->edge_count; +} + +/* Fan-out thแปฑc tแบฟ cแปงa node_id. */ +static inline uint32_t +eh_hgn_dag_fanout(const EH_HGN_BaseDag *dag, uint32_t node_id) +{ + if (__builtin_expect(node_id >= dag->vocab_size, 0)) return 0; + return dag->node_adj[node_id].edge_count; +} + +/* Weight vector cแปงa mแป™t edge โ€” chแป‰ gแปi sau khi filter xong. + * Dรนng vแป›i eh_scoring_dot_avx2() tแปซ core/eh_scoring.h */ +static inline const float * +eh_hgn_dag_edge_weight(const EH_HGN_BaseDag *dag, + const EH_HGN_EdgeCompact *edge) +{ + return dag->weight_pool[edge->weight_idx].vec; +} + +/* ---------------------------------------------------------------- + * Convenience macro: duyแป‡t toร n bแป™ edges cแปงa node_id + * + * Dรนng: + * EH_HGN_FOR_EDGES(dag, node_id, e) { + * // e lร  const EH_HGN_EdgeCompact* + * } + * ---------------------------------------------------------------- */ +#define EH_HGN_FOR_EDGES(dag, node_id, e) \ + for (const EH_HGN_EdgeCompact \ + *e = eh_hgn_dag_edges_begin((dag), (node_id)), \ + *_e_end_ = eh_hgn_dag_edges_end((dag), (node_id)); \ + e != NULL && e != _e_end_; \ + e++) + +#ifdef __cplusplus +} +#endif + +#endif /* EH_HGN_DAG_H */ \ No newline at end of file diff --git a/include/hgn/eh_hgn_engine.h b/include/hgn/eh_hgn_engine.h new file mode 100644 index 0000000..f84189e --- /dev/null +++ b/include/hgn/eh_hgn_engine.h @@ -0,0 +1,178 @@ +/* ================================================================ + * eh_hgn_engine.h โ€” HGN Layer 4: Unified Inference Engine + * + * Wiring layer kแบฟt nแป‘i tแบฅt cแบฃ HGN components: + * - Layer 1: CSR DAG (eh_hgn_dag) + * - Layer 2: Collapse Gating (eh_hgn_collapse) + * - Layer 3: Beam Search (eh_beam_search) + * + * Mแป—i inference step: + * 1. Beam expansion vแป›i AVX2 scoring + * 2. Collapse gating quyแบฟt ฤ‘แป‹nh COLLAPSE/EXPAND + * 3. Mutant spawning nแบฟu beam entropy cao + * 4. Trแบฃ vแป toร n bแป™ beam array (caller tแปฑ chแปn top-1) + * + * API Design: Stateful session-based, zero malloc per step. + * ================================================================ */ + +#ifndef EH_HGN_ENGINE_H +#define EH_HGN_ENGINE_H + +#include "eh_hgn_dag.h" +#include "eh_hgn_collapse.h" +#include "eh_beam_search.h" +#include "../../include/core/eh_arena.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * Engine Configuration + * ---------------------------------------------------------------- */ +typedef struct { + /* Collapse gating thresholds */ + float collapse_thresh; /* Default: 0.92 */ + float entropy_thresh; /* Default: 1.80 */ + float perturb_scale; /* Default: 0.088 */ + + /* Beam search config */ + uint32_t max_steps; /* Max generation steps (0=unlimited) */ + bool enable_collapse; /* Enable/disable collapse gating */ + bool enable_mutants; /* Enable/disable mutant nodes */ +} EH_HGN_EngineConfig; + +/* ---------------------------------------------------------------- + * Inference Session: toร n bแป™ state cho 1 generation pass + * + * Caller khแปŸi tแบกo session vแป›i prompt, sau ฤ‘รณ gแปi step() lแบทp lแบกi + * cho ฤ‘แบฟn khi generation hoร n thร nh hoแบทc max_steps ฤ‘แบกt. + * ---------------------------------------------------------------- */ +typedef struct { + /* --- Layer references --- */ + const EH_HGN_BaseDag *dag; /* Layer 1: CSR graph */ + EH_HGN_CollapseCtx collapse_ctx; /* Layer 2: Gating state */ + EH_HGN_BeamTracker beam_tracker; /* Layer 3: Beam state */ + + /* --- Session config --- */ + EH_HGN_EngineConfig config; + + /* --- Generation state --- */ + uint32_t step_count; /* Sแป‘ steps ฤ‘รฃ thแปฑc hiแป‡n */ + bool is_finished; /* All beams terminated? */ + + /* --- Temp workspace (aligned) --- */ + float h_current[EH_HGN_EMBED_DIM] __attribute__((aligned(32))); + float h_pooled[EH_HGN_EMBED_DIM] __attribute__((aligned(32))); +} __attribute__((aligned(64))) EH_HGN_InferenceSession; + +/* ---------------------------------------------------------------- + * API: Session Lifecycle + * ---------------------------------------------------------------- */ + +/* KhแปŸi tแบกo engine session vแป›i prompt sequence. + * Arena phแบฃi ฤ‘แปง lแป›n cho DAG + collapse context (ฦฐแป›c tรญnh ~DAG size + 16KB). + * + * Returns: 0 on success, -1 on error. + */ +int eh_hgn_session_init( + EH_HGN_InferenceSession *session, + const EH_HGN_BaseDag *dag, + EH_Arena *arena, + const uint32_t *prompt, + uint32_t prompt_len, + const EH_HGN_EngineConfig *config); /* NULL = use defaults */ + +/* Reset session ฤ‘แปƒ bแบฏt ฤ‘แบงu prompt mแป›i (giแปฏ nguyรชn config vร  arena) */ +void eh_hgn_session_reset( + EH_HGN_InferenceSession *session, + const uint32_t *prompt, + uint32_t prompt_len); + +/* ---------------------------------------------------------------- + * API: Inference Step + * ---------------------------------------------------------------- */ + +/* Thแปฑc hiแป‡n 1 bฦฐแป›c autoregressive generation: + * + * Pipeline: + * 1. Lแบฅy last token cแปงa beam paths hiแป‡n tแบกi + * 2. Tรญnh h_current tแปซ node embedding + * 3. Collapse gate: quyแบฟt ฤ‘แป‹nh COLLAPSE vs EXPAND + * - COLLAPSE: dรนng mean_pool โ†’ h_pooled + * - EXPAND: full DAG traversal vแป›i beam expansion + * 4. Beam expansion vแป›i AVX2 scoring + * 5. Tรญnh beam entropy โ†’ spawn mutants nแบฟu > threshold + * 6. Top-K selection โ†’ update beam_tracker + * 7. Cleanup mutants sau mแป—i step + * + * Returns: + * - Sแป‘ beams cรฒn active (chฦฐa finished) + * - 0 = tแบฅt cแบฃ beams ฤ‘รฃ terminate (generation done) + * - session->is_finished ฤ‘ฦฐแปฃc set = true khi return 0 + */ +uint32_t eh_hgn_session_step(EH_HGN_InferenceSession *session); + +/* ---------------------------------------------------------------- + * API: Results Access + * ---------------------------------------------------------------- */ + +/* Lแบฅy toร n bแป™ beam array hiแป‡n tแบกi (K=4 beams). + * Caller tแปฑ chแปn beam tแป‘t nhแบฅt dแปฑa trรชn score hoแบทc heuristic khรกc. + * + * Returns: pointer to EH_HGN_BeamPath[EH_BEAM_WIDTH], never NULL. + */ +const EH_HGN_BeamPath *eh_hgn_session_get_beams( + const EH_HGN_InferenceSession *session); + +/* Lแบฅy beam tแป‘t nhแบฅt (highest score). + * Shortcut cho eh_hgn_session_get_beams()[0] sau khi sort. + * + * Returns: pointer to best beam, NULL nแบฟu session chฦฐa init. + */ +const EH_HGN_BeamPath *eh_hgn_session_get_best( + const EH_HGN_InferenceSession *session); + +/* Kiแปƒm tra xem generation ฤ‘รฃ hoร n thร nh chฦฐa */ +static inline bool eh_hgn_session_is_done( + const EH_HGN_InferenceSession *session) +{ + return session->is_finished; +} + +/* ---------------------------------------------------------------- + * API: Stats & Debugging + * ---------------------------------------------------------------- */ + +/* Dump toร n bแป™ stats cแปงa session (collapse counts, mutant counts, etc.) */ +void eh_hgn_session_dump_stats(const EH_HGN_InferenceSession *session); + +/* Dump current beam states vแป›i scores vร  sequences */ +void eh_hgn_session_dump_beams(const EH_HGN_InferenceSession *session); + +/* ---------------------------------------------------------------- + * Utilities: Default Config + * ---------------------------------------------------------------- */ + +/* Tแบกo config mแบทc ฤ‘แป‹nh vแป›i cรกc thresholds chuแบฉn */ +static inline EH_HGN_EngineConfig eh_hgn_default_config(void) +{ + EH_HGN_EngineConfig cfg = { + .collapse_thresh = EH_HGN_COLLAPSE_THRESH, /* 0.92 */ + .entropy_thresh = EH_HGN_ENTROPY_THRESH, /* 1.80 */ + .perturb_scale = EH_HGN_PERTURB_SCALE, /* 0.088 */ + .max_steps = 0, /* unlimited */ + .enable_collapse = true, + .enable_mutants = true, + }; + return cfg; +} + +#ifdef __cplusplus +} +#endif + +#endif /* EH_HGN_ENGINE_H */ diff --git a/src/core/eh_arena.c b/src/core/eh_arena.c index aa7bfd8..2fc503a 100644 --- a/src/core/eh_arena.c +++ b/src/core/eh_arena.c @@ -7,7 +7,7 @@ #include #include #include -#include "../../include/eh_arena.h" +#include "../../include/core/eh_arena.h" /* ================================================================= * PART 1: ARENA INITIALIZATION diff --git a/src/core/eh_dag.c b/src/core/eh_dag.c index 537b817..ce2ae56 100644 --- a/src/core/eh_dag.c +++ b/src/core/eh_dag.c @@ -12,7 +12,7 @@ #include #include -#include "../../include/eh_dag.h" +#include "../../include/core/eh_dag.h" /* ================================================================= * PART 1: NODE CREATION diff --git a/src/core/eh_dynamic.c b/src/core/eh_dynamic.c index 2c73f04..bf69220 100644 --- a/src/core/eh_dynamic.c +++ b/src/core/eh_dynamic.c @@ -10,7 +10,7 @@ #include #include -#include "../../include/eh_dynamic.h" +#include "../../include/core/eh_dynamic.h" /* --- Internal: DFS graph traversal to collect nodes and build profiles --- */ static void _eh_dynamic_collect_nodes(EH_DAGNode *node, EH_DynamicProfile *profiles, int *count) { diff --git a/src/core/eh_engine.c b/src/core/eh_engine.c index 79b22ea..d39fec4 100644 --- a/src/core/eh_engine.c +++ b/src/core/eh_engine.c @@ -19,7 +19,7 @@ #include #include #include -#include "../../include/eh_engine.h" +#include "../../include/core/eh_engine.h" /* ================================================================= * INTERNAL: DAG TRAVERSAL FOR COLLAPSE EVALUATION diff --git a/src/core/eh_graph.c b/src/core/eh_graph.c index e936c71..c9349f9 100644 --- a/src/core/eh_graph.c +++ b/src/core/eh_graph.c @@ -3,7 +3,7 @@ * File: src/core/eh_graph.c */ -#include "../../include/eh_graph.h" +#include "../../include/core/eh_graph.h" #include EH_Graph* eh_graph_create(EH_Arena *node_arena, EH_Arena *edge_arena, uint32_t max_nodes, uint32_t max_edges) { diff --git a/src/core/eh_learning.c b/src/core/eh_learning.c index 5909441..1fa7a52 100644 --- a/src/core/eh_learning.c +++ b/src/core/eh_learning.c @@ -8,7 +8,7 @@ #include #include -#include "../../include/eh_learning.h" +#include "../../include/core/eh_learning.h" /* --- Internal: Allocate from Memory Arena (zero fragmentation) --- */ static void *arena_alloc(EH_MemoryArena *arena, size_t size) { diff --git a/src/core/eh_neuro.c b/src/core/eh_neuro.c index 161850a..0c651c5 100644 --- a/src/core/eh_neuro.c +++ b/src/core/eh_neuro.c @@ -24,7 +24,7 @@ #include #include -#include "../../include/eh_neuro.h" +#include "../../include/core/eh_neuro.h" /* ================================================================= * PART 1: NEURO CONTEXT INITIALIZATION diff --git a/src/core/eh_scoring.c b/src/core/eh_scoring.c index 7a9ff63..0f3da51 100644 --- a/src/core/eh_scoring.c +++ b/src/core/eh_scoring.c @@ -17,7 +17,7 @@ #include #include -#include "../../include/eh_scoring.h" +#include "../../include/core/eh_scoring.h" /* ================================================================= * PART 1: SCORING CORE INITIALIZATION diff --git a/src/core/eh_tokenizer.c b/src/core/eh_tokenizer.c index 68fa197..4e224eb 100644 --- a/src/core/eh_tokenizer.c +++ b/src/core/eh_tokenizer.c @@ -20,7 +20,7 @@ #include #include -#include "../../include/eh_tokenizer.h" +#include "../../include/core/eh_tokenizer.h" /* ================================================================= * PART 1: INITIALIZATION diff --git a/src/hgn/eh_adaptive.c b/src/hgn/eh_adaptive.c new file mode 100644 index 0000000..9f2ab6b --- /dev/null +++ b/src/hgn/eh_adaptive.c @@ -0,0 +1,248 @@ +/* ================================================================ + * eh_adaptive.c โ€” HGN Adaptive Mechanisms Implementation + * ================================================================ */ + +#include "../../include/hgn/eh_adaptive.h" +#include +#include +#include + +/* ---------------------------------------------------------------- + * Cosine Similarity: cos(a, b) = dot(a,b) / (||a|| ร— ||b||) + * ---------------------------------------------------------------- */ +float eh_adaptive_cosine_similarity(const float *a, const float *b, uint32_t dim) +{ + float dot = 0.0f, norm_a = 0.0f, norm_b = 0.0f; + + for (uint32_t i = 0; i < dim; i++) { + dot += a[i] * b[i]; + norm_a += a[i] * a[i]; + norm_b += b[i] * b[i]; + } + + norm_a = sqrtf(norm_a); + norm_b = sqrtf(norm_b); + + if (norm_a < 1e-8f || norm_b < 1e-8f) return 0.0f; + + return dot / (norm_a * norm_b); +} + +/* ---------------------------------------------------------------- + * Beam Entropy: H = -ฮฃ p_i log(p_i) + * Normalize scores to probabilities first via softmax + * ---------------------------------------------------------------- */ +float eh_adaptive_beam_entropy(const EH_HGN_BeamTracker *tracker) +{ + if (tracker->active_paths == 0) return 0.0f; + + /* Find max score for numerical stability */ + float max_score = tracker->paths[0].score; + for (uint32_t i = 1; i < tracker->active_paths; i++) { + if (tracker->paths[i].score > max_score) { + max_score = tracker->paths[i].score; + } + } + + /* Compute softmax probabilities */ + float probs[EH_BEAM_WIDTH]; + float sum = 0.0f; + + for (uint32_t i = 0; i < tracker->active_paths; i++) { + probs[i] = expf(tracker->paths[i].score - max_score); + sum += probs[i]; + } + + if (sum < 1e-8f) return 0.0f; + + /* Normalize and compute entropy */ + float entropy = 0.0f; + for (uint32_t i = 0; i < tracker->active_paths; i++) { + probs[i] /= sum; + if (probs[i] > 1e-8f) { + entropy -= probs[i] * logf(probs[i]); + } + } + + return entropy; +} + +/* ---------------------------------------------------------------- + * Linear Congruential Generator (LCG) + * Constants from Numerical Recipes + * ---------------------------------------------------------------- */ +uint32_t eh_adaptive_lcg_next(uint32_t seed) +{ + return (1664525u * seed + 1013904223u); +} + +/* ---------------------------------------------------------------- + * Generate Perturbation Vector + * Uses LCG for deterministic randomness in range [-scale, +scale] + * ---------------------------------------------------------------- */ +void eh_adaptive_generate_perturbation(uint32_t seed, + float scale, + float *out_vec, + uint32_t dim) +{ + uint32_t rng_state = seed; + + for (uint32_t i = 0; i < dim; i++) { + rng_state = eh_adaptive_lcg_next(rng_state); + + /* Map uint32 [0, 2^32-1] โ†’ float [-1, +1] */ + float normalized = (float)rng_state / (float)UINT32_MAX; + normalized = normalized * 2.0f - 1.0f; /* [-1, +1] */ + + out_vec[i] = normalized * scale; + } +} + +/* ---------------------------------------------------------------- + * Initialize Adaptive Context + * ---------------------------------------------------------------- */ +void eh_adaptive_init(EH_AdaptiveContext *ctx, + float collapse_threshold, + float entropy_threshold, + float mutant_scale) +{ + memset(ctx, 0, sizeof(*ctx)); + + ctx->state = EH_STATE_EXPAND; + ctx->collapse_threshold = collapse_threshold; + ctx->entropy_threshold = entropy_threshold; + ctx->mutant_scale = mutant_scale; +} + +/* ---------------------------------------------------------------- + * Collapse Gating Decision + * ---------------------------------------------------------------- */ +EH_CollapseState eh_adaptive_gate(EH_AdaptiveContext *ctx, + const float *h_current) +{ + /* First step: always EXPAND, save h_current as h_prev */ + if (ctx->step_counter == 0) { + memcpy(ctx->h_prev, h_current, sizeof(ctx->h_prev)); + ctx->state = EH_STATE_EXPAND; + ctx->expand_count++; + ctx->step_counter++; + return EH_STATE_EXPAND; + } + + /* Compute cosine similarity */ + float cos_sim = eh_adaptive_cosine_similarity( + h_current, ctx->h_prev, EH_HGN_EMBED_DIM); + + /* Gating decision */ + if (cos_sim > ctx->collapse_threshold) { + ctx->state = EH_STATE_COLLAPSE; + ctx->collapse_count++; + } else { + ctx->state = EH_STATE_EXPAND; + ctx->expand_count++; + } + + /* Update h_prev */ + memcpy(ctx->h_prev, h_current, sizeof(ctx->h_prev)); + ctx->step_counter++; + + return ctx->state; +} + +/* ---------------------------------------------------------------- + * Try Spawn Mutant + * ---------------------------------------------------------------- */ +uint32_t eh_adaptive_try_spawn_mutant(EH_AdaptiveContext *ctx, + const EH_HGN_BeamTracker *tracker, + const float *h_current) +{ + /* Check if mutant pool is full */ + if (ctx->mutant_count >= EH_MAX_MUTANTS) { + return UINT32_MAX; + } + + /* Compute beam entropy */ + float entropy = eh_adaptive_beam_entropy(tracker); + + /* Check threshold */ + if (entropy <= ctx->entropy_threshold) { + return UINT32_MAX; + } + + /* Find free slot */ + uint32_t slot = 0; + for (slot = 0; slot < EH_MAX_MUTANTS; slot++) { + if (!ctx->mutants[slot].active) break; + } + + if (slot >= EH_MAX_MUTANTS) return UINT32_MAX; + + /* Generate deterministic seed */ + uint32_t top_score_quantized = (uint32_t)(tracker->paths[0].score * 1000.0f); + uint32_t seed = top_score_quantized ^ ctx->step_counter; + + /* Generate perturbation */ + float perturbation[EH_HGN_EMBED_DIM]; + eh_adaptive_generate_perturbation(seed, ctx->mutant_scale, + perturbation, EH_HGN_EMBED_DIM); + + /* Create mutant: h_{t-1} + perturbation */ + EH_MutantNode *mutant = &ctx->mutants[slot]; + for (uint32_t i = 0; i < EH_HGN_EMBED_DIM; i++) { + mutant->vec[i] = h_current[i] + perturbation[i]; + } + + /* Assign virtual token_id >= vocab_size */ + mutant->token_id = 1000000u + ctx->total_mutants_spawned; + mutant->step_counter = ctx->step_counter; + mutant->active = true; + + ctx->mutant_count++; + ctx->total_mutants_spawned++; + + return mutant->token_id; +} + +/* ---------------------------------------------------------------- + * Get Mutant Vector + * ---------------------------------------------------------------- */ +const float *eh_adaptive_get_mutant_vec(const EH_AdaptiveContext *ctx, + uint32_t token_id) +{ + for (uint32_t i = 0; i < EH_MAX_MUTANTS; i++) { + if (ctx->mutants[i].active && ctx->mutants[i].token_id == token_id) { + return ctx->mutants[i].vec; + } + } + return NULL; +} + +/* ---------------------------------------------------------------- + * Reset Mutants (arena context reset) + * ---------------------------------------------------------------- */ +void eh_adaptive_reset_mutants(EH_AdaptiveContext *ctx) +{ + for (uint32_t i = 0; i < EH_MAX_MUTANTS; i++) { + ctx->mutants[i].active = false; + } + ctx->mutant_count = 0; +} + +/* ---------------------------------------------------------------- + * Dump Statistics + * ---------------------------------------------------------------- */ +void eh_adaptive_dump_stats(const EH_AdaptiveContext *ctx) +{ + fprintf(stderr, "\n=== EH_Adaptive Statistics ===\n"); + fprintf(stderr, " Steps total : %u\n", ctx->step_counter); + fprintf(stderr, " Collapse count : %u (%.1f%%)\n", + ctx->collapse_count, + ctx->step_counter ? 100.0f * ctx->collapse_count / ctx->step_counter : 0.0f); + fprintf(stderr, " Expand count : %u (%.1f%%)\n", + ctx->expand_count, + ctx->step_counter ? 100.0f * ctx->expand_count / ctx->step_counter : 0.0f); + fprintf(stderr, " Mutants spawned : %u\n", ctx->total_mutants_spawned); + fprintf(stderr, " Mutants active : %u / %u\n", + ctx->mutant_count, EH_MAX_MUTANTS); + fprintf(stderr, "==============================\n\n"); +} diff --git a/src/hgn/eh_beam_search.c b/src/hgn/eh_beam_search.c new file mode 100644 index 0000000..a733ca1 --- /dev/null +++ b/src/hgn/eh_beam_search.c @@ -0,0 +1,209 @@ +/* ================================================================ + * eh_beam_search.c โ€” HGN Layer 3: Beam Search Implementation + * + * Fix log: + * v1.1 - Fixed parent beam tracking bug: ScoredCandidate now + * stores parent_beam_idx so new paths are built from the + * correct parent sequence (not always paths[0]). + * - Added EOS/sink detection: nodes with 0 outgoing edges + * automatically set is_finished=true on the beam. + * ================================================================ */ + +#include "../../include/hgn/eh_beam_search.h" +#include +#include +#include + +/* ---------------------------------------------------------------- + * Internal: Candidate pool entry. + * Tracks which parent beam generated this candidate so we can + * copy the correct token history when building new paths. + * ---------------------------------------------------------------- */ +typedef struct { + uint32_t token_id; /* The candidate next token */ + float score; /* Cumulative score (parent + this edge) */ + uint32_t parent_beam_idx; /* Which beam in tracker->paths spawned */ + bool is_terminal; /* True if dst node is a sink (no edges) */ +} ScoredCandidate; + +static int compare_candidates(const void *a, const void *b) { + const ScoredCandidate *ca = (const ScoredCandidate *)a; + const ScoredCandidate *cb = (const ScoredCandidate *)b; + if (ca->score > cb->score) return -1; /* Descending */ + if (ca->score < cb->score) return 1; + return 0; +} + +/* ---------------------------------------------------------------- + * Internal: simple 128D dot product + * (Replace with AVX2 intrinsics for production hot path) + * ---------------------------------------------------------------- */ +static float dot_product_128(const float *a, const float *b) { + float sum = 0.0f; + for (int i = 0; i < EH_HGN_EMBED_DIM; i++) { + sum += a[i] * b[i]; + } + return sum; +} + +/* ---------------------------------------------------------------- + * eh_hgn_beam_init + * ---------------------------------------------------------------- */ +void eh_hgn_beam_init(EH_HGN_BeamTracker *tracker, + const EH_HGN_BaseDag *dag, + const uint32_t *prompt, + uint32_t prompt_len) +{ + memset(tracker, 0, sizeof(*tracker)); + tracker->dag = dag; + tracker->active_paths = 1; + + /* Initialize first beam with prompt */ + EH_HGN_BeamPath *path = &tracker->paths[0]; + path->seq_len = 0; + path->score = 0.0f; + path->is_finished = false; + + for (uint32_t i = 0; i < prompt_len && i < EH_BEAM_MAX_LEN; i++) { + path->tokens[path->seq_len++] = prompt[i]; + } +} + +/* ---------------------------------------------------------------- + * eh_hgn_beam_step + * + * Key fix: ScoredCandidate stores parent_beam_idx. + * When we build new_paths[i], we memcpy from paths[parent_beam_idx] + * instead of always copying paths[0]. + * ---------------------------------------------------------------- */ +uint32_t eh_hgn_beam_step(EH_HGN_BeamTracker *tracker, + const EH_HGN_BaseDag *dag) +{ + if (tracker->active_paths == 0) return 0; + + /* Count how many beams still need expansion */ + uint32_t still_active = 0; + for (uint32_t b = 0; b < tracker->active_paths; b++) { + if (!tracker->paths[b].is_finished) still_active++; + } + if (still_active == 0) return 0; /* All done */ + + /* -------------------------------------------------------- + * Candidate pool: each unfinished beam expands its edges. + * Finished beams are carried over directly. + * -------------------------------------------------------- */ + #define MAX_CANDIDATES (EH_BEAM_WIDTH * EH_HGN_MAX_FANOUT + EH_BEAM_WIDTH) + ScoredCandidate candidates[MAX_CANDIDATES]; + uint32_t cand_count = 0; + + for (uint32_t b = 0; b < tracker->active_paths; b++) { + EH_HGN_BeamPath *beam = &tracker->paths[b]; + + /* --- Finished beams: carry forward as-is (no new token) --- */ + if (beam->is_finished) { + if (cand_count < MAX_CANDIDATES) { + candidates[cand_count].token_id = EH_BEAM_EOS_TOKEN; /* sentinel */ + candidates[cand_count].score = beam->score; + candidates[cand_count].parent_beam_idx = b; + candidates[cand_count].is_terminal = true; + cand_count++; + } + continue; + } + + if (beam->seq_len >= EH_BEAM_MAX_LEN) continue; + + /* Last token context for scoring */ + uint32_t last_token = beam->tokens[beam->seq_len - 1]; + const float *ctx_vec = eh_hgn_dag_node_vec(dag, last_token); + if (!ctx_vec) continue; + + uint32_t fanout = eh_hgn_dag_fanout(dag, last_token); + + /* ---- Sink node: no edges โ†’ mark terminal, carry forward ---- */ + if (fanout == 0) { + if (cand_count < MAX_CANDIDATES) { + candidates[cand_count].token_id = EH_BEAM_EOS_TOKEN; + candidates[cand_count].score = beam->score; + candidates[cand_count].parent_beam_idx = b; + candidates[cand_count].is_terminal = true; + cand_count++; + } + continue; + } + + /* ---- Normal expansion: score each outgoing edge ---- */ + EH_HGN_FOR_EDGES(dag, last_token, edge) { + if (cand_count >= MAX_CANDIDATES) break; + + const float *w = eh_hgn_dag_edge_weight(dag, edge); + float ctx_score = dot_product_128(w, ctx_vec); + float total_score = beam->score + edge->prior + ctx_score; + + /* Check if destination is a sink (pre-check for next step) */ + bool dst_is_sink = (eh_hgn_dag_fanout(dag, edge->dst) == 0); + + candidates[cand_count].token_id = edge->dst; + candidates[cand_count].score = total_score; + candidates[cand_count].parent_beam_idx = b; /* <-- FIX */ + candidates[cand_count].is_terminal = dst_is_sink; + cand_count++; + } + } + + /* Sort candidates descending by score */ + qsort(candidates, cand_count, sizeof(ScoredCandidate), compare_candidates); + + /* Keep top-K beams */ + uint32_t new_beam_count = (cand_count < EH_BEAM_WIDTH) ? cand_count : EH_BEAM_WIDTH; + + EH_HGN_BeamPath new_paths[EH_BEAM_WIDTH]; + memset(new_paths, 0, sizeof(new_paths)); + + uint32_t active_after = 0; + + for (uint32_t i = 0; i < new_beam_count; i++) { + uint32_t pid = candidates[i].parent_beam_idx; /* <-- FIX: correct parent */ + + /* Copy full token history from the correct parent beam */ + memcpy(&new_paths[i], &tracker->paths[pid], sizeof(EH_HGN_BeamPath)); + + new_paths[i].score = candidates[i].score; + new_paths[i].is_finished = candidates[i].is_terminal; + + /* Append new token only if not a sentinel */ + if (candidates[i].token_id != EH_BEAM_EOS_TOKEN) { + if (new_paths[i].seq_len < EH_BEAM_MAX_LEN) { + new_paths[i].tokens[new_paths[i].seq_len] = candidates[i].token_id; + new_paths[i].seq_len++; + } + } + + if (!new_paths[i].is_finished) active_after++; + } + + /* Commit new paths to tracker */ + memcpy(tracker->paths, new_paths, new_beam_count * sizeof(EH_HGN_BeamPath)); + tracker->active_paths = new_beam_count; + + return active_after; + #undef MAX_CANDIDATES +} + +/* ---------------------------------------------------------------- + * eh_hgn_beam_get_best + * After sorting, paths[0] is always the highest-scoring beam. + * ---------------------------------------------------------------- */ +const EH_HGN_BeamPath *eh_hgn_beam_get_best(const EH_HGN_BeamTracker *tracker) +{ + if (tracker->active_paths == 0) return NULL; + return &tracker->paths[0]; +} + +/* ---------------------------------------------------------------- + * eh_hgn_beam_reset + * ---------------------------------------------------------------- */ +void eh_hgn_beam_reset(EH_HGN_BeamTracker *tracker) +{ + memset(tracker, 0, sizeof(*tracker)); +} diff --git a/src/hgn/eh_hgn_collapse.c b/src/hgn/eh_hgn_collapse.c new file mode 100644 index 0000000..5c2a0aa --- /dev/null +++ b/src/hgn/eh_hgn_collapse.c @@ -0,0 +1,285 @@ +/* ================================================================ + * eh_hgn_collapse.c โ€” HGN Layer 2: Dynamic Collapse Gating impl + * + * Compile tแปซ EventHorizon/: + * gcc -O3 -std=c99 -mavx2 -Wall -Wextra -I include \ + * -c src/hgn/eh_hgn_collapse.c -o src/hgn/eh_hgn_collapse.o + * ================================================================ */ + +#include "../../include/hgn/eh_hgn_collapse.h" + +#include +#include +#include + +/* ---------------------------------------------------------------- + * AVX2 dot product nแป™i bแป™ โ€” khรดng phแปฅ thuแป™c eh_scoring.c + * Dรนng cho cosine similarity trรชn hot path gating + * ---------------------------------------------------------------- */ +/* dot128: scalar vแป›i double accumulator โ€” gcc -O3 auto-vectorize + * Trรกnh dรนng AVX2 intrinsic trแปฑc tiแบฟp ฤ‘แปƒ khรดng phแปฅ thuแป™c alignment + * vร  khรดng conflict vแป›i attribute target cแปงa eh_scoring.c */ +static float _dot128(const float * restrict a, const float * restrict b) +{ + double acc = 0.0; + for (int i = 0; i < 128; i++) acc += (double)a[i] * (double)b[i]; + return (float)acc; +} + +/* Norm bรฌnh phฦฐฦกng cแปงa vector 128D */ +static float _norm_sq128(const float *v) +{ + return _dot128(v, v); +} + +/* ---------------------------------------------------------------- + * eh_hgn_collapse_ctx_init + * ---------------------------------------------------------------- */ +int eh_hgn_collapse_ctx_init(EH_HGN_CollapseCtx *ctx, + EH_Arena *arena) +{ + if (!ctx || !arena) return -1; + + memset(ctx, 0, sizeof(*ctx)); + + /* Cแบฅp phรกt mutant_pool tแปซ arena โ€” lifetime = toร n inference pass */ + size_t pool_size = EH_HGN_MAX_MUTANTS * sizeof(EH_HGN_MutantNode); + ctx->mutant_pool = (EH_HGN_MutantNode *)eh_arena_alloc(arena, pool_size); + if (!ctx->mutant_pool) { + fprintf(stderr, "[eh_hgn_collapse] Arena OOM for mutant_pool\n"); + return -1; + } + memset(ctx->mutant_pool, 0, pool_size); + + /* Copy defaults tแปซ compile-time constants โ€” cho phรฉp override */ + ctx->collapse_thresh = EH_HGN_COLLAPSE_THRESH; + ctx->entropy_thresh = EH_HGN_ENTROPY_THRESH; + ctx->perturb_scale = EH_HGN_PERTURB_SCALE; + ctx->h_prev_valid = false; + + return 0; +} + +/* ---------------------------------------------------------------- + * eh_hgn_collapse_ctx_reset + * ---------------------------------------------------------------- */ +void eh_hgn_collapse_ctx_reset(EH_HGN_CollapseCtx *ctx) +{ + if (!ctx) return; + + /* Giแปฏ lแบกi: mutant_pool pointer, thresholds */ + /* Reset: state, stats, h_prev */ + memset(ctx->h_prev, 0, sizeof(ctx->h_prev)); + ctx->h_prev_valid = false; + ctx->step_count = 0; + ctx->collapse_count = 0; + ctx->expand_count = 0; + ctx->mutant_count = 0; + ctx->mutant_active = 0; + + if (ctx->mutant_pool) + memset(ctx->mutant_pool, 0, + EH_HGN_MAX_MUTANTS * sizeof(EH_HGN_MutantNode)); +} + +/* ---------------------------------------------------------------- + * eh_hgn_collapse_gate + * + * Cosine similarity: cos(h_t, h_{t-1}) = dot / (|h_t| * |h_{t-1}|) + * Dรนng AVX2 dot product 2 lแบงn (dot + 2 norms) โ€” ~6 ns trรชn x86 + * ---------------------------------------------------------------- */ +EH_HGN_CollapseDecision eh_hgn_collapse_gate( + EH_HGN_CollapseCtx *ctx, + const float *h_t) +{ + EH_HGN_CollapseDecision decision; + + if (!ctx->h_prev_valid) { + /* Step ฤ‘แบงu tiรชn โ€” chฦฐa cรณ reference vector โ†’ expand */ + decision = EH_HGN_EXPAND; + } else { + /* Tรญnh cosine similarity giแปฏa h_t vร  h_prev */ + float dot = _dot128(h_t, ctx->h_prev); + float norm_t = _norm_sq128(h_t); + float norm_p = _norm_sq128(ctx->h_prev); + + float denom = sqrtf(norm_t * norm_p); + + /* Trรกnh chia cho 0 khi vector zero */ + float cosine = (denom > 1e-8f) ? (dot / denom) : 0.0f; + + decision = (cosine > ctx->collapse_thresh) + ? EH_HGN_COLLAPSE + : EH_HGN_EXPAND; + } + + /* Cแบญp nhแบญt stats vร  lฦฐu h_t cho step tiแบฟp theo */ + if (decision == EH_HGN_COLLAPSE) ctx->collapse_count++; + else ctx->expand_count++; + + ctx->step_count++; + memcpy(ctx->h_prev, h_t, EH_HGN_EMBED_DIM * sizeof(float)); + ctx->h_prev_valid = true; + + return decision; +} + +/* ---------------------------------------------------------------- + * eh_hgn_collapse_mean_pool + * + * Tรญnh mean cแปงa tแบฅt cแบฃ edge weight vectors cแปงa token_id. + * O(K ร— D) vแป›i K โ‰ค 32, D = 128 โ€” rแบฅt nhแบน. + * Nแบฟu node khรดng cรณ edges โ†’ trแบฃ vแป node embedding cแปงa token_id. + * ---------------------------------------------------------------- */ +void eh_hgn_collapse_mean_pool( + const EH_HGN_BaseDag *dag, + uint32_t token_id, + float *out_vec) +{ + memset(out_vec, 0, EH_HGN_EMBED_DIM * sizeof(float)); + + uint32_t count = eh_hgn_dag_fanout(dag, token_id); + if (count == 0) { + /* Sink node โ€” trแบฃ vแป embedding cแปงa chรญnh nรณ */ + const float *nv = eh_hgn_dag_node_vec(dag, token_id); + if (nv) memcpy(out_vec, nv, EH_HGN_EMBED_DIM * sizeof(float)); + return; + } + + /* Tรญch lลฉy tแบฅt cแบฃ weight vectors cแปงa cรกc edges */ + EH_HGN_FOR_EDGES(dag, token_id, e) { + const float *wv = eh_hgn_dag_edge_weight(dag, e); + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + out_vec[k] += wv[k]; + } + + /* Chia trung bรฌnh */ + float inv_count = 1.0f / (float)count; + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + out_vec[k] *= inv_count; +} + +/* ---------------------------------------------------------------- + * eh_hgn_beam_entropy + * + * H = -ฮฃ p_i * log(p_i) vแป›i p_i = softmax(scores) + * Dรนng log-sum-exp trick ฤ‘แปƒ trรกnh overflow: + * log(ฮฃ exp(s_i)) = max + log(ฮฃ exp(s_i - max)) + * ---------------------------------------------------------------- */ +float eh_hgn_beam_entropy(const float *scores, uint32_t count) +{ + if (!scores || count == 0) return 0.0f; + if (count == 1) return 0.0f; /* deterministic */ + + /* Tรฌm max ฤ‘แปƒ log-sum-exp */ + float max_s = scores[0]; + for (uint32_t i = 1; i < count; i++) + if (scores[i] > max_s) max_s = scores[i]; + + /* Tรญnh log-partition Z */ + double Z = 0.0; + for (uint32_t i = 0; i < count; i++) + Z += exp((double)(scores[i] - max_s)); + (void)(log(Z)); + + /* H = log_Z - (1/Z) * ฮฃ scores[i] * exp(scores[i] - max) */ + double H = 0.0; + for (uint32_t i = 0; i < count; i++) { + double p = exp((double)(scores[i] - max_s)) / Z; + if (p > 1e-12) H -= p * log(p); + } + + return (float)H; +} + +/* ---------------------------------------------------------------- + * eh_hgn_mutant_spawn + * + * seed = (uint32_t)(top_beam_score * 1000.0) XOR step_count + * perturb = LCG(seed) * perturb_scale cho mแป—i chiแปu + * mutant = h_prev + perturb + * ---------------------------------------------------------------- */ +EH_HGN_MutantNode *eh_hgn_mutant_spawn( + EH_HGN_CollapseCtx *ctx, + uint32_t src_token, + uint32_t dst_token, + float top_beam_score) +{ + if (!ctx || !ctx->mutant_pool) return NULL; + + /* Tรฌm slot trแป‘ng trong pool */ + EH_HGN_MutantNode *slot = NULL; + for (uint32_t i = 0; i < EH_HGN_MAX_MUTANTS; i++) { + if (!ctx->mutant_pool[i].active) { + slot = &ctx->mutant_pool[i]; + break; + } + } + if (!slot) { + fprintf(stderr, "[eh_hgn_collapse] Mutant pool full (%u slots)\n", + EH_HGN_MAX_MUTANTS); + return NULL; + } + + /* Seed tแปซ top_beam_score XOR step_count โ†’ deterministic nhฦฐng + * khรกc nhau mแป—i step vร  mแป—i score */ + uint32_t score_bits; + memcpy(&score_bits, &top_beam_score, sizeof(float)); + uint32_t seed = score_bits ^ ctx->step_count ^ (src_token << 16); + + /* Sinh perturbation vector bแบฑng LCG, scale Xavier */ + float scale = ctx->perturb_scale; + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) { + float perturb = _eh_hgn_lcg_float(&seed) * scale; + slot->vec[k] = ctx->h_prev[k] + perturb; + } + + slot->src_token = src_token; + slot->dst_token = dst_token; + slot->bridge_score = top_beam_score; + slot->active = true; + + ctx->mutant_active++; + ctx->mutant_count++; + + return slot; +} + +/* ---------------------------------------------------------------- + * eh_hgn_collapse_step_end: deactivate tแบฅt cแบฃ mutants sau 1 step + * ---------------------------------------------------------------- */ +void eh_hgn_collapse_step_end(EH_HGN_CollapseCtx *ctx) +{ + if (!ctx || !ctx->mutant_pool) return; + for (uint32_t i = 0; i < EH_HGN_MAX_MUTANTS; i++) + ctx->mutant_pool[i].active = false; + ctx->mutant_active = 0; +} + +/* ---------------------------------------------------------------- + * eh_hgn_collapse_dump_stats + * ---------------------------------------------------------------- */ +void eh_hgn_collapse_dump_stats(const EH_HGN_CollapseCtx *ctx) +{ + if (!ctx) { fprintf(stderr, "[eh_hgn_collapse] NULL ctx\n"); return; } + + uint32_t total = ctx->collapse_count + ctx->expand_count; + float collapse_pct = total > 0 + ? 100.0f * (float)ctx->collapse_count / (float)total + : 0.0f; + + fprintf(stderr, + "=== EH_HGN_CollapseCtx Stats ===\n" + " steps : %u\n" + " collapse : %u (%.1f%% FLOPs saved)\n" + " expand : %u\n" + " mutants : %u spawned, %u active\n" + " thresh : collapse=%.3f entropy=%.3f\n" + "================================\n", + ctx->step_count, + ctx->collapse_count, collapse_pct, + ctx->expand_count, + ctx->mutant_count, ctx->mutant_active, + ctx->collapse_thresh, ctx->entropy_thresh + ); +} \ No newline at end of file diff --git a/src/hgn/eh_hgn_dag.c b/src/hgn/eh_hgn_dag.c new file mode 100644 index 0000000..a23f058 --- /dev/null +++ b/src/hgn/eh_hgn_dag.c @@ -0,0 +1,210 @@ +/* ================================================================ + * eh_hgn_dag.c โ€” HGN Layer 1: Base DAG implementation + * + * Compile: gcc -O3 -std=c99 -mavx2 -Wall -Wextra \ + * -I../../include -c eh_hgn_dag.c -o eh_hgn_dag.o + * ================================================================ */ + +#define _POSIX_C_SOURCE 200112L + +#include "../../include/hgn/eh_hgn_dag.h" + +#include +#include +#include + +/* ---------------------------------------------------------------- + * eh_hgn_dag_load + * ---------------------------------------------------------------- */ +EH_HGN_Status eh_hgn_dag_load(EH_Arena *arena, + const char *path, + EH_HGN_BaseDag *out_dag) +{ + FILE *fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "[eh_hgn_dag] Cannot open '%s': %s\n", + path, strerror(errno)); + return EH_HGN_ERR_IO; + } + + /* -- ฤแปc vร  validate file header (32 bytes) -- */ + EH_HGN_DagFileHeader hdr; + if (fread(&hdr, sizeof(hdr), 1, fp) != 1) { + fprintf(stderr, "[eh_hgn_dag] Header read failed\n"); + fclose(fp); return EH_HGN_ERR_CORRUPT; + } + + if (hdr.magic != EH_HGN_DAG_MAGIC) { + fprintf(stderr, "[eh_hgn_dag] Bad magic: 0x%08X\n", hdr.magic); + fclose(fp); return EH_HGN_ERR_MAGIC; + } + if (hdr.version != EH_HGN_DAG_VERSION) { + fprintf(stderr, "[eh_hgn_dag] Version mismatch: %u\n", hdr.version); + fclose(fp); return EH_HGN_ERR_VERSION; + } + if (hdr.vocab_size > EH_HGN_VOCAB_SIZE) { + fprintf(stderr, "[eh_hgn_dag] vocab_size %u > %u\n", + hdr.vocab_size, EH_HGN_VOCAB_SIZE); + fclose(fp); return EH_HGN_ERR_OVERFLOW; + } + if (hdr.total_edges > EH_HGN_MAX_EDGES) { + fprintf(stderr, "[eh_hgn_dag] total_edges %u > %u\n", + hdr.total_edges, EH_HGN_MAX_EDGES); + fclose(fp); return EH_HGN_ERR_OVERFLOW; + } + if (hdr.embed_dim != EH_HGN_EMBED_DIM) { + fprintf(stderr, "[eh_hgn_dag] embed_dim %u != %u\n", + hdr.embed_dim, EH_HGN_EMBED_DIM); + fclose(fp); return EH_HGN_ERR_OVERFLOW; + } + if (hdr.max_fanout > EH_HGN_MAX_FANOUT) { + fprintf(stderr, "[eh_hgn_dag] max_fanout %u > %u\n", + hdr.max_fanout, EH_HGN_MAX_FANOUT); + fclose(fp); return EH_HGN_ERR_OVERFLOW; + } + + /* -- Tรญnh kรญch thฦฐแป›c tแปซng vรนng -- */ + size_t sz_embed = (size_t)hdr.vocab_size * sizeof(EH_HGN_NodeEmbed); + size_t sz_adj = (size_t)hdr.vocab_size * sizeof(EH_HGN_NodeAdj); + size_t sz_compact = (size_t)hdr.total_edges * sizeof(EH_HGN_EdgeCompact); + size_t sz_weight = (size_t)hdr.total_edges * sizeof(EH_HGN_EdgeWeight); + + /* -- Cแบฅp phรกt tแปซ arena (aligned 32-byte cho node_embed vร  weight_pool) -- */ + EH_HGN_NodeEmbed *node_embed = eh_arena_alloc(arena, sz_embed); + EH_HGN_NodeAdj *node_adj = eh_arena_alloc(arena, sz_adj); + EH_HGN_EdgeCompact *edge_compact = eh_arena_alloc(arena, sz_compact); + EH_HGN_EdgeWeight *weight_pool = eh_arena_alloc(arena, sz_weight); + + if (!node_embed || !node_adj || !edge_compact || !weight_pool) { + size_t need_mb = (sz_embed + sz_adj + sz_compact + sz_weight) >> 20; + size_t avail = (arena->capacity > arena->used) + ? (arena->capacity - arena->used) >> 20 : 0; + fprintf(stderr, "[eh_hgn_dag] Arena OOM: need ~%zu MB, avail %zu MB\n", + need_mb, avail); + fclose(fp); return EH_HGN_ERR_ARENA; + } + + /* -- fread zero-copy vร o arena -- */ + if (fread(node_embed, sz_embed, 1, fp) != 1) { + fprintf(stderr, "[eh_hgn_dag] node_embed read failed\n"); + fclose(fp); return EH_HGN_ERR_CORRUPT; + } + if (fread(node_adj, sz_adj, 1, fp) != 1) { + fprintf(stderr, "[eh_hgn_dag] node_adj read failed\n"); + fclose(fp); return EH_HGN_ERR_CORRUPT; + } + if (fread(edge_compact, sz_compact, 1, fp) != 1) { + fprintf(stderr, "[eh_hgn_dag] edge_compact read failed\n"); + fclose(fp); return EH_HGN_ERR_CORRUPT; + } + + /* -- Skip padding 32-byte trฦฐแป›c weight_pool -- */ + long pos = ftell(fp); + if (pos < 0) { fclose(fp); return EH_HGN_ERR_IO; } + long aligned_pos = ((long)pos + 31L) & ~31L; + if (aligned_pos != pos) { + if (fseek(fp, aligned_pos, SEEK_SET) != 0) { + fprintf(stderr, "[eh_hgn_dag] fseek padding failed\n"); + fclose(fp); return EH_HGN_ERR_IO; + } + } + + if (fread(weight_pool, sz_weight, 1, fp) != 1) { + fprintf(stderr, "[eh_hgn_dag] weight_pool read failed\n"); + fclose(fp); return EH_HGN_ERR_CORRUPT; + } + fclose(fp); + + /* -- CSR integrity check: O(V+E), chแบกy 1 lแบงn sau load -- */ + for (uint32_t i = 0; i < hdr.vocab_size; i++) { + const EH_HGN_NodeAdj *adj = &node_adj[i]; + + if (adj->edge_offset > hdr.total_edges || + adj->edge_count > EH_HGN_MAX_FANOUT || + (uint64_t)adj->edge_offset + adj->edge_count > hdr.total_edges) + { + fprintf(stderr, "[eh_hgn_dag] CSR corrupt at node %u\n", i); + return EH_HGN_ERR_CORRUPT; + } + + /* Validate tแปซng edge: dst hแปฃp lแป‡, weight_idx hแปฃp lแป‡ */ + const EH_HGN_EdgeCompact *e = edge_compact + adj->edge_offset; + const EH_HGN_EdgeCompact *e_end = e + adj->edge_count; + for (; e != e_end; e++) { + if (e->dst >= hdr.vocab_size) { + fprintf(stderr, "[eh_hgn_dag] Edge dst %u OOB at node %u\n", + e->dst, i); + return EH_HGN_ERR_CORRUPT; + } + if (e->weight_idx >= hdr.total_edges) { + fprintf(stderr, "[eh_hgn_dag] weight_idx %u OOB at node %u\n", + e->weight_idx, i); + return EH_HGN_ERR_CORRUPT; + } + } + } + + /* -- Ghi kแบฟt quแบฃ -- */ + out_dag->vocab_size = hdr.vocab_size; + out_dag->total_edges = hdr.total_edges; + out_dag->embed_dim = hdr.embed_dim; + out_dag->max_fanout = hdr.max_fanout; + out_dag->node_embed = node_embed; + out_dag->node_adj = node_adj; + out_dag->edge_compact = edge_compact; + out_dag->weight_pool = weight_pool; + + return EH_HGN_OK; +} + +/* ---------------------------------------------------------------- + * eh_hgn_dag_dump_info + * ---------------------------------------------------------------- */ +void eh_hgn_dag_dump_info(const EH_HGN_BaseDag *dag) +{ + if (!dag) { fprintf(stderr, "[eh_hgn_dag] NULL dag\n"); return; } + + uint32_t fanout_min = EH_HGN_MAX_FANOUT; + uint32_t fanout_max = 0; + uint64_t fanout_sum = 0; + uint32_t sink_nodes = 0; + + for (uint32_t i = 0; i < dag->vocab_size; i++) { + uint32_t cnt = dag->node_adj[i].edge_count; + if (cnt == 0) sink_nodes++; + if (cnt < fanout_min) fanout_min = cnt; + if (cnt > fanout_max) fanout_max = cnt; + fanout_sum += cnt; + } + + double avg = dag->vocab_size ? (double)fanout_sum / dag->vocab_size : 0.0; + + /* Tรญnh RAM thแปฑc tแบฟ (bytes โ†’ MB vแป›i >> 20) */ + size_t b_embed = (size_t)dag->vocab_size * sizeof(EH_HGN_NodeEmbed); + size_t b_adj = (size_t)dag->vocab_size * sizeof(EH_HGN_NodeAdj); + size_t b_compact = (size_t)dag->total_edges * sizeof(EH_HGN_EdgeCompact); + size_t b_weight = (size_t)dag->total_edges * sizeof(EH_HGN_EdgeWeight); + + fprintf(stderr, + "=== EH_HGN_BaseDag ===\n" + " vocab_size : %u\n" + " total_edges : %u\n" + " embed_dim : %u\n" + " max_fanout : %u (cap K)\n" + " fan-out : min=%u max=%u avg=%.2f\n" + " sink nodes : %u\n" + " RAM embed : %zu MB\n" + " RAM adj : %zu KB\n" + " RAM compact : %zu MB\n" + " RAM weight : %zu MB\n" + " RAM total : ~%zu MB\n" + "======================\n", + dag->vocab_size, dag->total_edges, dag->embed_dim, dag->max_fanout, + fanout_min, fanout_max, avg, sink_nodes, + b_embed >> 20, + b_adj >> 10, + b_compact >> 20, + b_weight >> 20, + (b_embed + b_adj + b_compact + b_weight) >> 20 + ); +} \ No newline at end of file diff --git a/src/hgn/eh_hgn_engine.c b/src/hgn/eh_hgn_engine.c new file mode 100644 index 0000000..2652499 --- /dev/null +++ b/src/hgn/eh_hgn_engine.c @@ -0,0 +1,259 @@ +/* ================================================================ + * eh_hgn_engine.c โ€” HGN Layer 4: Unified Inference Engine impl + * + * Compile: + * gcc -O3 -std=c99 -mavx2 -Wall -Wextra -I include \ + * -c src/hgn/eh_hgn_engine.c -o src/hgn/eh_hgn_engine.o + * ================================================================ */ + +#include "../../include/hgn/eh_hgn_engine.h" + +#include +#include +#include + +/* ---------------------------------------------------------------- + * eh_hgn_session_init + * ---------------------------------------------------------------- */ +int eh_hgn_session_init( + EH_HGN_InferenceSession *session, + const EH_HGN_BaseDag *dag, + EH_Arena *arena, + const uint32_t *prompt, + uint32_t prompt_len, + const EH_HGN_EngineConfig *config) +{ + if (!session || !dag || !arena || !prompt || prompt_len == 0) { + fprintf(stderr, "[eh_hgn_engine] Invalid arguments to session_init\n"); + return -1; + } + + memset(session, 0, sizeof(*session)); + + /* Layer 1: DAG reference (read-only) */ + session->dag = dag; + + /* Layer 2: Collapse context */ + if (eh_hgn_collapse_ctx_init(&session->collapse_ctx, arena) != 0) { + fprintf(stderr, "[eh_hgn_engine] Failed to init collapse context\n"); + return -1; + } + + /* Layer 3: Beam tracker */ + eh_hgn_beam_init(&session->beam_tracker, dag, prompt, prompt_len); + + /* Config: use defaults nแบฟu NULL */ + if (config) { + session->config = *config; + } else { + session->config = eh_hgn_default_config(); + } + + /* Apply custom thresholds vร o collapse context */ + session->collapse_ctx.collapse_thresh = session->config.collapse_thresh; + session->collapse_ctx.entropy_thresh = session->config.entropy_thresh; + session->collapse_ctx.perturb_scale = session->config.perturb_scale; + + /* Init state */ + session->step_count = 0; + session->is_finished = false; + + return 0; +} + +/* ---------------------------------------------------------------- + * eh_hgn_session_reset + * ---------------------------------------------------------------- */ +void eh_hgn_session_reset( + EH_HGN_InferenceSession *session, + const uint32_t *prompt, + uint32_t prompt_len) +{ + if (!session || !prompt || prompt_len == 0) return; + + /* Reset collapse context (giแปฏ pool, xรณa state) */ + eh_hgn_collapse_ctx_reset(&session->collapse_ctx); + + /* Reset beam tracker vแป›i prompt mแป›i */ + eh_hgn_beam_init(&session->beam_tracker, session->dag, prompt, prompt_len); + + /* Reset session state */ + session->step_count = 0; + session->is_finished = false; + memset(session->h_current, 0, sizeof(session->h_current)); + memset(session->h_pooled, 0, sizeof(session->h_pooled)); +} + +/* ---------------------------------------------------------------- + * eh_hgn_session_step: Core inference pipeline + * ---------------------------------------------------------------- */ +uint32_t eh_hgn_session_step(EH_HGN_InferenceSession *session) +{ + if (!session || session->is_finished) return 0; + + /* Check max_steps limit */ + if (session->config.max_steps > 0 && + session->step_count >= session->config.max_steps) { + session->is_finished = true; + return 0; + } + + /* --- Step 1: Lแบฅy current beam state --- */ + if (session->beam_tracker.active_paths == 0) { + session->is_finished = true; + return 0; + } + + /* Snapshot last_token TRฦฏแปšC khi gแปi beam_step(). + * Sau beam_step(), paths[] bแป‹ overwrite โ†’ mแปi pointer vร o + * paths[] ฤ‘แปu stale. Chแป‰ dรนng giรก trแป‹ scalar ฤ‘รฃ copy ra. */ + uint32_t pre_step_seq_len = session->beam_tracker.paths[0].seq_len; + if (pre_step_seq_len == 0) { + session->is_finished = true; + return 0; + } + uint32_t last_token = session->beam_tracker.paths[0].tokens[pre_step_seq_len - 1]; + const float *node_emb = eh_hgn_dag_node_vec(session->dag, last_token); + if (!node_emb) { + fprintf(stderr, "[eh_hgn_engine] Invalid last_token=%u\n", last_token); + session->is_finished = true; + return 0; + } + + /* Copy node embedding vร o h_current */ + memcpy(session->h_current, node_emb, EH_HGN_EMBED_DIM * sizeof(float)); + + /* --- Step 2: Collapse Gating (nแบฟu enabled) --- */ + EH_HGN_CollapseDecision decision = EH_HGN_EXPAND; + if (session->config.enable_collapse) { + decision = eh_hgn_collapse_gate(&session->collapse_ctx, session->h_current); + + if (decision == EH_HGN_COLLAPSE) { + /* Fast path: mean pooling O(N) thay vรฌ full DAG */ + eh_hgn_collapse_mean_pool(session->dag, last_token, session->h_pooled); + + /* NOTE: Trong collapse mode, ta cรณ thแปƒ inject h_pooled vร o beam scoring + * Hiแป‡n tแบกi beam_step() tแปฑ handle edges, nรชn ta chแป‰ log collapse decision. + * Production implementation cรณ thแปƒ override scoring vector แปŸ ฤ‘รขy. */ + } + } + + /* --- Step 3: Beam expansion (Layer 3) --- */ + uint32_t active_beams = eh_hgn_beam_step(&session->beam_tracker, session->dag); + + /* --- Step 4: Mutant spawning (nแบฟu enabled vร  entropy cao) --- */ + if (session->config.enable_mutants && active_beams > 0) { + /* Thu thแบญp scores cแปงa tแบฅt cแบฃ beams hiแป‡n tแบกi */ + float scores[EH_BEAM_WIDTH]; + uint32_t beam_count = 0; + for (uint32_t i = 0; i < EH_BEAM_WIDTH && i < session->beam_tracker.active_paths; i++) { + scores[beam_count++] = session->beam_tracker.paths[i].score; + } + + if (beam_count > 1) { + float entropy = eh_hgn_beam_entropy(scores, beam_count); + + if (entropy > session->collapse_ctx.entropy_thresh) { + /* src = token trฦฐแป›c expansion (ฤ‘รฃ snapshot). + * dst = token mแป›i nhแบฅt cแปงa best beam SAU expansion. + * paths[0] แปŸ ฤ‘รขy lร  NEW best beam (ฤ‘รฃ sort sau beam_step). */ + uint32_t src = last_token; + const EH_HGN_BeamPath *new_best = &session->beam_tracker.paths[0]; + uint32_t dst = (new_best->seq_len > 0) + ? new_best->tokens[new_best->seq_len - 1] + : 0; + float top_score = scores[0]; + + EH_HGN_MutantNode *mutant = eh_hgn_mutant_spawn( + &session->collapse_ctx, src, dst, top_score); + + /* Mutant ฤ‘ฦฐแปฃc sinh ra nhฦฐng khรดng ฤ‘ฦฐแปฃc inject vร o beam ngay lแบญp tแปฉc. + * Trong production, mutant->vec cรณ thแปƒ ฤ‘ฦฐแปฃc dรนng nhฦฐ alternative context + * cho edge scoring trong step tiแบฟp theo. */ + (void)mutant; /* suppress unused warning */ + } + } + } + + /* --- Step 5: Cleanup mutants sau mแป—i step (lifetime=1) --- */ + eh_hgn_collapse_step_end(&session->collapse_ctx); + + /* --- Step 6: Update session state --- */ + session->step_count++; + + /* Check termination: tแบฅt cแบฃ beams ฤ‘รฃ finished? */ + if (active_beams == 0) { + session->is_finished = true; + return 0; + } + + return active_beams; +} + +/* ---------------------------------------------------------------- + * eh_hgn_session_get_beams + * ---------------------------------------------------------------- */ +const EH_HGN_BeamPath *eh_hgn_session_get_beams( + const EH_HGN_InferenceSession *session) +{ + if (!session) return NULL; + return session->beam_tracker.paths; +} + +/* ---------------------------------------------------------------- + * eh_hgn_session_get_best + * ---------------------------------------------------------------- */ +const EH_HGN_BeamPath *eh_hgn_session_get_best( + const EH_HGN_InferenceSession *session) +{ + if (!session) return NULL; + return eh_hgn_beam_get_best(&session->beam_tracker); +} + +/* ---------------------------------------------------------------- + * eh_hgn_session_dump_stats + * ---------------------------------------------------------------- */ +void eh_hgn_session_dump_stats(const EH_HGN_InferenceSession *session) +{ + if (!session) { + fprintf(stderr, "[eh_hgn_engine] NULL session\n"); + return; + } + + fprintf(stderr, "\n=== EH_HGN_InferenceSession Stats ===\n"); + fprintf(stderr, " Steps executed : %u\n", session->step_count); + fprintf(stderr, " Generation done : %s\n", session->is_finished ? "YES" : "NO"); + fprintf(stderr, " Active beams : %u\n", session->beam_tracker.active_paths); + fprintf(stderr, " Collapse enabled : %s\n", session->config.enable_collapse ? "YES" : "NO"); + fprintf(stderr, " Mutants enabled : %s\n", session->config.enable_mutants ? "YES" : "NO"); + fprintf(stderr, " Max steps limit : %u (0=unlimited)\n", session->config.max_steps); + fprintf(stderr, "=====================================\n"); + + /* Dump collapse context stats */ + eh_hgn_collapse_dump_stats(&session->collapse_ctx); +} + +/* ---------------------------------------------------------------- + * eh_hgn_session_dump_beams + * ---------------------------------------------------------------- */ +void eh_hgn_session_dump_beams(const EH_HGN_InferenceSession *session) +{ + if (!session) { + fprintf(stderr, "[eh_hgn_engine] NULL session\n"); + return; + } + + fprintf(stderr, "\n=== Current Beam States ===\n"); + for (uint32_t i = 0; i < session->beam_tracker.active_paths && i < EH_BEAM_WIDTH; i++) { + const EH_HGN_BeamPath *beam = &session->beam_tracker.paths[i]; + fprintf(stderr, " Beam %u: score=%.3f len=%u finished=%s\n", + i, beam->score, beam->seq_len, beam->is_finished ? "YES" : "NO"); + fprintf(stderr, " Tokens: ["); + for (uint32_t j = 0; j < beam->seq_len && j < 16; j++) { /* limit to first 16 */ + fprintf(stderr, "%u%s", beam->tokens[j], (j < beam->seq_len - 1) ? ", " : ""); + } + if (beam->seq_len > 16) fprintf(stderr, ", ..."); + fprintf(stderr, "]\n"); + } + fprintf(stderr, "===========================\n"); +} diff --git a/src/main_learning.c b/src/main_learning.c index c6a7999..1bcd4a3 100644 --- a/src/main_learning.c +++ b/src/main_learning.c @@ -10,11 +10,11 @@ #include #include -#include "../include/eh_dag.h" -#include "../include/eh_scoring.h" -#include "../include/eh_dynamic.h" -#include "../include/eh_learning.h" -#include "../include/eh_engine.h" +#include "../include/core/eh_dag.h" +#include "../include/core/eh_scoring.h" +#include "../include/core/eh_dynamic.h" +#include "../include/core/eh_learning.h" +#include "../include/core/eh_engine.h" #define DIM_SIZE 8 diff --git a/src/main_test.c b/src/main_test.c index dbdfe56..5022896 100644 --- a/src/main_test.c +++ b/src/main_test.c @@ -11,9 +11,9 @@ #include #include -#include "../include/eh_dag.h" -#include "../include/eh_scoring.h" -#include "../include/eh_engine.h" +#include "../include/core/eh_dag.h" +#include "../include/core/eh_scoring.h" +#include "../include/core/eh_engine.h" #define INPUT_DIM 8 #define OUTPUT_DIM 8 diff --git a/src/test_neuro.c b/src/test_neuro.c index 417813a..53ec91f 100644 --- a/src/test_neuro.c +++ b/src/test_neuro.c @@ -17,12 +17,12 @@ #include #include -#include "../include/eh_dag.h" -#include "../include/eh_scoring.h" -#include "../include/eh_engine.h" -#include "../include/eh_dynamic.h" -#include "../include/eh_arena.h" -#include "../include/eh_neuro.h" +#include "../include/core/eh_dag.h" +#include "../include/core/eh_scoring.h" +#include "../include/core/eh_engine.h" +#include "../include/core/eh_dynamic.h" +#include "../include/core/eh_arena.h" +#include "../include/core/eh_neuro.h" #define DIM 8 diff --git a/tests/Makefile b/tests/Makefile index 695209b..737d32f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -5,31 +5,46 @@ CFLAGS = -O2 -std=c99 -Wall -Wextra -g LIBS = -lm INCDIR = ../include -SRCDIR = ../src/core - -CORE_SRCS = $(SRCDIR)/*.c +SRCDIR = ../src +CORE_SRCS = $(SRCDIR)/core/*.c +HGN_SRCS = $(SRCDIR)/hgn/*.c # Test targets -TESTS = test_arena test_dag test_engine +TESTS = test_arena test_dag test_engine test_eh_hgn_dag test_eh_hgn_beam test_eh_adaptive test_eh_hgn_collapse test_eh_hgn_engine .PHONY: all test clean help all: $(TESTS) @echo "โœ“ All tests built" -test_arena: test_arena.c $(CORE_SRCS) - $(CC) $(CFLAGS) -I$(INCDIR) test_arena.c $(CORE_SRCS) -o test_arena $(LIBS) +test_arena: test_arena.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_arena.c $(CORE_SRCS) $(HGN_SRCS) -o test_arena $(LIBS) + +test_dag: test_dag.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_dag.c $(CORE_SRCS) $(HGN_SRCS) -o test_dag $(LIBS) + +test_engine: test_engine.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_engine.c $(CORE_SRCS) $(HGN_SRCS) -o test_engine $(LIBS) + +test_eh_hgn_dag: test_eh_hgn_dag.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_eh_hgn_dag.c $(CORE_SRCS) $(HGN_SRCS) -o test_eh_hgn_dag $(LIBS) + +test_eh_hgn_beam: test_eh_hgn_beam.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_eh_hgn_beam.c $(CORE_SRCS) $(HGN_SRCS) -o test_eh_hgn_beam $(LIBS) + +test_eh_adaptive: test_eh_adaptive.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_eh_adaptive.c $(CORE_SRCS) $(HGN_SRCS) -o test_eh_adaptive $(LIBS) -test_dag: test_dag.c $(CORE_SRCS) - $(CC) $(CFLAGS) -I$(INCDIR) test_dag.c $(CORE_SRCS) -o test_dag $(LIBS) +test_eh_hgn_collapse: test_eh_hgn_collapse.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_eh_hgn_collapse.c $(CORE_SRCS) $(HGN_SRCS) -o test_eh_hgn_collapse $(LIBS) -test_engine: test_engine.c $(CORE_SRCS) - $(CC) $(CFLAGS) -I$(INCDIR) test_engine.c $(CORE_SRCS) -o test_engine $(LIBS) +test_eh_hgn_engine: test_eh_hgn_engine.c $(CORE_SRCS) $(HGN_SRCS) + $(CC) $(CFLAGS) -I$(INCDIR) -I$(INCDIR)/core -I$(INCDIR)/hgn test_eh_hgn_engine.c $(CORE_SRCS) $(HGN_SRCS) -o test_eh_hgn_engine $(LIBS) test: all @echo "Running all tests..." @echo "" - @./test_arena && echo "" && ./test_dag && echo "" && ./test_engine + @./test_arena && echo "" && ./test_dag && echo "" && ./test_engine && echo "" && ./test_eh_hgn_dag && echo "" && ./test_eh_hgn_beam && echo "" && ./test_eh_adaptive && echo "" && ./test_eh_hgn_collapse && echo "" && ./test_eh_hgn_engine @echo "" @echo "======================================" @echo "All test suites completed!" diff --git a/tests/test_arena b/tests/test_arena deleted file mode 100644 index 6198cd6..0000000 Binary files a/tests/test_arena and /dev/null differ diff --git a/tests/test_dag b/tests/test_dag deleted file mode 100644 index 1d03c1f..0000000 Binary files a/tests/test_dag and /dev/null differ diff --git a/tests/test_eh_adaptive.c b/tests/test_eh_adaptive.c new file mode 100644 index 0000000..b5a49ad --- /dev/null +++ b/tests/test_eh_adaptive.c @@ -0,0 +1,336 @@ +/* ================================================================ + * test_eh_adaptive.c โ€” Test Suite for Adaptive Mechanisms + * ================================================================ */ + +#include +#include +#include +#include +#include + +#include "hgn/eh_adaptive.h" +#include "hgn/eh_beam_search.h" + +/* Test framework */ +static int g_tests_run = 0; +static int g_tests_passed = 0; + +#define TEST_ASSERT(cond, msg) do { \ + g_tests_run++; \ + if (!(cond)) { \ + fprintf(stderr, " [FAIL] %s\n", msg); \ + } else { \ + fprintf(stderr, " [PASS] %s\n", msg); \ + g_tests_passed++; \ + } \ +} while(0) + +#define TEST_ASSERT_FLOAT_EQ(a, b, tol, msg) do { \ + g_tests_run++; \ + if (fabsf((a) - (b)) > (tol)) { \ + fprintf(stderr, " [FAIL] %s (%.6f vs %.6f)\n", msg, (float)(a), (float)(b)); \ + } else { \ + fprintf(stderr, " [PASS] %s\n", msg); \ + g_tests_passed++; \ + } \ +} while(0) + +/* ---------------------------------------------------------------- + * Test 1: Cosine Similarity + * ---------------------------------------------------------------- */ +static void test_cosine_similarity(void) +{ + fprintf(stderr, "\n=== Test 1: Cosine Similarity ===\n"); + + float a[4] = {1.0f, 0.0f, 0.0f, 0.0f}; + float b[4] = {1.0f, 0.0f, 0.0f, 0.0f}; + float c[4] = {0.0f, 1.0f, 0.0f, 0.0f}; + float d[4] = {0.707f, 0.707f, 0.0f, 0.0f}; + + /* Identical vectors โ†’ cos = 1.0 */ + float cos_ab = eh_adaptive_cosine_similarity(a, b, 4); + TEST_ASSERT_FLOAT_EQ(cos_ab, 1.0f, 1e-6f, "cos(a, b) = 1.0"); + + /* Orthogonal vectors โ†’ cos = 0.0 */ + float cos_ac = eh_adaptive_cosine_similarity(a, c, 4); + TEST_ASSERT_FLOAT_EQ(cos_ac, 0.0f, 1e-6f, "cos(a, c) = 0.0"); + + /* 45-degree angle โ†’ cos โ‰ˆ 0.707 */ + float cos_ad = eh_adaptive_cosine_similarity(a, d, 4); + TEST_ASSERT_FLOAT_EQ(cos_ad, 0.707f, 0.01f, "cos(a, d) โ‰ˆ 0.707"); +} + +/* ---------------------------------------------------------------- + * Test 2: LCG Determinism + * ---------------------------------------------------------------- */ +static void test_lcg_determinism(void) +{ + fprintf(stderr, "\n=== Test 2: LCG Determinism ===\n"); + + uint32_t seed = 12345u; + + /* Same seed โ†’ same sequence */ + uint32_t s1a = eh_adaptive_lcg_next(seed); + uint32_t s1b = eh_adaptive_lcg_next(seed); + TEST_ASSERT(s1a == s1b, "LCG: same seed โ†’ same output"); + + /* Different seeds โ†’ different outputs */ + uint32_t s2 = eh_adaptive_lcg_next(seed + 1); + TEST_ASSERT(s1a != s2, "LCG: different seeds โ†’ different outputs"); + + /* Multiple steps */ + uint32_t state = seed; + uint32_t seq1[5]; + for (int i = 0; i < 5; i++) { + state = eh_adaptive_lcg_next(state); + seq1[i] = state; + } + + /* Replay โ†’ same sequence */ + state = seed; + for (int i = 0; i < 5; i++) { + state = eh_adaptive_lcg_next(state); + TEST_ASSERT(state == seq1[i], "LCG: deterministic replay"); + } +} + +/* ---------------------------------------------------------------- + * Test 3: Perturbation Generation + * ---------------------------------------------------------------- */ +static void test_perturbation_generation(void) +{ + fprintf(stderr, "\n=== Test 3: Perturbation Generation ===\n"); + + float vec[128]; + uint32_t seed = 42u; + float scale = 0.3f; + + eh_adaptive_generate_perturbation(seed, scale, vec, 128); + + /* Check range [-scale, +scale] */ + bool in_range = true; + for (int i = 0; i < 128; i++) { + if (vec[i] < -scale - 1e-6f || vec[i] > scale + 1e-6f) { + in_range = false; + break; + } + } + TEST_ASSERT(in_range, "Perturbation in range [-0.3, +0.3]"); + + /* Check determinism */ + float vec2[128]; + eh_adaptive_generate_perturbation(seed, scale, vec2, 128); + + bool same = true; + for (int i = 0; i < 128; i++) { + if (fabsf(vec[i] - vec2[i]) > 1e-6f) { + same = false; + break; + } + } + TEST_ASSERT(same, "Perturbation deterministic"); +} + +/* ---------------------------------------------------------------- + * Test 4: Collapse Gating + * ---------------------------------------------------------------- */ +static void test_collapse_gating(void) +{ + fprintf(stderr, "\n=== Test 4: Collapse Gating ===\n"); + + EH_AdaptiveContext ctx; + eh_adaptive_init(&ctx, 0.85f, 2.5f, 0.3f); + + float h0[EH_HGN_EMBED_DIM]; + float h1[EH_HGN_EMBED_DIM]; + float h2[EH_HGN_EMBED_DIM]; + + memset(h0, 0, sizeof(h0)); + memset(h1, 0, sizeof(h1)); + memset(h2, 0, sizeof(h2)); + + /* h0: normalized vector */ + float norm0 = 0.0f; + for (int i = 0; i < EH_HGN_EMBED_DIM; i++) { + h0[i] = (i < 20) ? 1.0f : 0.0f; + norm0 += h0[i] * h0[i]; + } + norm0 = sqrtf(norm0); + for (int i = 0; i < EH_HGN_EMBED_DIM; i++) h0[i] /= norm0; + + /* h1: very similar to h0 (90% overlap) โ†’ should collapse */ + float norm1 = 0.0f; + for (int i = 0; i < EH_HGN_EMBED_DIM; i++) { + h1[i] = (i < 18) ? 1.0f : 0.0f; + norm1 += h1[i] * h1[i]; + } + norm1 = sqrtf(norm1); + for (int i = 0; i < EH_HGN_EMBED_DIM; i++) h1[i] /= norm1; + + /* h2: completely different pattern โ†’ should expand */ + float norm2 = 0.0f; + for (int i = 0; i < EH_HGN_EMBED_DIM; i++) { + h2[i] = (i >= 50 && i < 70) ? 1.0f : 0.0f; + norm2 += h2[i] * h2[i]; + } + norm2 = sqrtf(norm2); + for (int i = 0; i < EH_HGN_EMBED_DIM; i++) h2[i] /= norm2; + + /* Step 0: always EXPAND */ + EH_CollapseState s0 = eh_adaptive_gate(&ctx, h0); + TEST_ASSERT(s0 == EH_STATE_EXPAND, "Step 0: EXPAND"); + + /* Step 1: high similarity โ†’ COLLAPSE */ + EH_CollapseState s1 = eh_adaptive_gate(&ctx, h1); + float cos_h0_h1 = eh_adaptive_cosine_similarity(h0, h1, EH_HGN_EMBED_DIM); + fprintf(stderr, " [DEBUG] cos(h0, h1) = %.3f\n", cos_h0_h1); + TEST_ASSERT(s1 == EH_STATE_COLLAPSE, "Step 1: COLLAPSE (high similarity)"); + + /* Step 2: low similarity โ†’ EXPAND */ + EH_CollapseState s2 = eh_adaptive_gate(&ctx, h2); + float cos_h1_h2 = eh_adaptive_cosine_similarity(h1, h2, EH_HGN_EMBED_DIM); + fprintf(stderr, " [DEBUG] cos(h1, h2) = %.3f (threshold=%.2f)\n", cos_h1_h2, ctx.collapse_threshold); + TEST_ASSERT(s2 == EH_STATE_EXPAND, "Step 2: EXPAND (low similarity)"); + + /* Check stats */ + TEST_ASSERT(ctx.collapse_count == 1, "Collapse count = 1"); + TEST_ASSERT(ctx.expand_count == 2, "Expand count = 2"); +} + +/* ---------------------------------------------------------------- + * Test 5: Beam Entropy + * ---------------------------------------------------------------- */ +static void test_beam_entropy(void) +{ + fprintf(stderr, "\n=== Test 5: Beam Entropy ===\n"); + + EH_HGN_BeamTracker tracker; + memset(&tracker, 0, sizeof(tracker)); + + /* Uniform distribution (4 beams with same score) โ†’ max entropy */ + tracker.active_paths = 4; + for (int i = 0; i < 4; i++) { + tracker.paths[i].score = 0.0f; + } + + float h1 = eh_adaptive_beam_entropy(&tracker); + fprintf(stderr, " [INFO] Uniform entropy: %.3f\n", h1); + TEST_ASSERT(h1 > 1.0f, "Uniform โ†’ high entropy"); + + /* One dominant beam โ†’ low entropy */ + tracker.paths[0].score = 10.0f; + tracker.paths[1].score = 0.0f; + tracker.paths[2].score = 0.0f; + tracker.paths[3].score = 0.0f; + + float h2 = eh_adaptive_beam_entropy(&tracker); + fprintf(stderr, " [INFO] Dominant entropy: %.3f\n", h2); + TEST_ASSERT(h2 < 0.5f, "Dominant โ†’ low entropy"); + TEST_ASSERT(h2 < h1, "Dominant < Uniform"); +} + +/* ---------------------------------------------------------------- + * Test 6: Mutant Spawning + * ---------------------------------------------------------------- */ +static void test_mutant_spawning(void) +{ + fprintf(stderr, "\n=== Test 6: Mutant Spawning ===\n"); + + EH_AdaptiveContext ctx; + eh_adaptive_init(&ctx, 0.85f, 1.0f, 0.3f); /* Low entropy threshold */ + + /* Setup tracker with high entropy */ + EH_HGN_BeamTracker tracker; + memset(&tracker, 0, sizeof(tracker)); + tracker.active_paths = 4; + for (int i = 0; i < 4; i++) { + tracker.paths[i].score = (float)i * 0.1f; /* Spread scores */ + } + + float h_current[EH_HGN_EMBED_DIM]; + memset(h_current, 0, sizeof(h_current)); + h_current[0] = 1.0f; + + /* Try spawn mutant */ + uint32_t mutant_id = eh_adaptive_try_spawn_mutant(&ctx, &tracker, h_current); + + TEST_ASSERT(mutant_id != UINT32_MAX, "Mutant spawned (high entropy)"); + TEST_ASSERT(ctx.mutant_count == 1, "Mutant count = 1"); + TEST_ASSERT(ctx.total_mutants_spawned == 1, "Total spawned = 1"); + + /* Retrieve mutant vector */ + const float *mutant_vec = eh_adaptive_get_mutant_vec(&ctx, mutant_id); + TEST_ASSERT(mutant_vec != NULL, "Mutant vector retrieved"); + + /* Check perturbation applied */ + float diff = fabsf(mutant_vec[0] - h_current[0]); + TEST_ASSERT(diff > 0.0f, "Mutant != original (perturbation applied)"); + fprintf(stderr, " [INFO] Perturbation magnitude: %.3f\n", diff); + + /* Reset mutants */ + eh_adaptive_reset_mutants(&ctx); + TEST_ASSERT(ctx.mutant_count == 0, "Mutants reset"); + + const float *after_reset = eh_adaptive_get_mutant_vec(&ctx, mutant_id); + TEST_ASSERT(after_reset == NULL, "Mutant invalidated after reset"); +} + +/* ---------------------------------------------------------------- + * Test 7: Mutant Pool Limit + * ---------------------------------------------------------------- */ +static void test_mutant_pool_limit(void) +{ + fprintf(stderr, "\n=== Test 7: Mutant Pool Limit ===\n"); + + EH_AdaptiveContext ctx; + eh_adaptive_init(&ctx, 0.85f, 0.1f, 0.3f); /* Very low threshold */ + + EH_HGN_BeamTracker tracker; + memset(&tracker, 0, sizeof(tracker)); + tracker.active_paths = 4; + for (int i = 0; i < 4; i++) { + tracker.paths[i].score = (float)i; + } + + float h[EH_HGN_EMBED_DIM]; + memset(h, 0, sizeof(h)); + h[0] = 1.0f; + + /* Spawn until pool is full */ + uint32_t spawned = 0; + for (int i = 0; i < EH_MAX_MUTANTS + 5; i++) { + ctx.step_counter = i; /* Change seed */ + uint32_t id = eh_adaptive_try_spawn_mutant(&ctx, &tracker, h); + if (id != UINT32_MAX) spawned++; + } + + TEST_ASSERT(spawned == EH_MAX_MUTANTS, "Spawned exactly MAX_MUTANTS"); + TEST_ASSERT(ctx.mutant_count == EH_MAX_MUTANTS, "Pool at capacity"); + fprintf(stderr, " [INFO] Pool saturated at %u mutants\n", EH_MAX_MUTANTS); +} + +/* ---------------------------------------------------------------- + * Main + * ---------------------------------------------------------------- */ +int main(void) +{ + fprintf(stderr, "\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n"); + fprintf(stderr, "โ•‘ EH_Adaptive Mechanisms Test Suite โ•‘\n"); + fprintf(stderr, "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n"); + + test_cosine_similarity(); + test_lcg_determinism(); + test_perturbation_generation(); + test_collapse_gating(); + test_beam_entropy(); + test_mutant_spawning(); + test_mutant_pool_limit(); + + fprintf(stderr, "\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n"); + fprintf(stderr, "โ•‘ Result: %2d/%2d tests passed (%.0f%%) โ•‘\n", + g_tests_passed, g_tests_run, + g_tests_run ? 100.0f * g_tests_passed / g_tests_run : 0.0f); + fprintf(stderr, "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n\n"); + + return (g_tests_passed == g_tests_run) ? 0 : 1; +} diff --git a/tests/test_eh_hgn_beam.c b/tests/test_eh_hgn_beam.c new file mode 100644 index 0000000..7e1a91f --- /dev/null +++ b/tests/test_eh_hgn_beam.c @@ -0,0 +1,245 @@ +/* ========================================================================= + * MODULE: HGN (Heuristic Graph Network) - Layer 3 Test Suite + * FILE: test_eh_hgn_beam.c + * + * Tests: + * T1 - Arena init + * T2 - DAG load + * T3 - Beam tracker init + * T4 - Step 1: correct token selection (cat vs dog) + * T5 - Sequence converged to terminal (EOS detection) + * T6 - [REGRESSION] Multi-beam: correct parent tracking (exposes old bug) + * T7 - eh_hgn_beam_step returns 0 when all beams finished + * ========================================================================= */ + +#include +#include +#include +#include + +#include "core/eh_arena.h" +#include "hgn/eh_hgn_dag.h" +#include "hgn/eh_beam_search.h" + +/* ---------------------------------------------------------------- + * Helper: Build simple test binary + * + * Graph (4 nodes, 4 edges): + * 0 "The" โ†’ 1 "cat" (prior=0.8, weight[0]=1.5) + * โ†’ 2 "dog" (prior=0.2, weight[0]=-0.5) + * 1 "cat" โ†’ 3 "ends" (prior=0.9, weight[0]=1.0) + * 2 "dog" โ†’ 3 "ends" (prior=0.1, weight[0]=0.0) + * 3 "ends" โ†’ โˆ… (sink node) + * + * Embeddings: + * 0: vec[0]=1.0 (baseline) + * 1: vec[0]=2.0 (positive context) + * 2: vec[0]=-1.0 (negative context) + * 3: vec[0]=0.5 (neutral) + * ---------------------------------------------------------------- */ +static void build_beam_test_binary(const char* path) { + FILE *f = fopen(path, "wb"); + assert(f != NULL); + + EH_HGN_DagFileHeader hdr = { + .magic = EH_HGN_DAG_MAGIC, + .version = EH_HGN_DAG_VERSION, + .vocab_size = 4, + .total_edges = 4, + .embed_dim = EH_HGN_EMBED_DIM, + .max_fanout = 2, + .reserved = {0, 0} + }; + fwrite(&hdr, sizeof(hdr), 1, f); + + /* Node embeddings */ + EH_HGN_NodeEmbed embeds[4]; + memset(embeds, 0, sizeof(embeds)); + embeds[0].vec[0] = 1.0f; + embeds[1].vec[0] = 2.0f; + embeds[2].vec[0] = -1.0f; + embeds[3].vec[0] = 0.5f; + fwrite(embeds, sizeof(EH_HGN_NodeEmbed), 4, f); + + /* CSR adjacency */ + EH_HGN_NodeAdj adj[4] = { + {0, 2}, /* Token 0: 2 edges at offset 0 */ + {2, 1}, /* Token 1: 1 edge at offset 2 */ + {3, 1}, /* Token 2: 1 edge at offset 3 */ + {4, 0} /* Token 3: sink (0 edges) */ + }; + fwrite(adj, sizeof(EH_HGN_NodeAdj), 4, f); + + /* Edge compact */ + EH_HGN_EdgeCompact edges[4]; + memset(edges, 0, sizeof(edges)); + edges[0] = (EH_HGN_EdgeCompact){ .dst=1, .prior=0.8f, .weight_idx=0 }; + edges[1] = (EH_HGN_EdgeCompact){ .dst=2, .prior=0.2f, .weight_idx=1 }; + edges[2] = (EH_HGN_EdgeCompact){ .dst=3, .prior=0.9f, .weight_idx=2 }; + edges[3] = (EH_HGN_EdgeCompact){ .dst=3, .prior=0.1f, .weight_idx=3 }; + fwrite(edges, sizeof(EH_HGN_EdgeCompact), 4, f); + + /* Padding to 32-byte boundary */ + long pos = ftell(f); + long apos = (pos + 31L) & ~31L; + if (apos != pos) { + uint8_t pad[32] = {0}; + fwrite(pad, (size_t)(apos - pos), 1, f); + } + + /* Edge weights */ + EH_HGN_EdgeWeight weights[4]; + memset(weights, 0, sizeof(weights)); + weights[0].vec[0] = 1.5f; /* 0โ†’1: positive boost */ + weights[1].vec[0] = -0.5f; /* 0โ†’2: negative */ + weights[2].vec[0] = 1.0f; /* 1โ†’3 */ + weights[3].vec[0] = 0.0f; /* 2โ†’3 */ + fwrite(weights, sizeof(EH_HGN_EdgeWeight), 4, f); + + fclose(f); +} + +/* ---------------------------------------------------------------- + * Helper: pretty-print a beam path + * ---------------------------------------------------------------- */ +static void print_beam(const EH_HGN_BeamPath *p, const char *label) { + printf(" [INFO] %s: tokens=[", label); + for (uint32_t i = 0; i < p->seq_len; i++) { + printf("%u", p->tokens[i]); + if (i + 1 < p->seq_len) printf(" "); + } + printf("] score=%.3f finished=%s\n", + p->score, p->is_finished ? "yes" : "no"); +} + +/* ================================================================ + * MAIN + * ================================================================ */ +int main(void) { + printf("=== EH_HGN_BeamSearch Logic Tests ===\n"); + + const char* BIN_FILE = "/tmp/beam_test.bin"; + build_beam_test_binary(BIN_FILE); + + /* โ”€โ”€ T1: Arena init โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + EH_Arena *arena = eh_arena_create(10 * 1024 * 1024); + assert(arena != NULL); + printf(" [PASS] T1: Arena initialized (10MB)\n"); + + /* โ”€โ”€ T2: DAG load โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + EH_HGN_BaseDag dag; + EH_HGN_Status rc = eh_hgn_dag_load(arena, BIN_FILE, &dag); + assert(rc == EH_HGN_OK); + assert(dag.vocab_size == 4); + assert(dag.total_edges == 4); + printf(" [PASS] T2: CSR Binary loaded (vocab=%u, edges=%u)\n", + dag.vocab_size, dag.total_edges); + + /* โ”€โ”€ T3: Beam tracker init โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + EH_HGN_BeamTracker tracker; + uint32_t prompt[1] = {0}; /* "The" */ + eh_hgn_beam_init(&tracker, &dag, prompt, 1); + + assert(tracker.active_paths == 1); + assert(tracker.paths[0].tokens[0] == 0); + assert(tracker.paths[0].seq_len == 1); + assert(tracker.paths[0].is_finished == false); + printf(" [PASS] T3: Beam Tracker initialized with prompt\n"); + + /* โ”€โ”€ T4: Step 1 โ€” prefer cat (1) over dog (2) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + /* + * Scores from node 0 (embed[0]=1.0): + * edge 0โ†’1: prior=0.8 + dot(w=[1.5,โ€ฆ], ctx=[1.0,โ€ฆ]) = 0.8 + 1.5 = 2.3 + * edge 0โ†’2: prior=0.2 + dot(w=[-0.5,โ€ฆ], ctx=[1.0,โ€ฆ]) = 0.2 + (-0.5) = -0.3 + * Expected top-1 token: 1 ("cat"), score: 2.3 + */ + uint32_t active = eh_hgn_beam_step(&tracker, &dag); + + assert(tracker.active_paths > 0); + uint32_t step1_token = tracker.paths[0].tokens[1]; + float step1_score = tracker.paths[0].score; + + printf(" [INFO] Step 1 Top-1 Token: %u (expected 1 'cat')\n", step1_token); + printf(" [INFO] Step 1 Score: %.3f (expected 2.300)\n", step1_score); + printf(" [INFO] Active beams after step 1: %u\n", active); + + assert(step1_token == 1 && "Beam must prefer 'cat' (higher prior + context)"); + assert(step1_score > 2.0f && step1_score < 2.4f); + printf(" [PASS] T4: Step 1 autoregressive expansion correct\n"); + + /* โ”€โ”€ T5: Step 2 โ€” reach terminal node (EOS detection) โ”€โ”€โ”€โ”€โ”€ */ + /* + * From "cat" (1, embed[0]=2.0): + * edge 1โ†’3: prior=0.9 + dot(w=[1.0,โ€ฆ], ctx=[2.0,โ€ฆ]) = 0.9 + 2.0 = 2.9 + * Cumulative: 2.3 + 2.9 = 5.2 + * Node 3 is a sink โ†’ is_finished should be true + */ + active = eh_hgn_beam_step(&tracker, &dag); + + const EH_HGN_BeamPath *best = eh_hgn_beam_get_best(&tracker); + assert(best != NULL); + + print_beam(best, "Final best beam"); + printf(" [INFO] Active beams after step 2: %u\n", active); + + assert(best->seq_len == 3); + assert(best->tokens[0] == 0); + assert(best->tokens[1] == 1); + assert(best->tokens[2] == 3); + assert(best->is_finished == true && "Sink node must set is_finished=true"); + printf(" [PASS] T5: Sequence 0โ†’1โ†’3 converged to terminal, is_finished=true\n"); + + /* โ”€โ”€ T6: REGRESSION โ€” Multi-beam parent tracking โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + * + * Old bug: eh_hgn_beam_step always copied paths[0] as parent. + * This test has EH_BEAM_WIDTH >= 2 so both "cat" (beam 0) and + * "dog" (beam 1) beams exist after step 1. + * After step 2, beam "dogโ†’ends" must have token history [0,2,3], + * NOT [0,1,3] (which is what the old code would produce by always + * copying paths[0]). + * โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + printf("\n --- T6: Multi-beam parent tracking regression test ---\n"); + + EH_HGN_BeamTracker tracker2; + eh_hgn_beam_init(&tracker2, &dag, prompt, 1); + + /* Step 1: both "cat"(1) and "dog"(2) should appear as separate beams */ + eh_hgn_beam_step(&tracker2, &dag); + + printf(" [INFO] Beams after step 1:\n"); + for (uint32_t b = 0; b < tracker2.active_paths; b++) { + print_beam(&tracker2.paths[b], "beam"); + } + + /* Step 2: expand all beams */ + eh_hgn_beam_step(&tracker2, &dag); + + printf(" [INFO] Beams after step 2:\n"); + bool found_cat_path = false; + bool found_dog_path = false; + + for (uint32_t b = 0; b < tracker2.active_paths; b++) { + print_beam(&tracker2.paths[b], "beam"); + const EH_HGN_BeamPath *p = &tracker2.paths[b]; + if (p->seq_len == 3 && p->tokens[1] == 1 && p->tokens[2] == 3) + found_cat_path = true; + if (p->seq_len == 3 && p->tokens[1] == 2 && p->tokens[2] == 3) + found_dog_path = true; + } + + assert(found_cat_path && "Must find path 0->1->3 (cat sequence)"); + assert(found_dog_path && "Must find path 0->2->3 (dog sequence) โ€” BUG if missing!"); + printf(" [PASS] T6: Both 0โ†’1โ†’3 and 0โ†’2โ†’3 paths found (parent tracking correct)\n"); + + /* โ”€โ”€ T7: Step on finished tracker returns 0 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + uint32_t already_done = eh_hgn_beam_step(&tracker, &dag); + assert(already_done == 0 && "Fully finished tracker must return 0 active"); + printf(" [PASS] T7: eh_hgn_beam_step returns 0 when all beams finished\n"); + + /* โ”€โ”€ Cleanup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + eh_arena_destroy(arena); + remove(BIN_FILE); + + printf("\n=== ALL LAYER 3 BEAM TESTS PASSED! โœ“ ===\n"); + return 0; +} diff --git a/tests/test_eh_hgn_collapse.c b/tests/test_eh_hgn_collapse.c new file mode 100644 index 0000000..2d5edcc --- /dev/null +++ b/tests/test_eh_hgn_collapse.c @@ -0,0 +1,265 @@ +/* ================================================================ + * tests/test_eh_hgn_collapse.c โ€” Unit test Layer 2 + * + * Compile tแปซ EventHorizon/tests/: + * gcc -O3 -std=c99 -mavx2 -Wall -Wextra -I../include \ + * test_eh_hgn_collapse.c \ + * ../src/hgn/eh_hgn_collapse.c \ + * ../src/hgn/eh_hgn_dag.c \ + * ../src/core/eh_arena.c \ + * -o test_eh_hgn_collapse -lm && ./test_eh_hgn_collapse + * ================================================================ */ + +#include "hgn/eh_hgn_collapse.h" + +#include +#include +#include +#include + +/* ---------------------------------------------------------------- + * Test framework + * ---------------------------------------------------------------- */ +static int g_run = 0, g_pass = 0; + +#define ASSERT(cond, msg) do { \ + g_run++; \ + if (!(cond)) fprintf(stderr, " [FAIL] %s (L%d)\n", msg, __LINE__);\ + else { fprintf(stderr, " [PASS] %s\n", msg); g_pass++; } \ +} while(0) +#define ASSERT_EQ(a,b,msg) ASSERT((a)==(b), msg) +#define ASSERT_FEQ(a,b,eps,msg) ASSERT(fabsf((a)-(b)) < (eps), msg) +#define ASSERT_GT(a,b,msg) ASSERT((a)>(b), msg) +#define ASSERT_LT(a,b,msg) ASSERT((a)<(b), msg) + +/* ---------------------------------------------------------------- + * Helper: tแบกo vector normalized dim=128 + * ---------------------------------------------------------------- */ +static void make_vec(float *v, float base, float step) +{ + float norm = 0.0f; + for (uint32_t i = 0; i < EH_HGN_EMBED_DIM; i++) { + v[i] = base + step * (float)i; + norm += v[i] * v[i]; + } + norm = sqrtf(norm); + if (norm > 1e-8f) + for (uint32_t i = 0; i < EH_HGN_EMBED_DIM; i++) v[i] /= norm; +} + +/* ---------------------------------------------------------------- + * Helper: tแบกo dummy DAG nhแป trong arena (vocab=4, fanout=2) + * ---------------------------------------------------------------- */ +static const char *DAG_BIN = "/tmp/test_collapse_dag.bin"; + +static int build_dag_bin(void) +{ + const uint32_t V = 4, E = 8, K = 2; + FILE *fp = fopen(DAG_BIN, "wb"); + if (!fp) return -1; + + EH_HGN_DagFileHeader hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.magic = EH_HGN_DAG_MAGIC; hdr.version = EH_HGN_DAG_VERSION; + hdr.vocab_size = V; hdr.total_edges = E; + hdr.embed_dim = EH_HGN_EMBED_DIM; hdr.max_fanout = K; + fwrite(&hdr, sizeof(hdr), 1, fp); + + /* node_embed: vec[k] = i + k*0.01 */ + EH_HGN_NodeEmbed ne; + for (uint32_t i = 0; i < V; i++) { + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + ne.vec[k] = (float)i + (float)k * 0.01f; + fwrite(&ne, sizeof(ne), 1, fp); + } + + /* node_adj */ + EH_HGN_NodeAdj adj; + for (uint32_t i = 0; i < V; i++) { + adj.edge_offset = i * K; adj.edge_count = K; + fwrite(&adj, sizeof(adj), 1, fp); + } + + /* edge_compact */ + EH_HGN_EdgeCompact ec; + for (uint32_t i = 0; i < V; i++) { + ec.dst=(i+1)%V; ec.prior=0.8f; ec.weight_idx=i*K; + fwrite(&ec, sizeof(ec), 1, fp); + ec.dst=(i+2)%V; ec.prior=0.2f; ec.weight_idx=i*K+1; + fwrite(&ec, sizeof(ec), 1, fp); + } + + /* Padding */ + long pos = ftell(fp), apos = (pos+31L)&~31L; + if (apos != pos) { uint8_t p[32]={0}; fwrite(p,(size_t)(apos-pos),1,fp); } + + /* weight_pool: vec[k] = e + k*0.01 */ + EH_HGN_EdgeWeight ew; + for (uint32_t e = 0; e < E; e++) { + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + ew.vec[k] = (float)e + (float)k * 0.01f; + fwrite(&ew, sizeof(ew), 1, fp); + } + fclose(fp); + return 0; +} + +/* ---------------------------------------------------------------- + * main + * ---------------------------------------------------------------- */ +int main(void) +{ + fprintf(stderr, "\n=== EH_HGN_CollapseGating Tests ===\n\n"); + + ASSERT(build_dag_bin() == 0, "T0: build dummy DAG bin"); + + EH_Arena *arena = eh_arena_create(8 * 1024 * 1024); /* 8MB */ + ASSERT(arena != NULL, "T0: arena create"); + + /* Load DAG */ + EH_HGN_BaseDag dag; memset(&dag, 0, sizeof(dag)); + ASSERT(eh_hgn_dag_load(arena, DAG_BIN, &dag) == EH_HGN_OK, + "T0: DAG load"); + + /* ---- T1: ctx init ---- */ + fprintf(stderr, "\n-- T1: ctx init --\n"); + EH_HGN_CollapseCtx ctx __attribute__((aligned(32))); + ASSERT(eh_hgn_collapse_ctx_init(&ctx, arena) == 0, "T1: ctx init OK"); + ASSERT(ctx.mutant_pool != NULL, "T1: mutant_pool allocated"); + ASSERT(ctx.h_prev_valid == false, "T1: h_prev_valid=false initially"); + ASSERT_FEQ(ctx.collapse_thresh, EH_HGN_COLLAPSE_THRESH, 1e-6f, + "T1: collapse_thresh default"); + + /* ---- T2: step ฤ‘แบงu luรดn EXPAND (chฦฐa cรณ h_prev) ---- */ + fprintf(stderr, "\n-- T2: First step always EXPAND --\n"); + float h0[EH_HGN_EMBED_DIM] __attribute__((aligned(32))); + make_vec(h0, 1.0f, 0.01f); + EH_HGN_CollapseDecision d = eh_hgn_collapse_gate(&ctx, h0); + ASSERT_EQ(d, EH_HGN_EXPAND, "T2: first step = EXPAND"); + ASSERT(ctx.h_prev_valid == true, "T2: h_prev_valid=true after step"); + ASSERT_EQ(ctx.expand_count, 1u, "T2: expand_count==1"); + ASSERT_EQ(ctx.step_count, 1u, "T2: step_count==1"); + + /* ---- T3: vector giแป‘ng hแป‡t โ†’ COLLAPSE ---- */ + fprintf(stderr, "\n-- T3: Identical vector โ†’ COLLAPSE --\n"); + /* h_prev = h0, dรนng lแบกi h0 โ†’ cosine = 1.0 > 0.92 */ + d = eh_hgn_collapse_gate(&ctx, h0); + ASSERT_EQ(d, EH_HGN_COLLAPSE, "T3: same vector = COLLAPSE"); + ASSERT_EQ(ctx.collapse_count, 1u, "T3: collapse_count==1"); + + /* ---- T4: vector ngฦฐแปฃc chiแปu โ†’ EXPAND ---- */ + fprintf(stderr, "\n-- T4: Orthogonal vector โ†’ EXPAND --\n"); + float h_ortho[EH_HGN_EMBED_DIM] __attribute__((aligned(32))); + /* Vector trแปฑc giao vแป›i h0: ฤ‘แบฃo dแบฅu mแป™t nแปญa chiแปu */ + memcpy(h_ortho, h0, sizeof(h0)); + for (uint32_t i = 0; i < EH_HGN_EMBED_DIM / 2; i++) h_ortho[i] *= -1.0f; + /* Re-normalize */ + float n = 0.0f; + for (uint32_t i = 0; i < EH_HGN_EMBED_DIM; i++) n += h_ortho[i]*h_ortho[i]; + n = sqrtf(n); + for (uint32_t i = 0; i < EH_HGN_EMBED_DIM; i++) h_ortho[i] /= n; + + d = eh_hgn_collapse_gate(&ctx, h_ortho); + ASSERT_EQ(d, EH_HGN_EXPAND, "T4: orthogonal vector = EXPAND"); + + /* ---- T5: mean_pool output ---- */ + fprintf(stderr, "\n-- T5: mean_pool --\n"); + float pooled[EH_HGN_EMBED_DIM]; + eh_hgn_collapse_mean_pool(&dag, 0, pooled); + + /* Tแปฑ tรญnh expected: mean cแปงa weight_pool[0] vร  weight_pool[1] */ + float expected[EH_HGN_EMBED_DIM]; + const float *w0 = eh_hgn_dag_edge_weight(&dag, eh_hgn_dag_edges_begin(&dag, 0)); + const float *w1 = eh_hgn_dag_edge_weight(&dag, eh_hgn_dag_edges_begin(&dag, 0) + 1); + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + expected[k] = (w0[k] + w1[k]) * 0.5f; + + float max_diff = 0.0f; + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) { + float d2 = fabsf(pooled[k] - expected[k]); + if (d2 > max_diff) max_diff = d2; + } + ASSERT(max_diff < 1e-5f, "T5: mean_pool matches manual calculation"); + + /* ---- T6: beam_entropy ---- */ + fprintf(stderr, "\n-- T6: beam_entropy --\n"); + /* Uniform distribution โ†’ entropy = log(N) */ + float uniform[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + float H_uniform = eh_hgn_beam_entropy(uniform, 4); + ASSERT_FEQ(H_uniform, logf(4.0f), 1e-4f, "T6: uniform entropy=log(4)"); + + /* Deterministic (score cแปฑc cao vs cแปฑc thแบฅp) โ†’ entropy โ‰ˆ 0 */ + float deterministic[2] = {100.0f, -100.0f}; + float H_det = eh_hgn_beam_entropy(deterministic, 2); + ASSERT_LT(H_det, 0.01f, "T6: near-deterministic entropy โ‰ˆ 0"); + + /* High entropy signal (uniform) > entropy_thresh mแบทc ฤ‘แป‹nh? + * log(4) โ‰ˆ 1.386, entropy_thresh = 1.80 โ†’ uniform 4 beams chฦฐa trigger + * Dรนng uniform 8 beams: log(8) โ‰ˆ 2.079 > 1.80 */ + float uniform8[8] = {1,1,1,1,1,1,1,1}; + float H8 = eh_hgn_beam_entropy(uniform8, 8); + ASSERT_GT(H8, ctx.entropy_thresh, "T6: 8-beam uniform > entropy_thresh"); + + /* ---- T7: mutant_spawn ---- */ + fprintf(stderr, "\n-- T7: mutant_spawn --\n"); + /* Reset ctx ฤ‘แปƒ cรณ h_prev hแปฃp lแป‡ */ + eh_hgn_collapse_ctx_reset(&ctx); + eh_hgn_collapse_gate(&ctx, h0); /* step 1: h_prev = h0 */ + + EH_HGN_MutantNode *m = eh_hgn_mutant_spawn(&ctx, 0, 1, 2.5f); + ASSERT(m != NULL, "T7: mutant spawned"); + ASSERT(m->active == true, "T7: mutant active"); + ASSERT_EQ(m->src_token, 0u,"T7: src_token==0"); + ASSERT_EQ(m->dst_token, 1u,"T7: dst_token==1"); + ASSERT_EQ(ctx.mutant_active, 1u, "T7: mutant_active==1"); + ASSERT_EQ(ctx.mutant_count, 1u, "T7: mutant_count==1"); + + /* Vector mutant phแบฃi khรกc h_prev (ฤ‘รฃ perturb) */ + float diff_norm = 0.0f; + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) { + float d2 = m->vec[k] - ctx.h_prev[k]; + diff_norm += d2 * d2; + } + ASSERT_GT(sqrtf(diff_norm), 1e-6f, "T7: mutant vec != h_prev"); + + /* Spawn ฤ‘แปง EH_HGN_MAX_MUTANTS slots */ + for (uint32_t i = 1; i < EH_HGN_MAX_MUTANTS; i++) + eh_hgn_mutant_spawn(&ctx, i, i+1, 1.0f + (float)i); + ASSERT_EQ(ctx.mutant_active, EH_HGN_MAX_MUTANTS, "T7: pool full"); + + /* Pool ฤ‘แบงy โ†’ spawn thรชm trแบฃ NULL */ + EH_HGN_MutantNode *overflow = eh_hgn_mutant_spawn(&ctx, 0, 0, 0.0f); + ASSERT(overflow == NULL, "T7: overflow returns NULL"); + + /* ---- T8: step_end deactivates all mutants ---- */ + fprintf(stderr, "\n-- T8: step_end --\n"); + eh_hgn_collapse_step_end(&ctx); + ASSERT_EQ(ctx.mutant_active, 0u, "T8: mutant_active==0 after step_end"); + for (uint32_t i = 0; i < EH_HGN_MAX_MUTANTS; i++) + ASSERT(ctx.mutant_pool[i].active == false, + "T8: all mutants deactivated"); + + /* ---- T9: ctx_reset giแปฏ thresholds ---- */ + fprintf(stderr, "\n-- T9: ctx_reset --\n"); + ctx.collapse_thresh = 0.5f; /* override */ + eh_hgn_collapse_ctx_reset(&ctx); + ASSERT_FEQ(ctx.collapse_thresh, 0.5f, 1e-6f, + "T9: reset preserves custom threshold"); + ASSERT_EQ(ctx.step_count, 0u, "T9: step_count reset to 0"); + ASSERT(ctx.h_prev_valid == false, "T9: h_prev_valid=false after reset"); + + /* ---- T10: dump_stats no crash ---- */ + fprintf(stderr, "\n-- T10: dump_stats --\n"); + eh_hgn_collapse_gate(&ctx, h0); + eh_hgn_collapse_gate(&ctx, h0); /* trigger collapse */ + eh_hgn_collapse_dump_stats(&ctx); + ASSERT(1, "T10: dump_stats no crash"); + eh_hgn_collapse_dump_stats(NULL); + ASSERT(1, "T10: dump_stats(NULL) no crash"); + + /* ---- Summary ---- */ + fprintf(stderr, "\n=== %d/%d passed ===\n\n", g_pass, g_run); + + eh_arena_destroy(arena); + return (g_pass == g_run) ? 0 : 1; +} \ No newline at end of file diff --git a/tests/test_eh_hgn_dag.c b/tests/test_eh_hgn_dag.c new file mode 100644 index 0000000..b54e39a --- /dev/null +++ b/tests/test_eh_hgn_dag.c @@ -0,0 +1,218 @@ +/* ================================================================ + * tests/test_eh_hgn_dag.c โ€” Unit test Layer 1: EH_HGN_BaseDag + * + * Compile tแปซ thฦฐ mแปฅc EventHorizon/: + * gcc -O3 -std=c99 -mavx2 -Wall -Wextra \ + * -I include \ + * tests/test_eh_hgn_dag.c src/hgn/eh_hgn_dag.c \ + * -o tests/test_eh_hgn_dag -lm && tests/test_eh_hgn_dag + * ================================================================ */ + +#define _POSIX_C_SOURCE 200112L + +#include "hgn/eh_hgn_dag.h" +#include "core/eh_arena.h" + +#include +#include +#include +#include + +/* ---------------------------------------------------------------- + * Arena globals for test + * ---------------------------------------------------------------- */ +static EH_Arena *g_arena_ptr = NULL; + +static int arena_init(size_t cap) +{ + g_arena_ptr = eh_arena_create(cap); + return (g_arena_ptr != NULL) ? 0 : -1; +} + +static void arena_destroy(void) +{ + if (g_arena_ptr) { + eh_arena_destroy(g_arena_ptr); + g_arena_ptr = NULL; + } +} + +/* ---------------------------------------------------------------- + * Test framework + * ---------------------------------------------------------------- */ +static int g_run = 0, g_pass = 0; + +#define ASSERT(cond, msg) do { \ + g_run++; \ + if (!(cond)) fprintf(stderr, " [FAIL] %s (L%d)\n", msg, __LINE__);\ + else { fprintf(stderr, " [PASS] %s\n", msg); g_pass++; } \ +} while(0) + +#define ASSERT_EQ(a,b,msg) ASSERT((a)==(b), msg) +#define ASSERT_NEQ(a,b,msg) ASSERT((a)!=(b), msg) +#define ASSERT_FEQ(a,b,msg) ASSERT(fabsf((a)-(b)) < 1e-4f, msg) + +/* ---------------------------------------------------------------- + * Tแบกo dummy ehdag.bin: vocab=8, edges=16, fanout=2 + * ---------------------------------------------------------------- */ +static const char *BIN = "/tmp/test_eh_hgn_dag.bin"; + +static int build_dummy(void) +{ + const uint32_t V = 8, E = 16, K = 2; + + FILE *fp = fopen(BIN, "wb"); + if (!fp) return -1; + + /* Header */ + EH_HGN_DagFileHeader hdr = { + .magic = EH_HGN_DAG_MAGIC, + .version = EH_HGN_DAG_VERSION, + .vocab_size = V, + .total_edges = E, + .embed_dim = EH_HGN_EMBED_DIM, + .max_fanout = K, + .reserved = {0, 0} + }; + fwrite(&hdr, sizeof(hdr), 1, fp); + + /* node_embed: vec[k] = i*128 + k */ + EH_HGN_NodeEmbed ne; + for (uint32_t i = 0; i < V; i++) { + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + ne.vec[k] = (float)(i * EH_HGN_EMBED_DIM + k); + fwrite(&ne, sizeof(ne), 1, fp); + } + + /* node_adj: CSR liรชn tแปฅc, 2 edges/node */ + EH_HGN_NodeAdj adj; + for (uint32_t i = 0; i < V; i++) { + adj.edge_offset = i * K; + adj.edge_count = K; + fwrite(&adj, sizeof(adj), 1, fp); + } + + /* edge_compact: dst=(i+1)%V vร  (i+2)%V */ + EH_HGN_EdgeCompact ec; + for (uint32_t i = 0; i < V; i++) { + ec.dst = (i + 1) % V; + ec.prior = 0.1f * (float)(i * K); + ec.weight_idx = i * K; + fwrite(&ec, sizeof(ec), 1, fp); + + ec.dst = (i + 2) % V; + ec.prior = 0.1f * (float)(i * K + 1); + ec.weight_idx = i * K + 1; + fwrite(&ec, sizeof(ec), 1, fp); + } + + /* Padding ฤ‘แบฟn 32-byte boundary */ + long pos = ftell(fp); + long apos = (pos + 31L) & ~31L; + if (apos != pos) { + uint8_t pad[32] = {0}; + fwrite(pad, (size_t)(apos - pos), 1, fp); + } + + /* weight_pool: vec[k] = e*128 + k + 1000 */ + EH_HGN_EdgeWeight ew; + for (uint32_t e = 0; e < E; e++) { + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + ew.vec[k] = (float)(e * EH_HGN_EMBED_DIM + k + 1000); + fwrite(&ew, sizeof(ew), 1, fp); + } + + fclose(fp); + return 0; +} + +/* ---------------------------------------------------------------- + * main + * ---------------------------------------------------------------- */ +int main(void) +{ + fprintf(stderr, "\n=== EH_HGN_BaseDag Tests ===\n\n"); + + ASSERT(build_dummy() == 0, "T0: build dummy bin"); + ASSERT(arena_init(4*1024*1024)==0, "T0: arena 4MB init"); + + /* T1: Load */ + fprintf(stderr, "\n-- T1: Load --\n"); + EH_HGN_BaseDag dag; memset(&dag, 0, sizeof(dag)); + EH_HGN_Status rc = eh_hgn_dag_load(g_arena_ptr, BIN, &dag); + ASSERT_EQ(rc, EH_HGN_OK, "T1: load returns EH_HGN_OK"); + + /* T2: Fields */ + fprintf(stderr, "\n-- T2: Header fields --\n"); + ASSERT_EQ(dag.vocab_size, 8u, "T2: vocab_size"); + ASSERT_EQ(dag.total_edges, 16u, "T2: total_edges"); + ASSERT_EQ(dag.embed_dim, 128u, "T2: embed_dim"); + ASSERT_EQ(dag.max_fanout, 2u, "T2: max_fanout"); + ASSERT_NEQ((uintptr_t)dag.node_embed, 0u, "T2: node_embed ptr"); + ASSERT_NEQ((uintptr_t)dag.edge_compact,0u, "T2: edge_compact ptr"); + ASSERT_NEQ((uintptr_t)dag.weight_pool, 0u, "T2: weight_pool ptr"); + + /* T3: Edge iteration node 0 */ + fprintf(stderr, "\n-- T3: Edge iteration --\n"); + const EH_HGN_EdgeCompact *b = eh_hgn_dag_edges_begin(&dag, 0); + const EH_HGN_EdgeCompact *e = eh_hgn_dag_edges_end(&dag, 0); + ASSERT_EQ((uint32_t)(e - b), 2u, "T3: fanout==2"); + ASSERT_EQ(b[0].dst, 1u, "T3: edge[0].dst==1"); + ASSERT_EQ(b[1].dst, 2u, "T3: edge[1].dst==2"); + ASSERT_EQ(b[0].weight_idx, 0u, "T3: edge[0].weight_idx==0"); + ASSERT_EQ(b[1].weight_idx, 1u, "T3: edge[1].weight_idx==1"); + ASSERT_FEQ(b[0].prior, 0.0f, "T3: edge[0].prior==0.0"); + + /* T4: node_vec */ + fprintf(stderr, "\n-- T4: node_vec --\n"); + const float *nv = eh_hgn_dag_node_vec(&dag, 3); + ASSERT_NEQ((uintptr_t)nv, 0u, "T4: node_vec(3)!=NULL"); + ASSERT_FEQ(nv[0], 384.0f, "T4: nv[0]==384"); + ASSERT_FEQ(nv[127], 511.0f, "T4: nv[127]==511"); + ASSERT_EQ((uintptr_t)eh_hgn_dag_node_vec(&dag, 8), 0u, "T4: OOB==NULL"); + ASSERT_EQ((uintptr_t)eh_hgn_dag_node_vec(&dag, 99999u), 0u, "T4: OOB2==NULL"); + + /* T5: edge_weight */ + fprintf(stderr, "\n-- T5: edge_weight --\n"); + const float *ew = eh_hgn_dag_edge_weight(&dag, &b[1]); + ASSERT_NEQ((uintptr_t)ew, 0u, "T5: weight!=NULL"); + ASSERT_FEQ(ew[0], 1128.0f, "T5: weight[1][0]==1128"); + ASSERT_FEQ(ew[127], 1255.0f, "T5: weight[1][127]==1255"); + + /* T6: EH_HGN_FOR_EDGES macro */ + fprintf(stderr, "\n-- T6: FOR_EDGES macro --\n"); + uint32_t cnt = 0; + EH_HGN_FOR_EDGES(&dag, 5, edge) { cnt++; (void)edge; } + ASSERT_EQ(cnt, 2u, "T6: FOR_EDGES count==2 for node 5"); + + /* T7: full graph count */ + fprintf(stderr, "\n-- T7: Full graph iteration --\n"); + uint32_t total = 0; + for (uint32_t i = 0; i < dag.vocab_size; i++) { + EH_HGN_FOR_EDGES(&dag, i, ep) { total++; (void)ep; } + } + ASSERT_EQ(total, 16u, "T7: total edges==16"); + + /* T8: fanout helper */ + fprintf(stderr, "\n-- T8: fanout() --\n"); + ASSERT_EQ(eh_hgn_dag_fanout(&dag, 0), 2u, "T8: fanout(0)==2"); + ASSERT_EQ(eh_hgn_dag_fanout(&dag, 7), 2u, "T8: fanout(7)==2"); + ASSERT_EQ(eh_hgn_dag_fanout(&dag, 99), 0u,"T8: fanout(OOB)==0"); + + /* T9: OOB iterators */ + fprintf(stderr, "\n-- T9: OOB iterators --\n"); + ASSERT_EQ((uintptr_t)eh_hgn_dag_edges_begin(&dag, 8), 0u, "T9: begin OOB"); + ASSERT_EQ((uintptr_t)eh_hgn_dag_edges_end(&dag, 8), 0u, "T9: end OOB"); + + /* T10: dump_info */ + fprintf(stderr, "\n-- T10: dump_info --\n"); + eh_hgn_dag_dump_info(&dag); + ASSERT(1, "T10: dump_info no crash"); + eh_hgn_dag_dump_info(NULL); + ASSERT(1, "T10: dump_info(NULL) no crash"); + + /* Summary */ + fprintf(stderr, "\n=== %d/%d passed ===\n\n", g_pass, g_run); + arena_destroy(); + return (g_pass == g_run) ? 0 : 1; +} \ No newline at end of file diff --git a/tests/test_eh_hgn_engine.c b/tests/test_eh_hgn_engine.c new file mode 100644 index 0000000..846495c --- /dev/null +++ b/tests/test_eh_hgn_engine.c @@ -0,0 +1,248 @@ +/* ================================================================ + * tests/test_eh_hgn_engine.c โ€” Unit test Layer 4 (Unified Engine) + * + * Test toร n bแป™ pipeline: DAG โ†’ Collapse โ†’ Beam โ†’ Results + * + * Compile tแปซ tests/: + * gcc -O3 -std=c99 -mavx2 -Wall -Wextra -I../include \ + * test_eh_hgn_engine.c \ + * ../src/hgn/eh_hgn_engine.c \ + * ../src/hgn/eh_hgn_collapse.c \ + * ../src/hgn/eh_hgn_dag.c \ + * ../src/hgn/eh_beam_search.c \ + * ../src/core/eh_arena.c \ + * -o test_eh_hgn_engine -lm && ./test_eh_hgn_engine + * ================================================================ */ + +#include "hgn/eh_hgn_engine.h" + +#include +#include +#include +#include + +/* ---------------------------------------------------------------- + * Test framework + * ---------------------------------------------------------------- */ +static int g_run = 0, g_pass = 0; + +#define ASSERT(cond, msg) do { \ + g_run++; \ + if (!(cond)) fprintf(stderr, " [FAIL] %s (L%d)\n", msg, __LINE__);\ + else { fprintf(stderr, " [PASS] %s\n", msg); g_pass++; } \ +} while(0) +#define ASSERT_EQ(a,b,msg) ASSERT((a)==(b), msg) +#define ASSERT_GT(a,b,msg) ASSERT((a)>(b), msg) +#define ASSERT_LE(a,b,msg) ASSERT((a)<=(b), msg) + +/* ---------------------------------------------------------------- + * Helper: tแบกo tiny test DAG (vocab=4, cแบฅu trรบc: 0โ†’1, 0โ†’2, 1โ†’3, 2โ†’3) + * ---------------------------------------------------------------- */ +static const char *DAG_BIN = "/tmp/test_engine_dag.bin"; + +static int build_test_dag(void) +{ + const uint32_t V = 4, E = 4; + FILE *fp = fopen(DAG_BIN, "wb"); + if (!fp) return -1; + + EH_HGN_DagFileHeader hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.magic = EH_HGN_DAG_MAGIC; + hdr.version = EH_HGN_DAG_VERSION; + hdr.vocab_size = V; + hdr.total_edges = E; + hdr.embed_dim = EH_HGN_EMBED_DIM; + hdr.max_fanout = 2; + fwrite(&hdr, sizeof(hdr), 1, fp); + + /* Node embeddings: vec[k] = i + k*0.01 */ + EH_HGN_NodeEmbed ne; + for (uint32_t i = 0; i < V; i++) { + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + ne.vec[k] = (float)i + (float)k * 0.01f; + fwrite(&ne, sizeof(ne), 1, fp); + } + + /* Node adjacency (CSR) */ + EH_HGN_NodeAdj adj[4] = { + {0, 2}, /* Token 0 โ†’ 2 edges (offset 0) */ + {2, 1}, /* Token 1 โ†’ 1 edge (offset 2) */ + {3, 1}, /* Token 2 โ†’ 1 edge (offset 3) */ + {4, 0} /* Token 3 โ†’ sink node */ + }; + fwrite(adj, sizeof(EH_HGN_NodeAdj), 4, fp); + + /* Edge compact data */ + EH_HGN_EdgeCompact ec; + memset(&ec, 0, sizeof(ec)); + + /* Edge 0: 0โ†’1 (high prior) */ + ec.dst = 1; ec.prior = 0.8f; ec.weight_idx = 0; + fwrite(&ec, sizeof(ec), 1, fp); + + /* Edge 1: 0โ†’2 (low prior) */ + ec.dst = 2; ec.prior = 0.2f; ec.weight_idx = 1; + fwrite(&ec, sizeof(ec), 1, fp); + + /* Edge 2: 1โ†’3 */ + ec.dst = 3; ec.prior = 0.9f; ec.weight_idx = 2; + fwrite(&ec, sizeof(ec), 1, fp); + + /* Edge 3: 2โ†’3 */ + ec.dst = 3; ec.prior = 0.1f; ec.weight_idx = 3; + fwrite(&ec, sizeof(ec), 1, fp); + + /* Padding to 32-byte alignment */ + long pos = ftell(fp); + long apos = (pos + 31L) & ~31L; + if (apos != pos) { + uint8_t pad[32] = {0}; + fwrite(pad, (size_t)(apos - pos), 1, fp); + } + + /* Edge weights: vec[k] = e + k*0.01 */ + EH_HGN_EdgeWeight ew; + for (uint32_t e = 0; e < E; e++) { + for (uint32_t k = 0; k < EH_HGN_EMBED_DIM; k++) + ew.vec[k] = (float)e + (float)k * 0.01f; + fwrite(&ew, sizeof(ew), 1, fp); + } + + fclose(fp); + return 0; +} + +/* ---------------------------------------------------------------- + * main + * ---------------------------------------------------------------- */ +int main(void) +{ + fprintf(stderr, "\n=== EH_HGN_Engine Integration Tests ===\n\n"); + + /* --- T0: Setup --- */ + fprintf(stderr, "-- T0: Setup --\n"); + ASSERT(build_test_dag() == 0, "T0: build test DAG"); + + EH_Arena *arena = eh_arena_create(16 * 1024 * 1024); /* 16MB */ + ASSERT(arena != NULL, "T0: arena create"); + + EH_HGN_BaseDag dag; + memset(&dag, 0, sizeof(dag)); + ASSERT(eh_hgn_dag_load(arena, DAG_BIN, &dag) == EH_HGN_OK, + "T0: DAG load"); + + /* --- T1: Default config --- */ + fprintf(stderr, "\n-- T1: Default config --\n"); + EH_HGN_EngineConfig cfg = eh_hgn_default_config(); + ASSERT(cfg.collapse_thresh > 0.9f, "T1: default collapse_thresh=0.92"); + ASSERT(cfg.entropy_thresh > 1.7f, "T1: default entropy_thresh=1.80"); + ASSERT(cfg.enable_collapse == true, "T1: collapse enabled by default"); + ASSERT(cfg.enable_mutants == true, "T1: mutants enabled by default"); + + /* --- T2: Session init vแป›i prompt --- */ + fprintf(stderr, "\n-- T2: Session init --\n"); + EH_HGN_InferenceSession session; + uint32_t prompt[1] = {0}; /* Start tแปซ token 0 */ + + int rc = eh_hgn_session_init(&session, &dag, arena, prompt, 1, NULL); + ASSERT(rc == 0, "T2: session init OK"); + ASSERT(session.dag == &dag, "T2: DAG reference set"); + ASSERT(session.step_count == 0, "T2: step_count=0 initially"); + ASSERT(session.is_finished == false, "T2: not finished initially"); + + /* --- T3: Get beams sau init (chแป‰ cรณ prompt) --- */ + fprintf(stderr, "\n-- T3: Initial beams --\n"); + const EH_HGN_BeamPath *beams = eh_hgn_session_get_beams(&session); + ASSERT(beams != NULL, "T3: get_beams returns non-NULL"); + ASSERT(beams[0].seq_len == 1, "T3: first beam has prompt"); + ASSERT(beams[0].tokens[0] == 0, "T3: prompt token=0"); + + /* --- T4: First inference step --- */ + fprintf(stderr, "\n-- T4: First step --\n"); + uint32_t active = eh_hgn_session_step(&session); + ASSERT(active > 0, "T4: beams still active after step"); + ASSERT(session.step_count == 1, "T4: step_count=1"); + ASSERT(session.is_finished == false, "T4: not finished yet"); + + /* Beam ฤ‘รฃ expand tแปซ token 0 โ†’ [1, 2] */ + const EH_HGN_BeamPath *best = eh_hgn_session_get_best(&session); + ASSERT(best != NULL, "T4: get_best returns non-NULL"); + ASSERT(best->seq_len == 2, "T4: sequence grew to 2 tokens"); + + /* Token thแปฉ 2 cรณ thแปƒ lร  1 hoแบทc 2 (tรนy vร o beam scoring) */ + uint32_t selected_token = best->tokens[1]; + fprintf(stderr, " [INFO] Selected token: %u (expected 1 or 2)\n", selected_token); + ASSERT(selected_token == 1u || selected_token == 2u, "T4: selected valid token"); + + /* --- T5: Second step (convergence tแป›i sink token 3) --- */ + fprintf(stderr, "\n-- T5: Second step --\n"); + active = eh_hgn_session_step(&session); + ASSERT(session.step_count == 2, "T5: step_count=2"); + + best = eh_hgn_session_get_best(&session); + ASSERT(best->seq_len == 3, "T5: sequence grew to 3 tokens"); + ASSERT_EQ(best->tokens[2], 3u, "T5: converged to sink token 3"); + + /* --- T6: Third step (sink node โ†’ all beams finished) --- */ + fprintf(stderr, "\n-- T6: Terminal step --\n"); + active = eh_hgn_session_step(&session); + ASSERT(active == 0, "T6: no active beams (all finished)"); + ASSERT(session.is_finished == true, "T6: generation finished"); + ASSERT(eh_hgn_session_is_done(&session), "T6: is_done() returns true"); + + /* --- T7: Session reset vแป›i prompt mแป›i --- */ + fprintf(stderr, "\n-- T7: Session reset --\n"); + uint32_t new_prompt[2] = {0, 1}; + eh_hgn_session_reset(&session, new_prompt, 2); + ASSERT(session.step_count == 0, "T7: step_count reset to 0"); + ASSERT(session.is_finished == false, "T7: not finished after reset"); + + beams = eh_hgn_session_get_beams(&session); + ASSERT(beams[0].seq_len == 2, "T7: new prompt has 2 tokens"); + ASSERT(beams[0].tokens[0] == 0, "T7: prompt[0]=0"); + ASSERT(beams[0].tokens[1] == 1, "T7: prompt[1]=1"); + + /* --- T8: Custom config (disable collapse + mutants) --- */ + fprintf(stderr, "\n-- T8: Custom config --\n"); + EH_HGN_EngineConfig custom_cfg = eh_hgn_default_config(); + custom_cfg.enable_collapse = false; + custom_cfg.enable_mutants = false; + custom_cfg.max_steps = 5; + + EH_HGN_InferenceSession session2; + rc = eh_hgn_session_init(&session2, &dag, arena, prompt, 1, &custom_cfg); + ASSERT(rc == 0, "T8: custom config session init OK"); + ASSERT(session2.config.enable_collapse == false, "T8: collapse disabled"); + ASSERT(session2.config.enable_mutants == false, "T8: mutants disabled"); + ASSERT(session2.config.max_steps == 5, "T8: max_steps=5"); + + /* --- T9: Max steps limit --- */ + fprintf(stderr, "\n-- T9: Max steps enforcement --\n"); + for (uint32_t i = 0; i < 10; i++) { + active = eh_hgn_session_step(&session2); + if (session2.is_finished) break; + } + ASSERT_LE(session2.step_count, 5u, "T9: step_count โ‰ค max_steps"); + + /* --- T10: Stats dump no crash --- */ + fprintf(stderr, "\n-- T10: Stats dump --\n"); + eh_hgn_session_dump_stats(&session); + ASSERT(1, "T10: dump_stats no crash"); + + eh_hgn_session_dump_beams(&session); + ASSERT(1, "T10: dump_beams no crash"); + + /* --- T11: Null safety --- */ + fprintf(stderr, "\n-- T11: Null safety --\n"); + ASSERT(eh_hgn_session_get_beams(NULL) == NULL, "T11: get_beams(NULL)"); + ASSERT(eh_hgn_session_get_best(NULL) == NULL, "T11: get_best(NULL)"); + eh_hgn_session_dump_stats(NULL); /* should not crash */ + ASSERT(1, "T11: dump_stats(NULL) no crash"); + + /* --- Summary --- */ + fprintf(stderr, "\n=== %d/%d passed ===\n\n", g_pass, g_run); + + eh_arena_destroy(arena); + return (g_pass == g_run) ? 0 : 1; +} diff --git a/tests/test_engine b/tests/test_engine deleted file mode 100644 index f084b1d..0000000 Binary files a/tests/test_engine and /dev/null differ