File size: 34,973 Bytes
5863ad5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
/* SPDX-License-Identifier: AGPL-3.0-or-later
 * decode.c β€” RPI inference engine (the heart of it)
 *
 * This is the llama.cpp equivalent for Resonant Permutation Inference.
 * No multiply-accumulate. Only permute, accumulate, measure, route.
 *
 * (c) 2026 Elyan Labs β€” Claude Opus + GPT-5.4 collaboration
 */

#include "rpi_format.h"
#include "rpi_runtime.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#if defined(__aarch64__)
#include <arm_neon.h>
#endif

/* Lane energy (L1 norm of the 64 int16 lanes). Used for routing decisions and
 * emission strength. NEON version is BIT-EXACT vs the scalar loop: vabal_s16
 * against zero computes |v| widened to s32 before accumulating, so even
 * INT16_MIN contributes exactly 32768. */
static inline int32_t rpi_lane_energy(const int16_t *lanes) {
#if defined(__aarch64__)
    int32x4_t acc = vdupq_n_s32(0);
    int16x4_t zero = vdup_n_s16(0);
    for (int j = 0; j < RPI_LANES; j += 8) {
        int16x8_t v = vld1q_s16(lanes + j);
        acc = vabal_s16(acc, vget_low_s16(v), zero);
        acc = vabal_s16(acc, vget_high_s16(v), zero);
    }
    return vaddvq_s32(acc);
#else
    int32_t energy = 0;
    for (int j = 0; j < RPI_LANES; j++) {
        int16_t v = lanes[j];
        energy += (v >= 0) ? v : -v;
    }
    return energy;
#endif
}

/* ── Coarse phase profiler (RPI_PROFILE=1) ──────────────────
 * Accumulates timebase ticks in three buckets so we can see where per-token
 * time actually goes (permute vs routing/resonance vs vocab scoring) before
 * optimizing the wrong one. Percentages only; no tb_freq needed. */
static uint64_t g_prof_perm = 0, g_prof_route = 0, g_prof_reson = 0, g_prof_emit = 0;
static uint64_t g_prof_wall0 = 0;   /* generation start, for coverage */
static int g_prof_on = -1;
static int prof_enabled(void) {
    if (g_prof_on < 0) {
        const char *e = getenv("RPI_PROFILE");
        g_prof_on = (e && e[0] && e[0] != '0') ? 1 : 0;
    }
    return g_prof_on;
}

/* ── State Management ───────────────────────────────────── */

void rpi_state_init(RPIState *st) {
    memset(st, 0, sizeof(*st));
    st->tb_prev = rpi_tb_now();
}

void rpi_state_reset(RPIState *st) {
    st->n_active = 0;
    st->round = 0;
    st->sig_prev = 0;
    memset(st->tok_scores, 0, sizeof(st->tok_scores));
    st->n_touched = 0;  /* tok_scores is fully zeroed, so the touched list restarts */
    memset(st->rep_history, 0, sizeof(st->rep_history));
    st->rep_pos = 0;    /* reset means reset: no repetition carry-over */
}

/* ── Hardware Detection ─────────────────────────────────── */

void rpi_hw_detect(RPIHWProfile *hw) {
    memset(hw, 0, sizeof(*hw));
    hw->n_threads = 1;
    hw->cache_line_bytes = 64;
    hw->l1_size_kb = 32;
    hw->l2_size_kb = 256;
    hw->l3_size_kb = 8192;

#if defined(__powerpc64__)
    hw->has_vsx = 1;
    hw->cache_line_bytes = 128;  /* POWER8 uses 128-byte cache lines */
    hw->l1_size_kb = 64;
    hw->l2_size_kb = 512;
    hw->l3_size_kb = 8192;
    hw->tb_freq = 512000000;     /* 512 MHz typical POWER8 timebase */
    hw->lat_l1_ns = 1.2f;
    hw->lat_l2_ns = 4.5f;
    hw->lat_l3_ns = 12.0f;
    hw->lat_dram_ns = 55.0f;
    fprintf(stderr, "[RPI] POWER8 detected: VSX + 128-byte cache lines\n");
#elif defined(__ALTIVEC__)
    hw->has_altivec = 1;
    hw->cache_line_bytes = 32;   /* G4 uses 32-byte cache lines */
    hw->l1_size_kb = 32;
    hw->l2_size_kb = 256;
    hw->l3_size_kb = 0;          /* G4 typically no L3 */
    hw->tb_freq = 25000000;      /* 25 MHz typical G4 timebase */
    hw->lat_l1_ns = 2.0f;
    hw->lat_l2_ns = 8.0f;
    hw->lat_l3_ns = 0.0f;
    hw->lat_dram_ns = 80.0f;
    fprintf(stderr, "[RPI] PowerPC G4 detected: AltiVec + 32-byte cache lines\n");
#elif defined(__aarch64__)
    /* ARM64 (Apple M-series, Pi 5, Graviton, Cortex...). NEON tbl is baseline.
     * Read the actual counter frequency instead of assuming one β€” it varies by
     * platform (Apple 24 MHz, Pi 5 54 MHz, Graviton differs). Wrong tb_freq
     * poisons both the decode deadline budget and the tok/s report. */
    {
        uint64_t cntfrq = 0;
        __asm__ volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq));
        hw->tb_freq = cntfrq ? cntfrq : 24000000;
    }
    hw->l3_size_kb = 0;          /* no conventional L3 on these parts */
