Spaces:
Sleeping
Sleeping
File size: 47,202 Bytes
28a1a01 | 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 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 | # Development Snapshot Validation
Date: 2026-09-08. This is not beta certification or a llama.cpp comparison.
## Environment
- Windows x64, AMD Ryzen 5 5600, 6 physical cores / 12 logical processors.
- CPython 3.13, MSVC Visual Studio Build Tools 2022 17.14.7.
- One native inference thread, runtime-dispatched AVX2.
- Installed wheel: cism 0.1.0.dev0.
- NumPy 2.5.3, Transformers 5.16.1, PyTorch 2.14.0+cpu for reference tests only.
- FastAPI 0.141.1, Uvicorn 0.52.4, OpenAI Python SDK 2.54.0.
## Tests
`python -m pytest -q`: **555 passed**, two upstream TestClient deprecation
warnings. Includes independent NumPy numerical references, Transformers tiny
model parity, packed quantization, invalid checkpoint/config handling, generation
metadata, cancellation, queue limits, SSE, official SDK, and a live Uvicorn TCP
roundtrip using real native inference.
`python -m pip check`: no broken requirements.
Both scalar-only and AVX2 native builds were also tested separately during kernel
development. Linux, sanitizers, long-running load tests, and full-context real
checkpoint certification remain outstanding.
## Real Checkpoints
Compared default FP32 eager HF computation with native last-token logits and
four-token greedy continuations at prompt lengths 4, 8, and 16. All tested
continuations agreed; maximum absolute logit error was below 0.0001.
- SupraLabs/Supra-Mini-v5-8M: FP32, INT8, hybrid INT4.
- veyra-ai/Veyra2-Blueberry-10M-Base: FP32.
INT8/INT4 references used reconstructed quantized weights, not original FP32
weights. These results establish implementation parity only, not a PPL quality
budget or task-quality equivalence.
## Local Throughput
```text
cism bench SupraLabs/Supra-Mini-v5-8M --precision int8 --runs 5 --max-tokens 128
```
Model revision: `bb98e3566a5ab3f24be16ee8db7f06f0cb884eb5`.
Packed weight storage including scales/norms: 8,022,784 bytes.
- Prompt: 6 tokens; generation: 128 tokens; EOS ignored for fixed-length timing.
- One warmup, then five runs; native generation through coarse Python bindings.
- Median decode throughput excluding prefill/first token: **1,622.86 tokens/s**.
- Runs: 1,595.72 / 1,632.20 / 1,624.86 / 1,603.32 / 1,622.86 tokens/s.
- Median prefill plus first token: **3.744 ms**.
No tokenization, detokenization, HTTP, or queue latency is included in decode
timing. These are growing-context token-generation results, not weight sweeps.
CPU affinity and clocks were not locked. No comparison with a tuned llama.cpp
baseline has been run; the required speed advantage is not established.
## Why These Numbers Differ From The Original Experiments
The earlier session results (hundreds of thousands of tok/s) are not comparable
to these, for verifiable reasons on both sides:
- The supplied C harness (`decode_int8`/`decode_hybrid`) looped over flat weight
buffers with no layers, no logits, no sampling, and no token feedback. Its
"tok/s" measured buffer-processing rate, not decoding.
- Its Hugging Face baseline ran `use_cache=False`, which undercounts real HF
decode speed; its DDR4 column in the showcase table was computed from
`34500 / model_MB`, not measured.
- Kernel improvements have raised this snapshot's decode rate several-fold from
the first working build, and it remains compute-bound (see the probe results
above), so neither the old synthetic numbers nor the current 1,974 tok/s
represent a final ceiling.
## Kernel Optimization History
Baseline (per-row indirect dot calls, no FMA, serial INT4 block reduces),
Supra-Mini-8M, one thread, 128-token decode: FP32 757, INT8 1,623,
hybrid INT4 1,152 tokens/s.
After the first kernel pass (explicit FMA with 4 independent accumulator
chains, AVX2 matvec kernels that absorb the row loop so one native call serves
a whole matrix, FMA required in the CPU dispatch check):
| Precision | Kernel-only rate | Full-model decode | Before | Gain |
| --- | --- | --- | --- | --- |
| FP32 | 30.8 GB/s | 936 tok/s (31.5 MB weights) | 757 | +24% |
| INT8 | 24.2 GB/s | 1,974 tok/s (8.0 MB weights) | 1,623 | +22% |
| Hybrid INT4 | 4.8 GB/s per byte | 1,382 tok/s (6.6 MB weights) | 1,152 | +20% |
| SmolLM2-135M INT8 | - | 127 tok/s (135.4 MB weights) | 120 | +6% |
Cache-residency probe (unchanged conclusion): decode effective bandwidth
(15.8 GB/s at INT8) remains below even the cold DRAM scan rate (25.1 GB/s),
so decode is still compute-bound. The INT4 kernel is the current kernel
bottleneck (nibble unpacking without VNNI); full-model non-dot work (attention,
norms, 16k-logit sampling, epilogue) accounts for roughly a third of decode
time at INT8.
Next levers, by expected impact: multithreaded matvec (row-parallel),
prompt-prefill matrix-matrix batching, and further INT4 unpack improvements.
### Activation-quantized kernel follow-up (2026-09-12)
The experimental `act_precision="int8"` path is now implemented for quantized
matrix products. It quantizes every activation row in 32-element blocks using
`32767 / absmax`, uses int16 `pmaddwd` MACs, and retains FP32 block scales for
the epilogue. It is numerically sound but is not enabled by default: on this
Ryzen 5 5600 (Zen 3, AVX2/FMA, no VNNI), the quantization work dominates small
layer GEMMs. Supra-Mini-8M, one thread, measured about 1,753 tok/s with INT8
weights and FP32 activations versus 603 tok/s with the experimental path;
hybrid-INT4 measured 1,297 versus 607 tok/s, and hybrid-FP4 1,238 versus 574.
Custom PPL comparisons put the activation-mode delta near +0.01 on Supra-Mini
and at or below +0.1 on SmolLM2-135M. Keep it as an explicit experimental API
until a fused/coarser quantizer or a CPU with suitable integer-dot instructions
shows a net benefit.
### Unpack-tax reduction, phase 1 (2026-09-12)
Shipped: skip the nibble interleave (even/odd streams dot a permuted
activation scratch built once per call from plain local buffers, no TLS),
halving shuffle-port work per 32 weights; dead `matvec_*` family deleted
(~350 lines, verified uncalled); 5-arg scalar adapters keep non-AVX2 builds
working. Tried and reverted the same session: folding per-8 scale multiplies
into a per-block scalar epilogue β the serial per-block reduce loses to four
parallel mulps on Zen 3 out-of-order execution (measured slower, not faster).
Measured on Ryzen 5 5600: full suite green, Mini PPL identical to 3 decimals,
production AutoBench once showed fp4-2T beating int4-2T 1254 to 1015 (+24%)
then losing the rematch β direction flips are inside this box's Β±15-20%
multithread noise, so neither is claimed. Drift-controlled A/B (alternating
int8/fp4 in one process): 0.87 vs 0.90 pre-change. Verdict: the remaining
~26 Β΅ops/32 (converts, scale muls, act loads) are structural on AVX2 without
VNNI; 4-bit cannot beat int8 kernel efficiency on this chip. Realistic next
levers: int8-container upcast at load (int4 quality, near-int8 speed, int8
size β wins wherever cache residency makes bytes free), the q8 integer-MAC
track, stronger ISA (VNNI/AMX/tensor cores), or DRAM-bound regimes where the
2x byte saving dominates.
Fused epilogues tried and reverted (2026-09-12): accumulating o/down dots
directly onto x_ and SiLU into the up-gemm epilogue measured ~7% SLOWER on
Supra2-Medium int8-1T in a back-to-back stash A/B (580 vs 623, tight
clusters, wrong order for a thermal explanation). Likely cause: branches plus
a temp in the FMA loop beat clean separate vector passes on wide rows, and an
unconditional permute scratch taxed int8/fp32 paths (since made lazy).
Lesson: on Zen 3 streaming hardware, an extra cache-resident vector pass can
be cheaper than a branch in the hot loop β fuse only with A/B proof.
Session scratch arena (2026-09-12): per-call permute/row mallocs replaced by
Session-persistent grow-only buffers (caller-owned, pool-safe; q8 TLS left
alone). PPL identical; 4-bit/int8 tok/s ratios up ~0.1 at 2T on both models
(Medium int4 0.83 -> 0.98, fp4 0.83 -> 0.96) β consistent with removing
malloc-lock contention across pool threads rather than raw bytes. Model now
reports info["compiled"] = {key, hit: False, mode: "generic", canonical} as
the Phase-0 key plumbing goes live (execution still generic).
Policy: AutoBench now defaults to 512 decode tokens and compiles nothing
unless asked (inductor warmup taxed every default run); thread guidance is 2T
on a loaded server, never 12T (oversubscription convoy collapses pooled
paths while serial paths sail through).
### Supra2-Medium-Base production run (2026-09-12, live server, 512 tokens)
First model that spills past L3 (fp32 96.78 MB, DRAM-streaming):
| Precision | tok/s (2T) | PPL | vs fp32 |
| --- | --- | --- | --- |
| int8 (7.65 MB L3) | 540 | 59.40 | +0.62% |
| hybrid-fp4 (20.32 MB L3) | 533 | 60.28 | +2.12% |
| hybrid-int4 (20.89 MB L3) | 475 | 62.61 | +6.07% |
| fp32 (96.78 MB DRAM) | 278 | 59.03 | β |
| HF eager fp32 | 93 | 59.03 | β |
| HF dynamic int8 | 72 | 95.45 | +61.7% |
hybrid-fp4 beats hybrid-int4 on speed (+12%) and quality (+2.1% vs +6.1%)
where matrices are large enough to amortize the unpack. int8 still leads
overall at near-zero quality cost. Speculation accepts at 96-99% and is the
largest single lever here (fp32 +65% at spec 8, int4 +17%); HF dynamic int8
is outclassed on both axes. Threads peak at 2-4T; 12T convulses as usual.
### Spec K curve (2026-09-12, Medium-Base int8, 2T, 64 tokens)
Cap raised 8 -> 16 to test whether near-100% acceptance favors longer
drafts. Measured: K=0: 844, K=2: 974 (84% acc), K=4: 1016 (76%),
K=8: 954 (71%), K=12: 915 (69%), K=16: 834 (71%). Peak at K=4:
acceptance decays with draft length while block-verify cost grows linearly,
so dilution loses past the peak. Optimal K is prompt-dependent (longer,
more repetitive generations accept more); the sweep now covers 0-16 with
per-thread accept% tables so each setup can read its own peak. Thread sweep
preset trimmed to 1,2,4 (above 4T never pays on these sizes).
### SmolLM2-135M precision x thread (2026-09-13, single-harness, act fp32, rev 93efa2f0)
Weight bytes (engine.info): int8 135.4 MB (4.2x L3), hybrid-int4 105.1 MB,
hybrid-fp4 100.2 MB; all spill, fits_l3=false. 512-token decode, greedy,
spec_k=0, one live engine at a time:
| Threads | int8 tok/s (GB/s) | hybrid-int4 tok/s (GB/s) | hybrid-fp4 tok/s (GB/s) |
| --- | --- | --- | --- |
| 1T | 88 (11.9) | 72 (7.5, 0.82x) | 73 (7.3, 0.83x) |
| 2T | 120 (16.2) | 107 (11.3, 0.89x) | 106 (10.6, 0.88x) |
| 4T | 124 (16.8) | 122 (12.8, 0.98x) | 122 (12.2, 0.99x) |
PPL (514 scored tokens, window 128): int8 34.74, hybrid-int4 42.07 (+21.1%
vs int8), hybrid-fp4 41.32 (+18.9% vs int8; fp4 beats int4 on quality at
fewer bytes, speed tied within noise). Central 64-token 1T check: int8 120.9,
int4 90.4, fp4 89.0 tok/s with identical weight bytes β same ordering.
128-token bracket int8-1T 114.5 tok/s / 15.5 GB/s matches the old 120-127
table; 512-window context growth costs ~21%, so ratios are intra-run only.
Verdict: int8 is bandwidth-bound (saturates at 2T, +3.6% to 4T), 4-bit paths
are kernel-bound at every thread count (1T->4T ~1.7x scaling, DRAM idle,
never cash the 1.29x byte edge). Kernel bet unchanged on Zen 3 / no-VNNI:
int8 default everywhere, fp4 the 4-bit pick. 360M not fetched (724 MB BF16
shard); expect the amplified same pattern. Next levers: spec on DRAM-bound
int8-135M, int8-container upcast at load for cache-resident sizes (test-gated),
VNNI-class ISA. Compile Phase-0 stays hit:False/mode:generic (plumbing only,
execution generic) β correct until a PPL-gated execution change ships.
## Threading Results
Persistent native pool, row-sliced matvecs, pause-spin barriers (yield/sleep
only after long idle). Supra-Mini-8M INT8, 256-token decode, medians:
| Threads | Tokens/s | vs 1T |
| --- | ---: | ---: |
| 1 | 1,548 | 1.00x |
| 2 | 2,148 | 1.39x |
| 4 | 2,036 | 1.31x (regresses) |
FP32 (31.5 MB, DRAM-bound) shows no thread scaling: 998 -> 1,004 tok/s at 4T.
The Ryzen 5600 sweet spot is 2 threads for cache-sized quantized models;
beyond that, shared L3/LSU bandwidth saturates. gVisor-class sandboxes showed
~19 us sync round trips, so threading there may need larger models to pay off.
`cism bench/serve --threads N` sets the pool size; default remains 1.
## Speculative Decoding (Prompt Lookup, Greedy)
Implemented: n-gram drafter over full token history (up to 4-gram tails),
block verification via per-matrix GEMM (all T candidates share one weight
stream), greedy argmax acceptance with KV rollback, budget/EOS boundaries.
Speculative greedy output is bitwise identical to plain greedy decode (tested
across architectures, precisions, rejection paths, and budgets).
SmolLM2-135M (135 MB INT8 / 540 MB FP32 - exceeds the 32 MB L3), one thread,
256 tokens, repetitive prompt for high draft acceptance:
| Configuration | Tokens/s | Gain |
| --- | ---: | ---: |
| FP32 plain | 51 | 1.00x |
| FP32 spec_k=4 | 83 | 1.63x |
| INT8 plain | 106 | 1.00x |
| INT8 spec_k=4 | 130 | 1.23x |
Reason: speculation amortizes weight-bandwidth (one stream per verify block),
so gains appear when decoding is DRAM-bound - the FP32 regime. The INT8 kernel
is compute-bound (conversion-heavy SIMD), so amortizing bandwidth buys little
until integer kernels land. On the AVX-512 VNNI cloud target, integer kernels
should make INT8 bandwidth-bound and transfer the full speculation multiplier.
Two structural costs: the bonus token costs one extra single-token pass (ideal
(K+1)/2, measured 1.6x at K=4), and low-acceptance text can make speculation
slightly negative - disable it when drafts do not match the domain.
## Memory Control Verification
Real-model page locking and touch on this machine (Supra-Mini-8M INT8):
- `lock_pages()`: 8,022,880 of 8,022,784 weight bytes locked (page rounding).
- `touch()`: 8,022,880 bytes swept.
- Generation while keep-warm active and pages locked: normal.
- `unlock_pages()`: 6,268,768 bytes reported. Smaller than the lock count
because adjacent weight regions share 4 KiB pages; a page released by one
region cannot be released again by its neighbor. Counts are approximate byte
sums of successful OS calls, not exact page accounting.
x86 cannot pin cache lines; locking prevents OS paging, and sweeping only
refreshes residency while running. Suite at time of writing: 571 tests passed.
## Cache-Residency Probe Results
`cism cache-report` measures a warm full-weight scan, a cold scan after
evicting caches with a 96 MiB scrub buffer, and decode effective bandwidth
(weight bytes x decode tok/s). AMD Ryzen 5 5600, INT8, one thread:
| Model | Weights | Warm scan | Cold scan | Decode | Effective | Verdict |
| --- | --- | --- | --- | --- | --- | --- |
| Supra-Mini-8M | 8.0 MB | 28.5 GB/s | 25.1 GB/s | 1,646 tok/s | 13.2 GB/s | Compute-bound |
| SmolLM2-135M | 135.4 MB | 21.4 GB/s | 23.1 GB/s | 120 tok/s | 16.3 GB/s | Compute-bound |
Interpretation:
- Decode bandwidth is well below even the cold (post-eviction) scan rate in
both cases. The decoder does not saturate DRAM, so **cache residency is not
the current bottleneck; kernel efficiency is** (scalar conversion-heavy
quantized kernels, sequential prefill, single thread).
- The warm/cold scan ratio is small for these linear probes; hardware
prefetchers hide much of DRAM latency on sequential streams, so this probe
bounds bandwidth rather than proving residency. Definitive per-event counts
(LLC miss) require hardware PMU tooling such as WindowsPerf or AMD uProf.
- The earlier session's implied "SRAM 3x-6.5x multiplier" was not observed by
this probe on this hardware and remains unproven.
Speed work priorities implied by this data: quantized kernel quality, prefill
matrix-matrix, and multithreading before further cache-tuning.
## Measured Comparison Against PyTorch 2.14 And torch.compile
Supra-Mini-v5-8M, revision bb98e356, FP32 checkpoint, greedy, one torch/CISM
thread, 6-token prompt, 256-token fixed-length decode (EOS ignored both sides),
warmup absorbed, Dynamo counters verified zero recompilations during
measurement. Torch baselines use a KV cache (never use_cache=False).
Script: `scripts/bench_vs_torch.py`; CISM: `cism bench --max-tokens 256`.
| Decoder | Tokens/s | CISM FP32 (998.5) | CISM INT8 (1,716.4) |
| --- | ---: | ---: | ---: |
| HF generate(use_cache=True) eager | 158.9 | 6.28x | 10.80x |
| Manual eager loop + StaticCache | 195.8 | 5.10x | 8.77x |
| torch.compile default, dynamic=False | 260.1 | 3.84x | 6.60x |
| torch.compile default, dynamic=True | 224.9 | 4.44x | 7.63x |
The fair comparison is against torch.compile dynamic=False, its best
configuration here: **3.84x at matched FP32 precision, 6.60x for CISM INT8.**
Reasons, with evidence:
1. **Per-token dispatch.** torch.compile lowers eager's per-op Python/ATen
dispatch (159 -> 260 tok/s, +64%) but still launches dozens of generated
kernels through a Python-level decode loop each token. CISM executes the
whole token in one C++ call. Eager's effective bandwidth is
31.5 MB x 159 = 5.0 GB/s and compile's is 8.2 GB/s - both overhead-bound,
while CISM FP32 runs memory-bound at ~30 GB/s kernel rate.
2. **Memory traffic.** CISM INT8 reads 8.0 MB/token versus 31.5 MB for the
FP32 checkpoint, cutting the memory-bound floor ~4x on top of the
dispatch elimination.
3. **Quality.** CISM FP32 matches eager within 1e-5 max logit error
(established above). INT8 quality reporting remains a beta gate.
Caveats: compile mode is "default" (max-autotune not used); one compile
configuration initially crashed with an Inductor codegen bug on this model
(index-out-of-bounds in a generated kernel) and only ran after cache-reset
handling; multithreaded torch was not measured; prefill/TTFT is excluded from
decode rates. This is one cache-sized model on one machine - not yet the
agreed multi-model llama.cpp gate.
## 4-bit quantization scheme study (2026-09-12)
NumPy simulation mirroring the native hybrid-int4 quantizer (round-half-away,
blocks of 32 along the row, per-block scale; validated against the native
engine to 8 decimal places on all checkpoints). PPL over the fixed AutoBench
corpus (514 tokens, window 128), MLP matrices 4-bit, all other 2D matrices
per-row INT8, vectors FP32.
Machine: Ryzen 5 5600, local checkpoints, scripts/quant4_study.py.
| scheme (MLP element + scale) | bits/w | 8M ΞPPL | 100M ΞPPL | 135M ΞPPL |
|-------------------------------------|-------:|--------:|----------:|----------:|
| int4 + FP32 b32 (hybrid-int4) | 5.00 | +15.4% | +6.1% | +23.2% |
| int4 + E4M3 b32 | 4.25 | +15.5% | +6.0% | +26.7% |
| int4 + E8M0 b32 | 4.25 | +308% | +80.8% | +537% |
| E2M1 + E8M0 b32 (OCP MXFP4) | 4.25 | +318% | +92.4% | +323% |
| E2M1 + E4M3 b32 | 4.25 | +18.7% | +3.5% | +23.3% |
| E2M1 + E4M3 b16 (NVFP4 granularity) | 4.50 | +15.0% | +2.1% | +21.0% |
| int4 + E4M3 b16 | 4.50 | n/a | +8.1% | n/a |
| E2M1 + FP32 b32 | 5.00 | +13.7% | +4.4% | +19.9% |
| NF4 + FP32 b32 (QLoRA) | 5.00 | +44.1% | +8.8% | +32.0% |
| int4 + error feedback b32 | 5.00 | +36.5% | +16.9% | +80.9% |
Findings:
- Scale precision dominates element format; power-of-two (E8M0) scales are
catastrophic for both uniform and FP elements.
- E2M1 elements beat uniform INT4 at equal scale precision and block size
(100M model, b16+e4m3: +2.1% vs +8.1%).
- E2M1 + E4M3 b16 is the only scheme that improves on hybrid-int4 at every
model size while using fewer bits (4.50 vs 5.00): tied on 8M, 3x better on
100M, better on 135M.
- NF4 (QLoRA) loses badly on these trained small models; naive per-block
error feedback with a fixed scale inflates residuals and hurts everywhere.
Why hybrid-int4 decodes slower than int8 (measured, 1 thread, same
checkpoints): the nibble unpack is a ~10-instruction dependent chain per
block, so hybrid-int4 delivers only 7.6-8.1 GB/s of weight-equivalents vs
12.5-14.0 for int8 and 26.6 for fp32, despite moving 2.5x fewer bytes than
fp32. The E2M1 LUT kernel replaces that chain with a 16-entry table lookup
and is the reason to build the mode.
### Compile one-shot canonical parity (2026-09-14)
Phase-0 key plumbing is now cross-language exact. Python
`compile_cache.canonical_model_config()` builds the native 12-field pipe
`model_type|hidden|intermediate|layers|heads|kv_heads|head_dim|vocab|
context|hexfloat(eps)|hexfloat(rope_theta)|tied` with FP32-round-trip
`struct.pack('f')` hexfloat, matching `Model::refresh_compile_key`
bit-for-bit. Verified: tiny Llama 16/32/1/4/2/4/32/32 probe Python
canonical equals `Model.info["compiled"]["canonical"]`, and
`native.compile_key(python_canonical, precision, act, threads)` equals
`Model.info["compiled"]["key"]` (`d4547092c73a7aac` on the probe).
Legacy `canonical_config()` sorted-JSON remains for early tests only.
Kept as a stable model fingerprint (zero runtime cost, tested); the
packed-artifact load idea is dropped with the project (see below).
### SmolLM2-135M full AutoBench matrix (2026-09-13, 512 tokens, act fp32)
Local file `autobench_135M_512.json` (untracked measurement, torch
baselines disabled by policy `include_compile=False`): 4 precisions x
threads (1,2,4) x spec_k (0,2,4,8,12,16) = 72 decode cells, PPL once per
precision (514 scored tokens, window 128). Revision `93efa2f0` (report
does not yet stamp `revision`; next run should copy
`engine.info["revision"]`). All `fits_l3=false` (L3 cache size 32.0 MB).
Weight storage in megabytes: int8 129.16 MB (135,439,104 bytes),
hybrid-int4 100.27 MB (105,141,504 bytes), hybrid-fp4 95.52 MB
(100,164,864 bytes), fp32 513.13 MB.
Decode rate in tokens/s at spec_k=0 (effective bandwidth in GB/s =
weight bytes x tok/s):
| Precision | 1 thread tok/s (GB/s) | 2 threads tok/s (GB/s) | 4 threads tok/s (GB/s) | Best tok/s (threads, spec_k) | PPL (+vs int8) |
| --- | --- | --- | --- | --- | --- |
| fp32 | 43.78 (23.6) | 57.44 (30.9) | 55.34 (29.8) | 106.41 (4T, K=8) | 34.15 |
| int8 | 91.53 (12.4) | 124.82 (16.9) | 130.03 (17.6) | 147.19 (4T, K=2) | 34.74 (--) |
| hybrid-int4 | 71.93 (7.6) | 106.54 (11.2) | 115.69 (12.2) | 140.59 (4T, K=16) | 42.07 (+21.1%) |
| hybrid-fp4 | 71.85 (7.2) | 102.10 (10.2) | 117.51 (11.8) | 132.90 (4T, K=2) | 41.32 (+18.9%) |
Ratios int8 vs 4-bit at spec 0: 1T 1.27x, 2T 1.17-1.22x, 4T
1.11-1.12x. Scaling 1T to 4T: int8 1.42x but 2T to 4T only +4.2%
(saturates, bandwidth-bound); int4 1.61x, fp4 1.64x, still climbing
with DRAM idle (kernel-bound nibble unpack, no VNNI). Verdict unchanged:
int8 default everywhere, fp4 the 4-bit pick (better quality at fewer
bytes, speed tied within Β±15-20% multithread noise; ratios
single-harness only).
### Spec K-curve on 135M DRAM-bound int8 (512 tokens, greedy)
Accept rate in percent is deterministic across threads per K
(88.0% at K=2 decaying to 72.4% at K=16).
| Threads | K=0 tok/s | K=2 tok/s | K=4 tok/s | K=8 tok/s | K=12 tok/s | K=16 tok/s | Peak gain vs K=0 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1T | 91.53 | 98.42 | 100.47 | 99.45 | 96.14 | 93.67 | K=4, +9.8% |
| 2T | 124.82 | 136.05 | 132.62 | 131.34 | 129.72 | 126.94 | K=2, +9.0% |
| 4T | 130.03 | 147.19 | 146.55 | 135.13 | 136.81 | 143.53 | K=2, +13.2% |
Global best: int8 147.19 tok/s at 4 threads, spec_k=2. Contrast fp32
+73-92% peaking K=8 (bandwidth-bound, block verify amortizes DRAM) vs
4-bit +5-21% erratic (kernel-bound). Peak at small K because acceptance
decays with draft length while GEMM verify cost grows linearly, plus the
bonus-token extra pass. Prompt-lookup drafter (up to 4-gram, greedy-only,
bitwise identical) helps repetitive prompts only; it does not fix the
135M slow complaint (still ~147 tok/s vs ~1,600 tok/s cache-resident 8M).
Default 135M int8: 4 threads, spec_k=2 (2-thread servers also K=2).
### trust_remote_code custom-arch assessment (2026-09-14)
`trust_remote_code=True` authorizes tokenizer Python only, never custom
modeling operators; trusted code itself is not sandboxed. Enforcement:
loader `_ARCHITECTURES={"llama","qwen3"}`, `validate_config` rejects
non-llama/qwen3 `model_type` and any `auto_map` beyond `AutoTokenizer`
even with trust enabled, cross-repo refs rejected, `_expected_shapes`
plus `_load_weights` reject extra/missing tensors, native
`Config::validate`/`parse_config` reject bias/MoE/sliding-window/
non-silu/non-default-RoPE. Custom Negative/CMA/Ember2/Spark2A/Blaze
targets (ROADMAP gate 4: recurrence, routing, lane mixing, Engram state)
need new native Config fields, operators, KV layouts, quant definitions,
and PPL/parity gates per arch β large work, not a flag flip. Keep
explicit `UnsupportedModelError`; server stays opt-in
`--allow-remote-code-load` (403 otherwise).
### Compile project dropped (2026-09-14, verdict: correctly built, not worth it)
Target was one-time load cost for faster batch-1 CPU decode (generic
AVX2, PPL-gated). Verdict: the plumbing is correct and tested, but the
goal is unreachable on this CPU, so the project is dropped and its
speedup fiction removed:
- What was real: cross-language canonical parity (bit-exact, tested),
hoisted dispatch loops (same kernels/order, bitwise identical, kept as
plain code), manifest helpers (kept, unused by load path).
- What was fiction: manifest-hit as "compile hit" (bookkeeping, not a
faster layout), `compiled.hit/mode` in Engine info and tester cards
(removed), any decode tok/s attributed to compile (zero: mode was
generic everywhere; the +13.2% is spec K=2).
- Why not worth it: remaining ~26 Β΅ops/32 are structural on AVX2/no-VNNI;
int8 is bandwidth-bound (2T saturates), 4-bit kernel-bound; fresh 512
runs show hoisting moved 4-bit ~+23% but int8 K0 flat (-0.9%),
confounded by idle-machine effect β no clean decode win to claim;
inductor costs 107 s warmup for 32 tok/s against CISM's 190 (6-8x
lead from the pre-existing single-call C++ path + quant, not compile).
VNNI/VBMI or integer-MAC kernels would reopen the question; until
then the line is closed. Fingerprint key + hoisted loops stay; hit/UI/
future-speedup prose goes.
### Fresh 512-token 4T confirmation (SmolLM2-135M, all precisions, spec sweep)
Re-ran 24 cells (4 precisions x 4T x spec 0,2,4,8,12,16), 512 tokens,
127.3 s wall, torch off. Accept% deterministic and identical to the old
file; PPL/weights identical (fp32 34.15, int8 34.74, int4 42.07, fp4
41.32; 513.13/129.16/100.27/95.52 MB, all fits_l3=false). Absolute tok/s
ran +11-30% hotter than the old file (idle machine); cells >25% off are
marked SUSPECT β use this table, do not mix files.
| Precision | K=0 | K=2 | K=4 | K=8 | K=12 | K=16 | Best |
| --- | --- | --- | --- | --- | --- | --- | --- |
| fp32 | 58.77 | 72.89 | 101.92 | 125.09 | 122.38 | 121.18 | K=8 125.09 |
| int8 | 128.90 | 159.99 | 162.64 | 171.76 | 171.37 | 177.03 (SUSPECT vs old) | K=16 177.03 |
| hybrid-int4 | 142.63 | 155.69 | 151.97 | 159.86 | 160.29 | 156.40 | K=12 160.29 |
| hybrid-fp4 | 144.58 | 150.73 | 153.96 | 160.41 | 155.88 | 153.82 | K=8 160.41 |
Accept%: fp32 96.5/94.0/89.9/88.1/85.5; int8 88.0/83.4/77.1/73.0/72.4;
int4 90.1/84.4/79.1/75.6/75.3; fp4 84.1/76.6/70.3/66.7/67.6 (K=2..16).
Note 4-bit now leads int8 at K=0 (142.63/144.58 vs 128.90) in this
run β confounded by machine state (old file had int8 leading), so no
compile attribution claimed without a same-state A/B. fp4 vs int4: tied
within noise at every K (max gap 3.7%), fp4 keeps the quality edge
(+18.9% vs +21.1% PPL), so fp4 stays the 4-bit pick.
### 4T K=2 vs regular baselines (SmolLM2-135M int8, 512 tokens)
Global best cell is int8 4 threads spec_k=2 at 147.19 tok/s (accept
87.96%). Regular baselines from the same `autobench_135M_512.json` run
(`include_torch=False`, so no HF eager/compile in this file):
| Baseline (4T) | Tok/s | vs 4T K=2 (147.19) |
| --- | --- | --- |
| int8 K=0 (same weights, no spec) | 130.03 | +13.2% with spec |
| int8 2T K=0 | 124.82 | +17.9% (threads+spec) |
| int8 1T K=0 | 91.53 | +60.8% |
| fp32 K=0 | 55.34 | 2.66x |
| fp32 best (K=8) | 106.41 | 1.38x |
| hybrid-int4 K=0 | 115.69 | 1.27x |
| hybrid-int4 best (K=16) | 140.59 | 1.05x |
| hybrid-fp4 K=0 | 117.51 | 1.25x |
| hybrid-fp4 best (K=2) | 132.90 | 1.11x |
PPL in same run: int8 34.74, fp32 34.15, int4 42.07, fp4 41.32.
Spec pays (+13.2%) because 135M int8 spills (129.16 MB vs 32 MB L3):
block verify streams weights once per block via GEMM. It does not make
135M fast in absolute terms (still ~147 vs ~1,600 cache-resident 8M).
### HF eager/compile baselines (SmolLM2-135M, 512 tokens, 4T, same harness)
One `run_benchmark` (`src/cism/autobench.py:223`) with
`include_torch=True, include_compile=True`, `max_new_tokens=512`,
threads 4, spec sweep 0-16, rev `2c4d40c`. Whole sweep ran ~4-15% under
the fresh reference (mild thermal throttle); HF rows are baselines, not
throttle-sensitive conclusions.
| Decoder (512 tok, 4T) | Tok/s | PPL (514 scored tokens) |
| --- | --- | --- |
| CISM int8 K=0 | 131.88 | 34.74 (nll 3.5480, +1.73% vs eager) |
| CISM int8 best K=16 | 167.23 | same |
| CISM fp32 K=0 / best K=8 | 53.32 / 114.67 | 34.15 (+0.002%) |
| CISM hybrid-int4 K=0 / best K=8 | 137.20 / 153.07 | 42.07 (+23.18%) |
| CISM hybrid-fp4 K=0 / best K=16 | 137.38 / 150.25 | 41.32 (+21.00%) |
| HF eager fp32 | 23.79 (21.52 s) | 34.15 (nll 3.5308) |
| HF dynamic int8 | 19.74 (25.94 s) | 81.12 (broken, +137.5%) |
| HF torch.compile fp32 | 25.18 (20.33 s, warmup 147.3 s) | not scored |
| HF compile int8 | 16.86 (30.38 s) | not scored |
| torchao int4 | skipped (not installed) | -- |
Speedups (same harness): CISM int8 K=0 **5.54x eager / 5.24x compile**;
best K=16 **7.03x / 6.64x**; fp32 K=0 2.24x/2.12x, best K=8 4.82x/4.55x.
Compile barely beats eager (+5.9%) after a 147 s warmup; dynamic int8 is
slower than fp32 AND destroyed (PPL 81). Weights: 513.13/129.16/100.27/
95.52 MB, all fits_l3=false (L3 32 MB).
### Surjo hybrid native Phase1 (loader+fp32 decode)
Scope: SurjoLabs Surjo hybrid import plus native fp32 decode runs. No
Surjo tok/s, no Surjo PPL, no speedup claim in this section. Reference
dense numbers below are context only, single-harness, Β±15-20%
multithread noise (see 135M matrix notes); do not mix files or
attribute cross-run deltas to Surjo.
Config fingerprint (Surjo-50m, `tests/test_surjo.py:20`): `model_type`
surjo / `SurjoForCausalLM`, vocab 32768, hidden 512, intermediate
1536, layers 10, context 2048, prelude 1 / recurrent 8 / coda 1,
groups 2 / passes 2 / gdn_per_xsa 3, heads 8 / kv 4 / head_dim 64,
gdn_v_heads 8 / gdn_k_dim 64 / gdn_v_dim 64 / kernel 4,
`gdn_allow_neg_eigval` false, `xsa_projection` true, bias false,
dropout 0.0, `hidden_act` silu, eps 1e-5, theta 10000.0, default RoPE
(`partial_rotary_factor` 1.0), tied true, `layer_types`
`[F,L,L,L,F,L,L,L,F,F]`, `auto_map` pinned to
`configuration_surjo`/`modeling_surjo` (never executed). Base
checkpoints ship `use_cache=false`; `import_model`
(`src/cism/loader.py:666`) preserves the flag verbatim and it does
not disable native caching (XSA KV slots + GDN recurrent/conv state
always allocate).
Checkpoint header: 178xF32 tensors in `model.safetensors`, tied
`lm_head` omitted, no rotary `inv_freq` buffers (RoPE is computed in
`forward()`). `_expected_shapes_surjo` length is 179 including the
tied-head alias entry; spot-checked in
`test_surjo_50m_expected_shapes_match_header` (embed `(32768,512)`,
XSA `q (512,512)` / `k (256,512)` / `q_norm (64,)`, GDN `q/k (512,512)`
/ `v (512,512)` / conv `(512,1,4)` / `f0 (64,512)` / `f1 (512,64)` /
`A_log (8,)` / `dt_bias (512,)` / `g1.bias (512,)` / `o_norm (64,)`).
Loader (`src/cism/loader.py`): `validate_surjo_config`,
`surjo_exec_plan` (18 steps: 1 prelude + 2 passes x 2 groups x
(3 GDN + 1 XSA) + 1 coda; XSA slots `[0..5]`, 12 GDN `(layer,pass)`
steps), `surjo_num_xsa_slots` (=6), `_expected_shapes_surjo:393`,
`_load_weights` Surjo branch (no buffers, strict shape/dtype/finite
checks, tied-head alias by identity), `import_model` dispatch
(`model_type=="surjo"` -> Surjo path, else dense). `_SURJO_SUPPORT`
string still reads "next phase" in this snapshot; Phase1 below is
what landed natively.
Native (`native/runtime.{hpp,cpp}`, `native/bindings.cpp:468`,
`src/cism/engine.py:80` dispatch to `_native.SurjoModel`):
`SurjoModel`/`SurjoSession` fp32 decode. XSA: 6 slots, KV
`[slots,capacity,kv_width]` with `kv_width=256`; at max context 2048
that is 4 MiB/slot (K+V), 24 MiB total (session-sized by
`prompt+max_new`, so 24 MiB is the upper bound). GDN: 12 states
(6 GDN layers x 2 passes), each `S` is `H*K*V=8*64*64=32768` floats =
128 KiB (12x128 KiB = 1.5 MiB), plus depthwise-conv raw FIFO
`(kernel-1)=3`: q/k/v FIFOs ~72 KiB each, ~0.21 MiB total. MLP is
ClampedMLP (gate clamped to `[-15,15]` then `silu*up`,
`runtime.cpp:2010`). `spec_k` is rejected for Surjo
(`next_tokens` throws unless `spec_k==0`; `Engine.stream(spec_k=2)`
raises `ValueError`, covered by `test_surjo_engine_decode_runs`).
Prefill is sequential per-token (`forward_tokens` loop); `logits` /
`nll` teacher-forcing paths exist but are unscored against HF so far.
Quant mapping reuse, no new format: `protected_storage` (embed, head,
all XSA/GDN projections incl. `f0/f1/g0/g1/o_proj`) is fp32 or int8;
MLP `gate/up/down` follow the dense hybrid rule (hybrid-int4 -> int4,
hybrid-fp4 -> fp4). Same `Matrix` kernels/scratch/act_q8 path as
dense; only norms/vectors/conv/A_log/dt_bias stay fp32.
Parity gate: dense bar is max abs logit error <1e-4 vs HF eager.
Surjo fp32 decode/smoke runs (tiny shapes fixture, tied-head
identity, `Engine.logits/generate` finite, spec rejection). HF
`modeling_surjo.py` parity script and Surjo PPL are pending β no
parity number claimed here.
Batch/paging: Phase0 files unchanged. Single-request serialized
`Engine` generation lock, `create_session`/`next_tokens`,
cancel/finish, `lock_pages`/`touch`/`scan` weight-memory controls
are reused as-is. No continuous batching, no paged-KV layout, no new
server path for Surjo.
FLA CPU verdict: decode needs no Flash-Linear-Attention (single-token
GDN recurrence is O(1) state + per-slot causal XSA). Chunkwise/FLA
prefill is an optional long-prompt throughput optimization only, not
correctness; current sequential prefill stands.
Dense context (same `autobench_135M_512.json`, `include_torch=False`,
NOT Surjo results): 4T K=0 fp32 55.34 / int8 130.03 / hybrid-int4
115.69 / hybrid-fp4 117.51 tok/s; best cells fp32 106.41 (K=8), int8
147.19 (K=2, accept 87.96%), hybrid-int4 140.59 (K=16), hybrid-fp4
132.90 (K=2); PPL fp32 34.15 / int8 34.74 / int4 42.07 / fp4 41.32.
Ratios single-harness only; 4-bit vs int8 gaps sit inside Β±15-20%
noise. Surjo inherits none of these numbers.
### Surjo-50m full AutoBench (Phase2, 2026-09-15, 4T K0)
File `autobench_surjo50m_512.json` (local snapshot
`35c9fa6e`, 512-token HF window, CISM greedy K0 only, act fp32,
threads 1/2/4). Weights (engine.info): fp32 205.21 MB, int8
51.84 MB, hybrid-int4 43.27 MB, hybrid-fp4 41.86 MB; L3 32.0 MB,
all `fits_l3=false`. PPL over the fixed AutoBench corpus (527 scored
tokens): fp32 59.29 / int8 59.46 (+0.30% vs HF eager) / hybrid-int4
62.90 (+6.10%) / hybrid-fp4 61.80 (+4.25%). CISM int8/fp32 PPL matches
HF eager fp32 59.28 to 0.3% β quant quality gate holds at int8, 4-bit
pays +4-6%.
CISM decode at 4T K0 (greedy; CISM stopped on EOS at 43-48 tokens,
HF forced 512 via min_new_tokens β lengths differ, ratios indicative):
| Precision | 1T tok/s | 2T tok/s | 4T tok/s | PPL |
| --- | --- | --- | --- | --- |
| fp32 | 62.18 | 81.03 | 81.06 | 59.29 |
| int8 | 129.86 | 158.39 | 207.80 | 59.46 |
| hybrid-int4 | 109.05 | 159.04 | 175.49 | 62.90 |
| hybrid-fp4 | 105.29 | 153.13 | 171.31 | 61.80 |
HF baselines (same harness, FP32 CPU, same thread count):
| Decoder (4T) | Tok/s | PPL |
| --- | --- | --- |
| HF eager fp32 | 26.27 (1T 28.08 / 2T 27.74 β inverse scaling, MT noise) | 59.28 |
| HF dynamic int8 | 18.18 (main cell 19.93; sweep 17.42/18.95/18.18) | 66.35 destroyed (+11.9%) |
| HF torch.compile fp32 | ~~31.72~~ INVALID β StaticCache path, see below | not scored |
| HF compile int8 | 16.73 (same StaticCache invalidity) | not scored |
Speedups at 4T K0 vs HF eager 26.27: int8 7.91x, hybrid-int4 6.68x,
hybrid-fp4 6.52x, fp32 3.09x. `speedup_vs_compile` in the JSON divides
by the invalid ~~31.72~~ and is void β do not cite.
Compile correction: `modeling_surjo.py:645` discards non-SurjoCache
past (`if not isinstance(past, SurjoCache): past = None`), so the
autobench StaticCache loop allocated a fresh cache per forward call and
state never carried β ~~31.72 tok/s~~ is not a Surjo compile number.
`src/cism/autobench.py` now detects `surjo_mode` and threads returned
`past_key_values` (SurjoCache) under `torch.compile(dynamic=True)`,
recording `{"skipped": "StaticCache invalid for Surjo"}` if the
threaded path cannot run β never a StaticCache tok/s. Correct path
proven by `scripts/bench_compiler.py`: threaded SurjoCache +
`dynamic=True` ~41 tok/s (compile warmup 43-119 s across runs;
189.3 s in the old JSON was the invalid-path warmup);
`dynamic=False` unusable (~0.06 tok/s, recompiles per token as XSA KV
length grows). torchao int4 blocked on Windows (no mslk wheel β
`Requires mslk >= 1.0.0`); torchao int8wo is the CPU-working control
only. FLA CPU dead here (Triton needs GPU; `fla.ops.gdn2` chunk/fused
probe does not run on CPU) β decode needs no FLA anyway (single-token
GDN recurrence is O(1) state + per-slot causal XSA).
Spec: unsupported for Surjo hybrid (GDN state destructive) β K0 only
(`spec_note` in JSON); K2βK0 where probed, no speculation gain claimed.
Native `SurjoSession` rejects `spec_k!=0`; `Engine.stream(spec_k=2)`
raises.
Rigor: this JSON predates the harness (`matrix` rows lack
`rep_rates`/`thermal_throttled`/`cpu_snapshot`). Current
`src/cism/autobench.py:_timed` reports 1 warmup + median-of-`reps`
(`--reps`/`--runs` wired through `cism bench`; default 3) with min/max/
std, `thermal_throttled` when max-min spread exceeds 15% (wider than the
Β±15-20% MT noise band), and best-effort `cpu_snapshot` (clock/temp;
`{}` when unavailable, never fails the run). Next Surjo rerun picks up
medians + thermal flags automatically; do not mix old single-sample
cells with new median cells.
### Surjo-50m rerun on current code (2026-09-15 night, 72 cells, reps=3)
`autobench_surjo50m_512.json` overwritten with current native
(GDN-OPT + XSA-blocked + `forward_block` + spec verify). Threads
1/2/4 x spec 0/2/4/8/12/16, 512tok, act fp32, median-of-3, all
`thermal_throttled=false` except two fp32-4T spec cells. CISM EOS-stops
43-48tok vs HF forced-512 β same caveat as before.
K0 medians: fp32 60.32/84.73/88.86, int8 141.76/197.34/234.87,
int4 114.59/169.65/197.28, fp4 113.54/169.98/214.61. Best cells:
fp32 K2 92.66, int8 K4 236.91, int4 K4 201.33, fp4 K0 214.61.
Spec accept on bench prompt is 0.0 for fp32/int8 (no gain, no loss
beyond noise); int4 6.25%->1.7%, fp4 8.3%->2.7% decaying with K, and
spec K>0 is slower than K0 there (verify cost, no amortization win
on this prompt). PPL unchanged: 59.29/59.46/62.90/61.80.
HF equal-opportunity, same harness, threads swept, median-of-3:
eager 31.22/30.43/29.50 (4T 29.50 PPL 59.28),
dynamic-int8 20.27/22.59/22.37 (4T 22.69 PPL 66.35),
compile threaded-SurjoCache dynamic=True 38.77 warmup 100.6s,
compile-int8 21.10, torchao-int4 failed `Requires mslk>=1.0.0`.
Speedups vs eager 29.50: int8 K0 7.96x, best K4 8.03x;
vs compile 38.77: K0 6.06x. Old invalid StaticCache 31.72 is
superseded β do not cite it.
### Supra-50M-Base full AutoBench (dense Llama, 2026-09-15 night)
File `autobench_supra50M_512.json`, Hub rev
`521bfd3d3901fbeefa943a2f3d461a9d193c52ec`, dense Llama
(hidden 512, layers 12, heads 8/kv 4/head_dim 64, intermediate
1408, vocab 32000, ctx 1024, tied, `use_cache=false` preserved).
Threads 1/2/4 x spec 0/2/4/8/12/16 = 72 cells, 512tok, act
fp32, median-of-3. Weights: fp32 ~209MB / int8 ~53MB class
(all `fits_l3=false`). PPL (527 scored tokens): fp32 63.32 /
int8 63.79 (+0.7%) / int4 65.36 (+3.2%) / fp4 68.90 (+8.8%).
CISM K0 medians: fp32 108.29/131.54/123.55, int8
214.81/284.36/292.58, int4 178.04/247.49/250.87, fp4
171.67/234.54/251.38. Best cells: fp32 K8 268.19 (2T),
int8 K12 349.10 (4T), int4 K2 257.43, fp4 K8 274.25.
Accept on bench prompt is 100% for fp32/int8/fp4 at every K
(hence spec pays: int8 4T K0 292.58 -> K12 349.10, +19.3%;
fp32 K0 123.55 -> K8 268.19, +117%); int4 86%->70%.
HF same harness, threads swept, median-of-3: eager
48.59/47.90/43.61 (4T 43.61 PPL 63.32), dynamic-int8
37.70/40.49/37.78 (4T 34.91 PPL 101.68 destroyed),
compile StaticCache dynamic=False 55.23 warmup 143.0s,
compile-int8 33.14, torchao-int4 `Requires mslk>=1.0.0`.
Speedups vs eager 43.61: int8 K0 6.71x, best K12 8.00x;
vs compile 55.23: K0 5.30x, best 6.32x.
### FWKV Myosotis-1-base full support + AutoBench (2026-09-15 night)
Files inspected in snapshot `031fe84a34f667bc8bfff7dc3f8a77ef3c17efb2`:
`config.json` (model_type fwkv, d_model 768, d_emb 192,
n_layers 13, ffn_mult 4, vocab 50257, wkv_floor 0.1, tied),
`configuration_fwkv.py` (FWKVConfig + HF aliases),
`modeling_fwkv.py` (FactorizedTiedHead, FWKVBlock with
proj_k/v/r/out + W recurrence + GELU FFN + 2x LayerNorm,
`_supports_cache_class=False`, tuple-state threading),
`model.safetensors` (147 F32 tensors, no rotary buffers).
Native support landed, mirroring the Surjo vertical slice:
`loader.validate_fwkv_config` + `_expected_shapes_fwkv` (147
shapes) + `_load_weights` fwkv branch (also fixed an eager
`config["head_dim"]` default crash affecting any arch without
head_dim) + `import_model` dispatch; `native` FwkvConfig /
FwkvModel / FwkvSession (WKV recurrence, exact GELU,
LayerNorm eps 1e-5, factorized head, fp32/int8/int4/fp4 reuse
via Matrix, `forward_block` batched projections + sequential
recurrence, spec verify with ~40KB state checkpoint/restore);
`bindings` FwkvModel/FwkvSession; `engine` dispatch;
`tests/test_fwkv.py` 11 passed; `scripts/parity_fwkv.py`:
tiny 1.19e-07, live vs HF fp32 5.72e-06 (<1e-4 OK).
Autobench: `trust_remote_code=True` for fwkv HF loads,
threaded-FWKV-state compile path (StaticCache invalid, same
class of bug as Surjo), `_hf_context` fallback to `seq_len`
(FWKVConfig has no `max_position_embeddings`).
File `autobench_fwkv_512.json`, 72 cells, reps=3 (v4 drafter rerun,
2026-09-15 late night; supersedes the v1-drafter run below). Weights:
fp32 425.38MB / int8 107.64MB. PPL: fp32 156.68 (= HF eager
156.68, gate holds) / int8 156.80 (+0.07%) / int4 156.63
(-0.04%) / fp4 158.53 (+1.18%). High absolute PPL is base
model quality, faithfully reproduced.
CISM K0: fp32 64.65/93.73/87.24, int8 169.01/268.22/342.27,
int4 121.54/215.32/284.66, fp4 119.21/205.16/303.52. Best:
fp32 K16 177.34, int8 K4 382.74, int4 K2 290.19, fp4 K2
305.98. Accept (bench prompt): fp32 85%->61%, int8
85%->62%, int4 77%->45%, fp4 74%->44% (K2->K16). Spec pays
on fp32/int8: int8 4T K0 342.27 -> K4 382.74 (+11.8%);
fp32 K0 87.24 -> K16 177.34 (+103%); 4-bit int4 still
peaks near K0-K2, fp4 peaks K2.
HF same harness, threads swept, median-of-3: eager
42.72/45.59/43.42 (PPL 156.68), blocks-only dynamic-int8
34.74/43.88/51.26 (4T 51.22 PPL 158.42 +1.1% ok β fixed by
quantizing `blocks` Linears only; full-model quantize fails
on `FactorizedTiedHead.proj.weight.t()`), compile
threaded-FWKV-state dynamic=True 49.73 warmup 45.9s,
compile-int8 same path 46.93 warmup 36.2s, torchao-int4
`Requires mslk>=1.0.0` (no Windows wheel). Speedups vs
eager 43.42: int8 K0 7.88x, best K4 8.81x; vs compile
49.73: K0 6.88x, best 7.70x. Previous run (v1 drafter):
K0 59.82/83.70/87.54 etc., best int8 K8 359.97 β v4 gains
come from restored accept (19%->85% @K2).
### Spec drafter v4 + doom-loop check (2026-09-15 late night)
v1 (most-recent single match, verbatim tail truncated at history
end): Surjo bench 0% (correct text, nothing to match), Supra
bench 100% (degenerate loop flatters it). v2 (pure frequency
vote) collapsed on drifting text (FWKV bench int8 K2
85%->19%, K8 69%->9%). v3 (recency-decayed vote) same failure.
v4 (landed): most-recent strictly-inside occurrence per
position + iterative extension (fixes v1 truncation) +
rejected-block latch REMOVED (the 32-propose/<1/3-accept
latch fired during the short-history warmup and blinded the
session to later 85%-accept regions; rejected blocks stream
weights once per block via forward_block, so worst case on
0%-accept text is ~K0 speed). Shared by dense/Surjo/FWKV;
`tests/test_native.py::test_speculative_draft_hits_and_misses`
reimplemented against v4; suite 759 passed; K0==Kx exact
everywhere probed.
Doom-loop probe (greedy, 128 tok, fp32+int8, 3 prompts):
Surjo stops coherently on bench/code prompts (48/15 tok,
uniq 0.73/0.93, loop 0) but loops story prompts (128 tok,
uniq 0.09-0.16, 5/11-period loops); Supra loops ALL prompts
(128 tok, uniq 0.07-0.11, 7/9/12-period loops β bench,
story, AND code); FWKV never hard-loops bench/story
(uniq 0.28-0.54, loop 0; int8 bench drifts into a 10-period
body/brain cycle) but degenerates to 128 spaces on code
(uniq 0.02 both precisions). So Supra's 100% spec accept is
a looping artifact, Surjo's 0% is healthy text, FWKV sits
between. No repetition penalty exists anywhere (greedy
only); spec accept must always be read next to uniqueness.
### Portable kernels: Intel/AMD tiers + ARM64 phones (2026-09-16)
Tier ladder (runtime-dispatched, scalar fallback always works):
`scalar` (any CPU) < `avx2`+FMA (x86-64 baseline, all current
numbers) < `avx-vnni` (Zen4/Zen5, Alder Lake+: int8 VNNI fast
path) < `avx512-vnni` (server AVX512) / `neon` (ARM64 phones,
Apple Silicon, Raspberry Pi). New files: `native/kernels_vnni.cpp`
(dpbusd int8xint8, int8-activation ABI via `quantize_row_i8`),
`native/kernels_neon.cpp` (fp32/int8/int4/fp4 + uzp permute),
CPUID detection in `native/kernels.cpp` (AVX-VNNI = leaf7:1
EAX[4], AVX512-VNNI = leaf7:0 EBX[11] + foundation + ZMM state;
MSVC VNNI TU is EVEX so it dispatches only on AVX512-VNNI,
GCC VEX on AVX-VNNI), per-file codegen flags in CMakeLists
(dispatcher/runtime stay generic). `kernel_name()` keeps the
fp32 family; new `kernel_variant()` + `has_avx_vnni` /
`has_avx512_vnni` / `has_neon` bindings; `cpu_features` gains
`avx_vnni`; `Model.info["kernels"]` gains `variant`.
`tests/test_kernels_portable.py` (5 passed): tier reporting,
flag consistency, pre-VNNI fallback untouched, i8 quantizer
round-trip bounds, dpbusd bias-identity in numpy.
Proven on Ryzen 5 5600: builds clean (MSVC /arch:AVX512 VNNI TU
accepted), `kernel avx2 / variant avx2 / vnni False`,
suite 764 passed (759 + 5 new) β zero behavior change, as
designed (VNNI selector is null here so Matrix keeps the int16
Q8 / fp32-activation paths byte-for-byte). Pending hardware CI:
VNNI tok/s + int8-activation PPL re-gate on Zen4/ADL+ (expect
prefill/spec-verify wins ~2-3x integer MACs; decode stays
bandwidth-bound so batch-1 gains concentrate in K>0), NEON
tok/s + parity on ARM64 hardware (expects the usual
Silicon/Android numbers; tails fall back to scalar by
construction). No speedup is claimed for either until measured.
|