#ifdef __APPLE__
    hw->cache_line_bytes = 128;  /* Apple M-series use 128-byte lines */
    hw->l1_size_kb = 128;        /* M-series P-core L1 data cache */
    hw->l2_size_kb = 16384;      /* shared L2 (P-cluster) */
    hw->lat_l1_ns = 0.9f; hw->lat_l2_ns = 3.5f; hw->lat_dram_ns = 45.0f;
    fprintf(stderr, "[RPI] Apple Silicon detected: NEON tbl, tb_freq=%llu Hz\n",
            (unsigned long long)hw->tb_freq);
#else
    hw->cache_line_bytes = 64;   /* generic AArch64 default */
    hw->l1_size_kb = 64;
    hw->l2_size_kb = 512;
    hw->lat_l1_ns = 1.2f; hw->lat_l2_ns = 4.0f; hw->lat_dram_ns = 60.0f;
    fprintf(stderr, "[RPI] AArch64 detected: NEON tbl, tb_freq=%llu Hz\n",
            (unsigned long long)hw->tb_freq);
#endif
    hw->lat_l3_ns = 0.0f;
#else
    hw->tb_freq = 1000000000;    /* assume ~1GHz for rdtsc */
    hw->lat_l1_ns = 1.0f;
    hw->lat_l2_ns = 4.0f;
    hw->lat_l3_ns = 10.0f;
    hw->lat_dram_ns = 50.0f;
    fprintf(stderr, "[RPI] Generic CPU detected\n");
#endif
}

/* ── Permutation Block Execution (generic C) ────────────── */

void rpi_run_perm_block_c(const RPIPermBlock *block,
                          const int16_t *in, int16_t *out) {
    /*
     * For each output lane:
     *   1. Read from source lane (permutation)
     *   2. Apply sign (negate if sign bit set)
     *   3. Accumulate into output
     *
     * This is the ZERO-MULTIPLY core. Only permute + add/subtract.
     */
    for (int i = 0; i < RPI_LANES; i++) {
        uint8_t src = block->src_idx[i];
        if (src == 0xFF) {
            /* Zero input β€” skip (W=0 in ternary) */
            continue;
        }
        int16_t val = in[src & 0x3F];  /* mask to valid range */

        /* Apply sign: bit i of sign_bits β†’ negate */
        if ((block->sign_bits >> i) & 1) {
            out[i] -= val;  /* W=-1: subtract */
        } else {
            out[i] += val;  /* W=+1: add */
        }
    }
}

/* ── Run permutation blocks for one cell ────────────────── */

static void run_cell_perms(const RPIModel *model, const RPICell *cell,
                           int16_t *lanes) {
    /* Select backend */
    void (*run_block)(const RPIPermBlock *, const int16_t *, int16_t *);

    /* Runtime override for A/B benchmarking: RPI_SCALAR=1 forces the generic
     * C backend on any platform. Resolved once. */
    static int force_scalar = -1;
    if (force_scalar < 0) {
        const char *e = getenv("RPI_SCALAR");
        force_scalar = (e && e[0] && e[0] != '0') ? 1 : 0;
    }

    if (force_scalar) {
        run_block = rpi_run_perm_block_c;
    } else {
#if defined(__POWER8_VECTOR__)
        run_block = rpi_run_perm_block_vsx;
#elif defined(__ALTIVEC__)
        run_block = rpi_run_perm_block_altivec;
#elif defined(__aarch64__)
        /* Build the TBL control table once so the hot path stays pure-vector. */
        rpi_neon_prepare(model->perm_blocks, model->hdr.n_perm_blocks);
        run_block = rpi_run_perm_block_neon;
#else
        run_block = rpi_run_perm_block_c;
#endif
    }

#if defined(__aarch64__)
    /* NEON fast path: the write-variant kernel (out = Β±in[src], no accumulate)
     * is arithmetically identical to memset+accumulate and is in-place safe
     * (all input loaded to registers before any store). So run each block
     * directly lanes -> lanes: no tmp, no 128B memset, no 128B memcpy per
     * block. RPI_OLD_PERM=1 keeps the old ping-pong for same-binary A/B. */
    static int old_perm = -1;
    if (old_perm < 0) {
        const char *e = getenv("RPI_OLD_PERM");
        old_perm = (e && e[0] && e[0] != '0') ? 1 : 0;
    }
    if (!force_scalar && !old_perm) {
        for (uint32_t b = 0; b < cell->perm_block_count; b++) {
            const RPIPermBlock *block = &model->perm_blocks[cell->perm_block_start + b];
            rpi_run_perm_block_neon_write(block, lanes, lanes);
        }
        return;
    }
#endif

    /* Temporary buffer for ping-pong */
    int16_t tmp[RPI_LANES];

    for (uint32_t b = 0; b < cell->perm_block_count; b++) {
        const RPIPermBlock *block = &model->perm_blocks[cell->perm_block_start + b];
        memset(tmp, 0, sizeof(tmp));
        run_block(block, lanes, tmp);

        /* Ping-pong: tmp becomes input for next block */
        memcpy(lanes, tmp, sizeof(tmp));
    }
}

/* ── Activate seed cells for a token ────────────────────── */

void rpi_activate_token(const RPIModel *model, RPIState *st,
                        uint32_t token_id) {
    if (!model->embed_seeds || token_id >= model->hdr.vocab_size)
        return;

    uint32_t seed_cell = model->embed_seeds[token_id];
    if (seed_cell >= model->hdr.n_cells)
        return;

    /* Add to active set if not already present and not full */
    for (uint32_t i = 0; i < st->n_active; i++) {
        if (st->active_ids[i] == seed_cell)
            return;  /* already active */
    }

    if (st->n_active < RPI_MAX_ACTIVE_CELLS) {
        uint32_t idx = st->n_active++;
        st->active_ids[idx] = seed_cell;
        memset(st->active_lanes[idx], 0, sizeof(st->active_lanes[idx]));

        /* Initialize lanes with a simple hash of token_id + cell_id */
        uint64_t h = rpi_fnv1a(&token_id, sizeof(token_id));
        for (int i = 0; i < RPI_LANES; i++) {
            st->active_lanes[idx][i] = (int16_t)((h >> (i % 48)) & 0xFF) - 128;
        }
    }
}

/* ── Cache Resonance Probing ────────────────────────────── */

void rpi_probe_resonance(const RPIModel *model, const RPIHWProfile *hw,
                         RPIState *st) {
    /*
     * For each active cell, measure access latency to its weight data.
     * Faster access = stronger resonance = higher routing priority.
     *
     * This is the CACHE-RESONANCE ATTENTION mechanism.
     * GPUs have uniform shared memory β€” they can't do this.
     * CPU cache hierarchy IS the attention score function.
     */
    for (uint32_t i = 0; i < st->n_active; i++) {
        uint32_t cell_id = st->active_ids[i];
        const RPICell *cell = &model->cells[cell_id];

        /* Touch the first perm block of this cell and measure time */
        if (cell->perm_block_count == 0) {
            st->lat_score[i] = 0.0f;
            continue;
        }

        const RPIPermBlock *block = &model->perm_blocks[cell->perm_block_start];

        /* Timed probe: read the block data and measure latency */
        uint64_t t0 = rpi_tb_now();

        /* Force a real memory access (prevent optimization) */
        volatile uint8_t sink = 0;
        for (int j = 0; j < 8; j++) {
            sink ^= block->src_idx[j * 8];
        }
        (void)sink;

        uint64_t t1 = rpi_tb_now();
        uint64_t dt = t1 - t0;

        /* Convert to a score: faster access = higher score */
        /* Normalize by hardware latency profile */
        float ns_est = (float)dt * 1e9f / (float)hw->tb_freq;

        if (ns_est <= hw->lat_l1_ns * 2.0f) {
            st->lat_score[i] = 1.0f;       /* L1: strong resonance */
        } else if (ns_est <= hw->lat_l2_ns * 2.0f) {
            st->lat_score[i] = 0.6f;       /* L2: moderate */
        } else if (ns_est <= hw->lat_l3_ns * 2.0f) {
            st->lat_score[i] = 0.3f;       /* L3: weak */
        } else {
            st->lat_score[i] = 0.05f;      /* DRAM: minimal */
        }
    }
}

/* ── One Recurrence Round ───────────────────────────────── */

void rpi_round(const RPIModel *model, const RPIHWProfile *hw,
               RPIState *st) {
    /*
     * For each active cell:
     *   1. Run permutation blocks (zero-multiply forward pass)
     *   2. Follow routes to activate downstream cells
     *   3. Measure cache resonance
     */

    uint64_t _pt0 = prof_enabled() ? rpi_tb_now() : 0;

    /* Phase 1: Run permutations on all active cells */
    for (uint32_t i = 0; i < st->n_active; i++) {
        uint32_t cell_id = st->active_ids[i];
        const RPICell *cell = &model->cells[cell_id];

        /* Check phase mask */
        if (!((cell->phase_mask >> st->phase) & 1))
            continue;

        run_cell_perms(model, cell, st->active_lanes[i]);
    }

    if (prof_enabled()) { uint64_t t = rpi_tb_now(); g_prof_perm += t - _pt0; _pt0 = t; }

    /* Phase 2: Follow routes β€” activate downstream cells.
     *
     * RPI_OLD_ROUTE=1 selects the original buffered version (double-copies the
     * whole lane state into scratch buffers each round). The default routes
     * IN PLACE: existing cells are untouched, new cells are appended straight
     * into st->active_ids / st->active_lanes past the original count. Identical
     * behavior (same dedup over the growing set, same decayed lanes from the
     * source cell), minus four memcpys and a 16 KB stack buffer per round. */
    static int old_route = -1;
    if (old_route < 0) {
        const char *e = getenv("RPI_OLD_ROUTE");
        old_route = (e && e[0] && e[0] != '0') ? 1 : 0;
    }

    if (old_route) {
        uint32_t new_active[RPI_MAX_ACTIVE_CELLS];
        int16_t  new_lanes[RPI_MAX_ACTIVE_CELLS][RPI_LANES];
        uint32_t n_new;

        memcpy(new_active, st->active_ids, st->n_active * sizeof(uint32_t));
        memcpy(new_lanes, st->active_lanes, st->n_active * sizeof(new_lanes[0]));
        n_new = st->n_active;

        for (uint32_t i = 0; i < st->n_active; i++) {
            int32_t energy = rpi_lane_energy(st->active_lanes[i]);
            if (energy < 64 * 10) continue;

            const RPICell *cell = &model->cells[st->active_ids[i]];
            for (uint32_t r = 0; r < cell->route_count && r < RPI_MAX_ROUTES; r++) {
                uint32_t dst = model->routes[cell->route_start + r].dst_cell_id;
                int found = 0;
                for (uint32_t k = 0; k < n_new; k++)
                    if (new_active[k] == dst) { found = 1; break; }
                if (found || n_new >= model->hdr.max_active) continue;
                new_active[n_new] = dst;
                for (int j = 0; j < RPI_LANES; j++)
                    new_lanes[n_new][j] = st->active_lanes[i][j] >> 2;
                n_new++;
            }
        }
        st->n_active = n_new;
        memcpy(st->active_ids, new_active, n_new * sizeof(uint32_t));
        memcpy(st->active_lanes, new_lanes, n_new * sizeof(new_lanes[0]));
    } else {
        /* In-place, with an O(1) hash-set membership check instead of the old
         * O(n_active) linear "already active?" scan. That scan made routing
         * O(n_active^2 * routes) per round and dominated per-token time; the
         * hash set makes it O(n_active * routes). Open addressing, linear
         * probe; 0xFFFFFFFF marks empty. Sized well above max_active. */
        #define RPI_SEEN_SZ 512
        #define RPI_SEEN_MASK (RPI_SEEN_SZ - 1)
        uint32_t seen[RPI_SEEN_SZ];
        memset(seen, 0xFF, sizeof(seen));
        for (uint32_t k = 0; k < st->n_active; k++) {
            uint32_t id = st->active_ids[k];
            uint32_t h = (id * 2654435761u) & RPI_SEEN_MASK;
            while (seen[h] != 0xFFFFFFFFu && seen[h] != id) h = (h + 1) & RPI_SEEN_MASK;
            seen[h] = id;
        }

        uint32_t orig_active = st->n_active;
        for (uint32_t i = 0; i < orig_active; i++) {
            int32_t energy = rpi_lane_energy(st->active_lanes[i]);
            if (energy < 64 * 10) continue;

            const RPICell *cell = &model->cells[st->active_ids[i]];
            for (uint32_t r = 0; r < cell->route_count && r < RPI_MAX_ROUTES; r++) {
                uint32_t dst = model->routes[cell->route_start + r].dst_cell_id;
                uint32_t h = (dst * 2654435761u) & RPI_SEEN_MASK;
                while (seen[h] != 0xFFFFFFFFu && seen[h] != dst) h = (h + 1) & RPI_SEEN_MASK;
                if (seen[h] == dst || st->n_active >= model->hdr.max_active) continue;
                uint32_t slot = st->n_active;
                st->active_ids[slot] = dst;
                for (int j = 0; j < RPI_LANES; j++)
                    st->active_lanes[slot][j] = st->active_lanes[i][j] >> 2;
                st->n_active++;
                seen[h] = dst;  /* probe stopped at the empty slot for dst */
            }
        }
    }

    /* Opt-in correctness check (RPI_ROUTE_CHECK=1): the hash-set dedup is
     * correct iff the active set never contains a duplicate. Scan for dups. */
    {
        static int route_check = -1;
        if (route_check < 0) {
            const char *e = getenv("RPI_ROUTE_CHECK");
            route_check = (e && e[0] && e[0] != '0') ? 1 : 0;
        }
        if (route_check) {
            for (uint32_t a = 0; a < st->n_active; a++)
                for (uint32_t b = a + 1; b < st->n_active; b++)
                    if (st->active_ids[a] == st->active_ids[b]) {
                        static int rep = 0;
                        if (rep++ < 5)
                            fprintf(stderr, "[RPI] ROUTE_CHECK: duplicate active cell %u\n",
                                    st->active_ids[a]);
                    }
        }
    }

    if (prof_enabled()) { uint64_t t = rpi_tb_now(); g_prof_route += t - _pt0; _pt0 = t; }

    /* Phase 3: Probe cache resonance */
    rpi_probe_resonance(model, hw, st);

    if (prof_enabled()) g_prof_reson += rpi_tb_now() - _pt0;

    st->round++;
}

/* ── Token Emission (v2: multi-cell voting + diversity) ─── */

/* Repetition-penalty history lives in RPIState (per-stream, thread-safe). */

uint32_t rpi_emit_next(const RPIModel *model, const RPIHWProfile *hw,
                       RPIState *st) {
    (void)hw;

    /* RPI_DENSE_EMIT=1 forces the old full-vocab path for same-binary A/B. */
    static int dense_emit = -1;
    if (dense_emit < 0) {
        const char *e = getenv("RPI_DENSE_EMIT");
        dense_emit = (e && e[0] && e[0] != '0') ? 1 : 0;
    }

    /* Clear scores. Sparse (default): wipe only slots dirtied last step, an
     * O(touched) lazy clear that keeps the invariant "untouched reads 0" and
     * replaces the O(vocab) memset (was ~a third of per-token time at 32k
     * vocab). Dense: the old full-vocab memset. */
    if (dense_emit) {
        memset(st->tok_scores, 0, model->hdr.vocab_size * sizeof(int32_t));
    } else {
        for (uint32_t i = 0; i < st->n_touched; i++)
            st->tok_scores[st->touched[i]] = 0;
    }
    st->n_touched = 0;

    /* Collect emissions from ALL active cells (multi-cell voting) */
    for (uint32_t i = 0; i < st->n_active; i++) {
        uint32_t cell_id = st->active_ids[i];
        if (cell_id >= model->hdr.n_cells) continue;
        const RPICell *cell = &model->cells[cell_id];

        /* Lane energy as cell activation strength */
        int32_t energy = rpi_lane_energy(st->active_lanes[i]);

        /* Bank diversity bonus: cells from underrepresented banks score higher */
        int32_t bank_bonus = (cell->bank_id != (st->phase & 3)) ? 50 : 0;

        /* Resonance bonus from cache timing */
        int32_t lat_bonus = (int32_t)(st->lat_score[i] * 80.0f);

        for (uint32_t e = 0; e < cell->emit_count && e < RPI_MAX_EMITS; e++) {
            const RPIEmit *emit = &model->emits[cell->emit_start + e];

            /* Phase check */
            if (emit->phase_mask != 0x0F &&
                !((emit->phase_mask >> st->phase) & 1))
                continue;

            int32_t score = (int32_t)emit->rank_bias
                          + (energy >> 8)       /* cell activation (reduced weight) */
                          + lat_bonus
                          + bank_bonus;

            if (emit->token_id < model->hdr.vocab_size) {
                st->tok_scores[emit->token_id] += score;
                /* Record the dirtied slot so we can scan/clear it sparsely.
                 * Duplicates are fine: the score already accumulated in the
                 * slot, and the top-K scan below dedups. */
                if (st->n_touched < RPI_MAX_TOUCHED)
                    st->touched[st->n_touched++] = emit->token_id;
            }
        }
    }

    /* Repetition penalty: halve scores of recently emitted tokens */
    for (uint32_t r = 0; r < RPI_REP_WINDOW; r++) {
        uint32_t prev_tok = st->rep_history[r];
        if (prev_tok < model->hdr.vocab_size && st->tok_scores[prev_tok] > 0) {
            st->tok_scores[prev_tok] >>= 2;  /* quarter the score */
        }
    }

    /* Top-K sampling with hardware entropy (K=8).
     * Scan ONLY the touched candidates, not the whole vocab. The set of tokens
     * with score>0 is exactly the touched set (untouched read 0 by invariant),
     * so this yields the same top-K set as the old full-vocab scan. */
    uint32_t top_ids[8] = {0};
    int32_t  top_scores[8] = {0};

    if (dense_emit) {
        /* Old full-vocab scan (A/B baseline). */
        for (uint32_t t = 0; t < model->hdr.vocab_size; t++) {
            if (st->tok_scores[t] <= 0) continue;
            int min_idx = 0;
            for (int k = 1; k < 8; k++)
                if (top_scores[k] < top_scores[min_idx]) min_idx = k;
            if (st->tok_scores[t] > top_scores[min_idx]) {
                top_scores[min_idx] = st->tok_scores[t];
                top_ids[min_idx] = t;
            }
        }
    } else {
        for (uint32_t idx = 0; idx < st->n_touched; idx++) {
            uint32_t t = st->touched[idx];
            int32_t sc = st->tok_scores[t];
            if (sc <= 0) continue;

            /* Skip if already placed (touched[] may contain duplicates). */
            int dup = 0;
            for (int k = 0; k < 8; k++)
                if (top_scores[k] > 0 && top_ids[k] == t) { dup = 1; break; }
            if (dup) continue;

            int min_idx = 0;
            for (int k = 1; k < 8; k++)
                if (top_scores[k] < top_scores[min_idx]) min_idx = k;
            if (sc > top_scores[min_idx]) {
                top_scores[min_idx] = sc;
                top_ids[min_idx] = t;
            }
        }
    }

    /* Opt-in differential self-check: verify the sparse top-K SET matches the
     * old dense full-vocab scan. Resolved once; off by default. */
    static int emit_check = -1;
    if (emit_check < 0) {
        const char *e = getenv("RPI_EMIT_CHECK");
        emit_check = (e && e[0] && e[0] != '0') ? 1 : 0;
    }
    if (emit_check) {
        uint32_t d_ids[8] = {0}; int32_t d_scores[8] = {0};
        for (uint32_t t = 0; t < model->hdr.vocab_size; t++) {
            if (st->tok_scores[t] <= 0) continue;
            int mi = 0;
            for (int k = 1; k < 8; k++) if (d_scores[k] < d_scores[mi]) mi = k;
            if (st->tok_scores[t] > d_scores[mi]) { d_scores[mi] = st->tok_scores[t]; d_ids[mi] = t; }
        }
        /* Compare as multisets of (score) present with score>0: same top-K
         * scores => same candidate strength distribution. */
        int32_t ss[8], ds[8]; int ns = 0, nd = 0;
        for (int k = 0; k < 8; k++) { if (top_scores[k] > 0) ss[ns++] = top_scores[k];
                                      if (d_scores[k] > 0) ds[nd++] = d_scores[k]; }
        for (int a = 0; a < ns; a++) for (int b = a + 1; b < ns; b++) if (ss[b] > ss[a]) { int32_t t = ss[a]; ss[a] = ss[b]; ss[b] = t; }
        for (int a = 0; a < nd; a++) for (int b = a + 1; b < nd; b++) if (ds[b] > ds[a]) { int32_t t = ds[a]; ds[a] = ds[b]; ds[b] = t; }
        int mismatch = (ns != nd);
        for (int k = 0; !mismatch && k < ns; k++) if (ss[k] != ds[k]) mismatch = 1;
        if (mismatch) {
            static int reported = 0;
            if (reported++ < 5)
                fprintf(stderr, "[RPI] EMIT_CHECK MISMATCH: sparse topK=%d dense topK=%d\n", ns, nd);
        }
    }

    /* RPI_GREEDY=1: deterministic argmax over the top-K instead of entropy
     * sampling. This is the drafting mode for speculative decoding β€” a draft
     * should be the model's best guess, and deterministic drafts make
     * verifier acceptance measurable. */
    static int greedy = -1;
    if (greedy < 0) {
        const char *e = getenv("RPI_GREEDY");
        greedy = (e && e[0] && e[0] != '0') ? 1 : 0;
    }
    if (greedy) {
        int best = 0;
        for (int k = 1; k < 8; k++)
            if (top_scores[k] > top_scores[best]) best = k;
        uint32_t chosen_g = top_ids[best];
        st->rep_history[st->rep_pos % RPI_REP_WINDOW] = chosen_g;
        st->rep_pos++;
        return chosen_g;
    }

    /* Sample from top-K using hardware entropy */
    uint64_t tb = rpi_tb_now();
    int32_t total = 0;
    for (int k = 0; k < 8; k++) total += top_scores[k];

    uint32_t chosen = top_ids[0];
    if (total > 0) {
        int32_t threshold = (int32_t)((tb ^ (tb >> 17)) % (uint64_t)total);
        int32_t cumulative = 0;
        for (int k = 0; k < 8; k++) {
            cumulative += top_scores[k];
            if (cumulative >= threshold) {
                chosen = top_ids[k];
                break;
            }
        }
    }

    /* Update repetition history */
    st->rep_history[st->rep_pos % RPI_REP_WINDOW] = chosen;
    st->rep_pos++;

    return chosen;
}

/* ── Full Token Decode (v2: min rounds + cross-bank activation) ── */

uint32_t rpi_decode_token(const RPIModel *model, const RPIHWProfile *hw,
                          RPIState *st) {
    st->round = 0;
    st->sig_prev = 0;
    st->tb_round_start = rpi_tb_now();

    uint64_t budget = hw->tb_freq / 10;  /* 100ms max */
    uint64_t deadline = st->tb_round_start + budget;

    uint32_t stable = 0;
    uint32_t min_rounds = 3;  /* MINIMUM 3 rounds before convergence check */

    while (st->round < model->hdr.max_rounds &&
           rpi_tb_now() < deadline) {

        rpi_round(model, hw, st);

        /* Force cross-bank activation every round:
         * Pick a random cell from a different bank and activate it */
        if (st->n_active < model->hdr.max_active && model->hdr.n_cells > 0) {
            uint64_t tb_seed = rpi_tb_now();
            uint32_t target_bank = (st->phase + st->round + 1) & 3;
            uint32_t cells_per_bank = model->hdr.n_cells / model->hdr.n_banks;
            uint32_t cross_cell = target_bank * cells_per_bank
                                + (uint32_t)(tb_seed % cells_per_bank);

            if (cross_cell < model->hdr.n_cells) {
                /* Check not already active */
                int found = 0;
                for (uint32_t k = 0; k < st->n_active; k++) {
                    if (st->active_ids[k] == cross_cell) { found = 1; break; }
                }
                if (!found && st->n_active < RPI_MAX_ACTIVE_CELLS) {
                    uint32_t idx = st->n_active++;
                    st->active_ids[idx] = cross_cell;
                    /* Initialize with entropy-seeded lanes */
                    for (int j = 0; j < RPI_LANES; j++) {
                        st->active_lanes[idx][j] =
                            (int16_t)((tb_seed >> (j & 0x3F)) & 0x7F) - 64;
                    }
                }
            }
        }

        /* Only check convergence after minimum rounds */
        if (st->round >= min_rounds) {
            uint64_t sig = rpi_fnv1a(st->active_ids,
                                     st->n_active * sizeof(uint32_t));
            for (uint32_t i = 0; i < st->n_active; i++) {
                int32_t sum = 0;
                for (int j = 0; j < RPI_LANES; j++)
                    sum += st->active_lanes[i][j];
                sig ^= rpi_fnv1a(&sum, sizeof(sum));
            }

            if (sig == st->sig_prev) {
                stable++;
                if (stable >= 2) break;
            } else {
                stable = 0;
            }
            st->sig_prev = sig;
        }
    }

    /* Emit next token with diversity */
    uint64_t _et0 = prof_enabled() ? rpi_tb_now() : 0;
    uint32_t next = rpi_emit_next(model, hw, st);
    if (prof_enabled()) g_prof_emit += rpi_tb_now() - _et0;

    /* Activate seed cells for next token */
    rpi_activate_token(model, st, next);

    /* Advance phase (rotates through banks) */
    st->phase = (st->phase + 1) & 3;
    st->last_token = next;
    st->total_tokens++;
    st->tb_prev = rpi_tb_now();

    return next;
}

/* ── Generate Text ──────────────────────────────────────── */

/* ── Context-copy drafting (RPI_NGRAM=1) ─────────────────────
 * RPI as a matmul-free PROCEDURAL accelerator: instead of asking the cell
 * engine to invent the next token, find the longest suffix of the recent
 * tokens that already occurred earlier in [prompt + generated] and COPY its
 * continuation. This is prompt-lookup / n-gram speculative drafting β€” pure
 * table permutation, zero multiplies, no model quality required β€” and it is
 * the token class (copied spans, boilerplate, structure) where a draft can
 * actually agree with an LLM verifier. Semantics stay with the LLM; the
 * reordering-shaped tokens run here.
 *
 * hist(i) spans prompt then output. Longest match wins, ties go to the MOST
 * RECENT occurrence. Returns the continuation token, or UINT32_MAX if no
 * n-gram of length >= NGRAM_MIN matches. */
#define NGRAM_MAX 8
#define NGRAM_MIN 1   /* n=1 (most-recent unigram) rescues structural copies
                         like JSON keys after a novel value token; llama.cpp's
                         prompt-lookup allows it for the same reason */
static uint32_t ngram_copy_next(const uint32_t *prompt_ids, uint32_t prompt_len,
                                const uint32_t *output_ids, uint32_t n_out) {
    const uint32_t T = prompt_len + n_out;
    if (T < NGRAM_MIN + 1) return UINT32_MAX;

#define HIST(i) ((i) < prompt_len ? prompt_ids[(i)] : output_ids[(i) - prompt_len])

    uint32_t nmax = (T - 1 < NGRAM_MAX) ? T - 1 : NGRAM_MAX;
    for (uint32_t n = nmax; n >= NGRAM_MIN; n--) {
        /* suffix = HIST(T-n .. T-1); scan for its most recent earlier copy */
        for (uint32_t p = T - n; p-- > 0; ) {   /* p = candidate match start */
            uint32_t ok = 1;
            for (uint32_t j = 0; j < n; j++) {
                if (HIST(p + j) != HIST(T - n + j)) { ok = 0; break; }
            }
            if (ok && p + n < T)
                return HIST(p + n);             /* the copied continuation */
        }
    }
#undef HIST
    return UINT32_MAX;
}

void rpi_generate(const RPIModel *model, const RPIHWProfile *hw,
                  RPIState *st, const uint32_t *prompt_ids,
                  uint32_t prompt_len, uint32_t max_tokens,
                  uint32_t *output_ids, uint32_t *n_output) {
    if (prof_enabled()) g_prof_wall0 = rpi_tb_now();

    static int ngram_mode = -1;
    if (ngram_mode < 0) {
        const char *e = getenv("RPI_NGRAM");
        ngram_mode = (e && e[0] && e[0] != '0') ? 1 : 0;
    }

    /* Ingest prompt */
    for (uint32_t i = 0; i < prompt_len; i++) {
        rpi_activate_token(model, st, prompt_ids[i]);
        /* Quick round for prompt tokens (fewer rounds needed) */
        for (uint32_t r = 0; r < 2; r++) {
            rpi_round(model, hw, st);
        }
        st->phase = (st->phase + 1) & 3;
    }

    /* Generate */
    *n_output = 0;
    for (uint32_t i = 0; i < max_tokens; i++) {
        uint32_t tok;

        if (ngram_mode) {
            tok = ngram_copy_next(prompt_ids, prompt_len, output_ids, *n_output);
            if (tok != UINT32_MAX) {
                /* Copied continuation β€” DELIBERATELY lightweight. The contract
                 * of a copy is zero engine compute: no rounds, no emission
                 * scoring, no repetition history. Only seed-activation + phase
                 * advance, so a later fallback token still has context. A
                 * fallback after copies therefore runs on a state no pure-
                 * engine path produces; that hybrid is the design, not an
                 * accident. (A copied 0 propagates and stops generation below,
                 * same as an engine-emitted EOS.) */
                rpi_activate_token(model, st, tok);
                st->phase = (st->phase + 1) & 3;
                st->last_token = tok;
                st->total_tokens++;
            } else {
                /* No copyable span β€” this token is not procedural. In a
                 * dual-brain deployment this is the escalation point; standalone
                 * we fall back to the cell engine. */
                tok = rpi_decode_token(model, hw, st);
            }
        } else {
            tok = rpi_decode_token(model, hw, st);
        }

        output_ids[i] = tok;
        (*n_output)++;

        /* Stop on EOS (token 0 by convention) */
        if (tok == 0)
            break;
    }

    if (prof_enabled()) {
        uint64_t tot = g_prof_perm + g_prof_route + g_prof_reson + g_prof_emit;
        if (tot == 0) tot = 1;
        fprintf(stderr, "[RPI] profile: permute=%.1f%%  routing=%.1f%%  "
                "resonance=%.1f%%  vocab-scoring=%.1f%%\n",
                100.0 * g_prof_perm / tot,
                100.0 * g_prof_route / tot,
                100.0 * g_prof_reson / tot,
                100.0 * g_prof_emit / tot);
        /* Coverage: how much of REAL generation time the buckets account for.
         * Percentages above are shares of the instrumented buckets only; work
         * outside them (convergence sig hash, token activation, cross-bank
         * injection, loop overhead) shows up here as "uncounted". */
        uint64_t wall = rpi_tb_now() - g_prof_wall0;
        if (wall == 0) wall = 1;
        double ns_per_tick = (hw->tb_freq > 0) ? 1e9 / (double)hw->tb_freq : 0.0;
        uint32_t ntok = (*n_output > 0) ? *n_output : 1;
        fprintf(stderr, "[RPI] profile: buckets cover %.1f%% of wall "
                "(uncounted %.1f%%); per token: perm=%.0fns route=%.0fns "
                "reson=%.0fns emit=%.0fns uncounted=%.0fns wall=%.0fns\n",
                100.0 * tot / wall, 100.0 * (double)(wall - tot) / wall,
                g_prof_perm * ns_per_tick / ntok,
                g_prof_route * ns_per_tick / ntok,
                g_prof_reson * ns_per_tick / ntok,
                g_prof_emit * ns_per_tick / ntok,
                (double)(wall - tot) * ns_per_tick / ntok,
                (double)wall * ns_per_tick / ntok);
    }
}