|
Download README.md from rkeyes/OldIron: direct link, hf CLI and curl.
- Browser
- Download file 18.8 kB
-
https://huggingface.co/rkeyes/OldIron/resolve/main/README.md
- Command line
-
hf download hf://rkeyes/OldIron/README.md
-
curl -L -o README.md https://huggingface.co/rkeyes/OldIron/resolve/main/README.md
18.8 kB
| license: gpl-3.0 | |
| language: | |
| - en | |
| library_name: llama.cpp | |
| pretty_name: OldIron | |
| tags: | |
| - llama.cpp | |
| - gguf | |
| - cpu-inference | |
| - inference-engine | |
| - quantization | |
| - avx | |
| - avx1 | |
| - sandy-bridge | |
| - hp-z820 | |
| - xeon | |
| - cuda | |
| pipeline_tag: text-generation | |
| # OldIron | |
| An inference engine plus **one named weight file**, built for hardware that current engines have | |
| written off. OldIron is a product line: each variant names the *lowest* machine it must serve, and nothing in it | |
| is allowed to be slower than that floor needs. The weights are third-party and are not shipped here β see | |
| *The weights* below. | |
| | variant | lowest supported CPU | lowest supported GPU | status | | |
| |---|---|---|---| | |
| | **OldIron-AVX** | Xeon E5-2665 (Sandy Bridge-EP, **AVX1**: no AVX2/FMA/F16C/BMI2) | RTX 3060 8 GB (**sm_86**) | built, staged, **measured on two HP Z820s**; CPU kernel work finished (+13.5% at fixed format, +6.4% net) | | |
| | OldIron-Core2 (planned) | SSE4.1/SSSE3 (2007) | none | not started | | |
| ## Results, plainly | |
| **One measured step, and no more.** The AVX1 kernel work shipped and it is worth **+13.5% at fixed format and | |
| +6.4% net** (7.45 β 7.86-7.93 t/s). Every other CPU-lane lever was measured and rejected. The "~3x" projection in | |
| the first version of this README **did not survive the engine tests and is retracted below** β the large multiple | |
| on this hardware is on the GPU lane, not in the matvec. | |
| All numbers measured on an HP Z820 (2Γ Xeon E5-2665, 32 GB DDR3, RTX 5060 Ti) with the 35B-A3B weight file named in | |
| each run, coherent output throughout, `nice -n 19`, box verified idle between arms. | |
| ### CPU lane β shipped state | |
| | what | measured | note | | |
| |---|---|---| | |
| | stock engine, IQ3_S 3.66 bpw | **7.45 Β± 0.04 t/s** | `-ngl 0 --no-op-offload -t 24`, the best prior config | | |
| | stock engine, Q4_0 4.50 bpw | 6.99 Β± 0.47 t/s | the wider format is *slower* on stock kernels (23% more bytes) | | |
| | **shipped W3 AVX1 kernel, Q4_0** | **7.86 Β± 0.08 t/s** (bench) Β· 7.93 Β± 0.02 (harness) | **+13.5% vs stock Q4_0, +6.4% net over stock IQ3_S**; coherent | | |
| | prefill pp128 / pp512 / pp4096 | 29.70 / 29.9 / 28.0 t/s | 8Γ the context costs 6% per token | | |
| | W1 GEMV kernel alone | 4.4 GMAC/s/core from DRAM; 8.9 / 18.1 L1-resident (1 / 2 rows) | exact-integer vs the scalar reference, AVX1-clean | | |
| | gcc vs clang 18 vs icx 2026 | gcc best; clang β9%, icx β15.5% | compiler lever closed | | |
| The trade the kernel buys: the Q4_0 weight file is ~19 GiB against 15.7 GiB for IQ3_S. It still fits a 32 GB box, | |
| but it is a size regression in exchange for the speed. | |
| ### Retractions β what this README claimed, and what the tests returned | |
| | claim in the first version | measured result | | |
| |---|---| | |
| | "projection when W3 lands ~20-24 t/s, about 3x today" | **retracted.** W3 landed and bought 13.5% at fixed format. The engine moves ~28 GB/s where the pure kernel reaches 44 GB/s, so closing that gap entirely is **~1.35Γ end-to-end** on a 77% matvec share β not 3Γ. The kernel path is close to exhausted | | |
| | "NUMA interleave, one flag, +59%" | **0% in the engine** β 3.13 vs 3.13 t/s, two interleaved rounds, on a copy materialised under the interleave policy. `numastat` also shows the page cache already split 50/50 across nodes. The +59% was a 64 MB streaming microbenchmark; the engine's matvec is not purely bandwidth-bound, so it did not transfer | | |
| | "11.96 GB/s = 16% of the 74.9 GB/s ceiling, so 3-5Γ headroom" | the 74.9 GB/s figure is a **sequential** streaming measurement that this access pattern never reaches. Like-for-like the engine is at 28 GB/s against the kernel's 44 GB/s β 1.6Γ, worth ~1.35Γ end-to-end | | |
| | lm_head Q6_K β Q4_0 (417 MB/token on the old path) | **0%** β 7.83 vs 7.93 t/s, inside noise | | |
| ### Also measured and rejected (detail in [`kernels/RESULTS.md`](kernels/RESULTS.md)) | |
| - **hoisted-correction GEMM: β40% in situ** (7.93 β 4.67 t/s) despite +113% on the isolated matrix β the engine | |
| makes ~1.9M small `vec_dot` calls per token, so the per-call precompute is never amortised. Reverted. | |
| - **4-row GEMM batching:** inseparable from the hoist that broke it; not shipped. | |
| - **speculative decoding:** 7.3 β 6.8 t/s (**β7%**) with a 0.8B draft on this thinking-heavy output. | |
| - **NUMA chunk granularity** (dropping the `ggml_is_numa()` term): prefill flat, **decode β7%**. Reverted. | |
| - **K-quant tiled GEMM instead of Q4_0 + `vec_dot`:** the tiled path is **44% slower at prefill** on AVX1 | |
| (19.00/15.74 t/s vs 29.9/28.0), because it compiles its generic path without AVX2/VNNI. Q4_0 + the W3 kernel | |
| wins on decode *and* prefill. | |
| - **CONCAT "10% of prefill":** a profiler artifact β it was the previous node's barrier wait. Parallelising the | |
| copy changed nothing (pp512 29.97 β 29.92). | |
| - **Prefill ceiling:** pp512 is ~2.5 TMAC at 29.9 t/s β 145 GMAC/s against the measured 170 GMAC/s int8 floor, | |
| i.e. **85-90%**, not the 52% an earlier active-parameter estimate assumed. | |
| ### Where the multiple actually is: the GPU lane | |
| The CPU lane is at its ISA ceiling. The 16 GB card is not. Best long-context split on the 35B weights, measured at | |
| a real 32k context with a 140 KB prompt (not `llama-bench`'s short context): | |
| | config | prompt | generation | peak VRAM | | |
| |---|---|---|---| | |
| | **`-ngl 99 -ncmoe 10 -t 24 -c 32768`, f16 KV** | **832.9 t/s** | **52.5 t/s** | 15339 MiB | | |
| | same + KV q8_0 | 839.2 | 48.3 (β8%) | 15339 MiB | | |
| | `-ncmoe 12 -t 24` | 731.7 | 51.6 | 14465 MiB (1.4 GB spare) | | |
| | CPU only (fallback) | 29.9 | 7.86 | β | | |
| **6.7Γ the CPU lane's decode and 29Γ its prefill.** Only 508 MiB spare at `-ncmoe 10`, so 32k is the ceiling with | |
| this split; `-ncmoe 12` costs 2% and holds 1.4 GB of headroom. KV quantisation is a net loss here β the hybrid | |
| DeltaNet KV is ~12 KB/token, so q8 mostly adds dequant work. | |
| ## What OldIron-AVX is | |
| One binary, one weight file, three lanes: | |
| | lane | how it runs | target | |
| |---|---|---| | |
| | A β CPU | `-ngl 0` | the AVX1 Xeon pair, no GPU at all | | |
| | B β GPU | `-ngl 99` | RTX 5060 Ti 16 GB (sm_120) | | |
| | C β 8 GB budget | `-ngl 99 -ncmoe N` | RTX 3060 8 GB (sm_86): attention on the card, routed experts on the CPU | | |
| ### Engine provenance | |
| - source: llama.cpp `2744e48d1` (build 816, 0.7.0-dev), `qwen35moe` architecture support present | |
| - CPU backend: **one variant**, `-march=sandybridge -mno-avx2 -mno-fma -mno-f16c -mno-bmi2` + `-msse4.2 -mavx` | |
| (ggml prints that as `-msse4.2;-mavx` / `GGML_SSE42;GGML_AVX` β on Sandy Bridge that *is* AVX1, not a lower floor) | |
| - `-march=sandybridge` also sets `-mtune=sandybridge`, so the code is scheduled for the Z820 core even though it is | |
| built on a Zen4 host | |
| - CUDA fatbin: **`sm_86` + `sm_120`**, 145 kernels each (RTX 3060 is the supported floor, 5060 Ti the test card) | |
| - `GGML_LLAMAFILE=ON`, `GGML_CPU_REPACK=ON`, `GGML_OPENMP=ON` | |
| - `RUNPATH=$ORIGIN/../lib` on binaries, `$ORIGIN` on libs β the tree is relocatable | |
| ### ISA gate (measured, not asserted) | |
| `scripts/isa-audit.sh` on the shipped `libggml-cpu.so`: | |
| ``` | |
| AVX2-only mnemonics : 0 FMA : 0 | |
| int-vector on %ymm : 0 F16C : 0 | |
| AVX-512 (%zmm) : 0 AVX1 %ymm ops : 5335 | |
| VERDICT: AVX1-clean and using 256-bit width | |
| ``` | |
| **The audit pattern matters.** A naive grep for "AVX2 opcodes" reports false positives: `vpmulld`, `vpblendvb`, | |
| `vpminsd`/`vpmaxsd` are SSE4.1 instructions whose **VEX.128 form is legal AVX1**, and unanchored patterns like | |
| `v(p[a-z]*|ps[a-z]*)` match the `vps` inside `vdivps`/`vpermilps`. The sound tests are (a) genuinely AVX2-only | |
| mnemonics (`vpermd`, `vpermq`, `vpbroadcast[dwq]`, `vpsllvd`, `vpgather*`, `vpblendd`) and (b) **integer-vector ops | |
| on `%ymm`** β AVX1 has no 256-bit integer arithmetic at all. `vmovdqa/vmovdqu` on `%ymm` are AVX1-legitimate. | |
| ### The weights: third-party, and not shipped | |
| `Qwen3.6-35B-A3B` in GGUF β 35B total / 3B active, arch `qwen35moe` (256 experts, 8 routed + 1 shared, 40 layers, | |
| 3:1 Gated DeltaNet : full attention, 262k context, 248320 vocab, MTP-trained). Three files appear in these | |
| measurements: the stock `Qwen3.6-35B-A3B-IQ3_M.gguf` (**16.9 GB, 3.52 bits per weight**), an `IQ3_S` mix | |
| (3.66 bpw, 15.73 GiB) used for the lane matrix, and a `Q4_0` re-quant of the same model (4.50 bpw, ~19 GiB) | |
| written only to A/B the AVX1 kernel. | |
| **These weights are not ours, and this project does not include them.** They are third-party GGUF files of a public | |
| model, downloaded and checksum-verified in transit; the `Q4_0` copy is a local re-quant of the same model and is a | |
| test artefact, not a product. What is published here is engine source, kernel source, scripts and measurements β no | |
| weight file is redistributed, and no weight file is claimed as part of the deliverable. | |
| Why this shape of model: decode speed is set by *active* parameters and RAM is the scarce resource, so a 35B/3B | |
| MoE at 3.5-4.5 bits fits a 32 GB box with ~15 GB to spare for a long-context KV cache (20 KB/token β 32k ctx | |
| β 0.67 GB), where a 27B dense at a higher quant would be ~7Γ slower for less quality headroom. | |
| ## Measured: HP Z820 (2Γ E5-2665, 16 cores, RTX 5060 Ti) | |
| `Qwen3.6-35B-A3B-IQ3_S` mix, 3.66 bpw, 15.73 GiB, 34.66B params β coherent on every lane | |
| ("The capital of France is **Paris**."). | |
| | lane | config | decode | prefill | peak VRAM | verdict | | |
| |---|---|---|---|---|---| | |
| | A β CPU only | `-ngl 0 -nopo 1 -t 16` | 7.25 Β± 0.10 t/s | 28.8 t/s (pp128) | β | works, kernel-bound | | |
| | A β CPU only | `-ngl 0 -nopo 1 -t 24` | **7.56 Β± 0.03 t/s** | 28.5 t/s | β | best CPU arm on stock kernels | | |
| | A β CPU, shipped kernel | Q4_0 + W3, `-t 24` | **7.86 Β± 0.08 t/s** | 29.70 t/s (pp128) | β | what actually ships | | |
| | B β 5060 Ti maximised | `-ngl 99 -ncmoe 4` | **73.3 Β± 1.0 t/s** | **763.7 t/s** (pp512) | 14797 MiB | best short-context GPU arm | | |
| | B β | `-ngl 99 -ncmoe 8` | 50.8 t/s | β | 13345 MiB | | | |
| | C β 3060 8 GB, tight | `-ncmoe 24` | 29.7 t/s | β | 7739 MiB | fits, ~450 MiB margin | | |
| | **C β 3060 8 GB, safe** | **`-ncmoe 32`** | **23.8 t/s** | 63.9 t/s | **4905 MiB** | **recommended: 3 GB headroom for context** | | |
| | C β | `-ncmoe 40` | 19.1 t/s | 52.7 t/s | 1869 MiB | | | |
| | β | `-ngl 99` alone | **OOM** | β | β | 15898 MiB allocation fails | | |
| **"GPU full" does not exist for this weight set on a 16 GB card.** 15.73 GiB of weights + KV + context will not fit | |
| 15.8 GiB of VRAM, so the GPU lane is *always* hybrid here: `-ncmoe 4` on a 16 GB card, `-ncmoe 32` on an 8 GB card. | |
| For long context the split that matters is in *Results, plainly* above (`-ncmoe 10`, 52.5 t/s at 32k). | |
| CPU lane effective throughput: ~1.54 GB/token (3B active + the 248k-vocab lm_head) Γ· 132 ms β **11.7 GB/s.** That | |
| is against the **44 GB/s** the pure kernel reaches on this access pattern, not against the box's 74.9 GB/s | |
| sequential streaming figure β see the retractions above for why the difference matters. | |
| ### Earlier proxy run (kept for comparison) | |
| `gpt-oss-20b Q4_K_M`, 20.9B total / 3.6B active, 10.81 GiB, measured before the 35B weights finished copying: | |
| | lane | decode | prefill | note | | |
| |---|---|---|---| | |
| | A CPU only, `-t 16` | **8.03 Β± 0.02 t/s** | **23.9 t/s** (pp512, `--no-op-offload`) | coherent ("β¦is Paris") | | |
| | B GPU full (`-ngl 99`) | **142.7 Β± 2.4 t/s** | 297 t/s | coherent | | |
| | C `-ncmoe 16` | 19.7 t/s | β | peak VRAM 4605 MiB | | |
| | C `-ncmoe 24/32` | 13.4β13.9 t/s | β | peak VRAM 1373 MiB | | |
| ### Two measurement traps, both found here | |
| **1. `-ngl 0` is not CPU-only when a CUDA backend is present.** Op-offload defaults to ON and moves the batched | |
| matmuls to the GPU. The contaminated arms reported **242β341 t/s of "CPU prefill"** β physically impossible on this | |
| box (it implies ~3 TMAC/s against a ~0.3 TMAC/s ceiling). With `--no-op-offload` the same arm reads **23.9 Β± 0.9 | |
| t/s**. Decode was never affected, only prefill. Any CPU-lane prefill number without `--no-op-offload` is a GPU | |
| number wearing a CPU label. | |
| **2. `-fitt` is not a VRAM cap** (see below). | |
| ### AVX1 floor vs `-march=native`: the floor costs nothing | |
| Same source (2744e48d1), same model, same flags, built both ways on the Z820 itself: | |
| | build | decode (tg48/128) | prefill (pp512) | | |
| |---|---|---| | |
| | OldIron-AVX floor (`-march=sandybridge`, cross-built on Zen4) | 8.55 / 9.03 t/s | 23.9 Β± 0.9 t/s | | |
| | native (`-march=native`, built on the E5-2665) | 8.74 / 8.83 t/s | ~25 t/s | | |
| Parity within run-to-run spread. Repeated on the 35B weights: floor **7.56 Β± 0.03** (t=24) / 7.25 Β± 0.10 (t=16) | |
| vs native **7.63 Β± 0.05** t/s β ~1% apart. One detached harness pass measured the two back to back: | |
| floor **7.38 Β± 0.02** vs native **7.43 Β± 0.05**: **0.7%**, which is the run-to-run spread itself. Three independent passes of the same arms now agree within ~3-6%, so the floor is confirmed free. The two targets differ only in `-maes` (AES-NI), which no inference kernel | |
| uses, so the cross-built floor is not leaving AVX1 performance on the table and OldIron-AVX can be built anywhere. | |
| (An A/B against the fleet's older `ik_llama-sb-avx1` floor build could not be run: its `llama-cli` exits | |
| `Permission denied` even after `chmod +x`, so that binary is unusable as an arm β unresolved, and not needed while | |
| the same-commit native comparison exists.) | |
| CPU lane effective weight throughput on that run: 3.6B active Γ 4.85 bits β 2.18 GB/token Γ· 125 ms β | |
| **17.5 GB/s.** Measured at 8 / 16 threads the engine moves 24 / **28 GB/s**, against 44 GB/s for the pure kernel on | |
| a sequential 64 MB matrix β within 1.6Γ, which is the honest size of the kernel headroom. | |
| ### Open item: `-fitt` is not a safe VRAM cap | |
| `-fitt 7400` produced a **peak of 8515 MiB** and `-fitt 6400` a peak of **9447 MiB** β both above an 8 GB card's | |
| budget. The fit heuristic and `-ncmoe` fight each other, and the peak includes the CUDA context. Lane C must be | |
| pinned with an explicit `-ncmoe` and verified by sampling `nvidia-smi` *during* the run (sampling before/after | |
| reads 7 MiB and proves nothing). | |
| ## One binary, both Z820s (the compatibility claim, measured) | |
| Same OldIron-AVX binary, CPU lane, `-ngl 0 -nopo 1`: | |
| | host | CPU | quant | decode | prefill | effective weight throughput | | |
| |---|---|---|---|---|---| | |
| | Z820 #1 | 2Γ E5-2665 @ 2.4 GHz | IQ3_M, 3.66 bpw | 7.45 Β± 0.04 t/s (t=24) | 28.5 t/s | **11.96 GB/s** | | |
| | Z820 #2 | 2Γ E5-2687W @ 3.1 GHz | Q4_K_M, 4.85 bpw | 5.66 Β± 0.02 t/s (t=16) | 40.7 t/s | **12.04 GB/s** | | |
| Normalised for quant, the two boxes move **within 0.7% of the same bytes per second** β decode is DRAM-bound, so the | |
| 29% clock advantage buys nothing there, exactly as the memory model predicts. Prefill is compute-bound and *does* | |
| scale with clock (1.43Γ). Coherent on both ("The capital of France is **Paris**."). | |
| That is the compatibility measurement, not an argument: one AVX1 binary covers both boxes with no per-host build, and | |
| the only thing that changed the token rate was the weight file. | |
| ## Operational notes (learned the hard way on this build) | |
| - **Run the harness detached, in tmux** β the wifi link to the Z820s drops ssh sessions mid-run. tmux is installed | |
| without root on both Z820s (`scripts/install-tmux-noroot.sh`: `apt-get download` + `dpkg -x` into `~/tmux-local`, | |
| wrapper at `~/bin/tmux`); the build host has it from apt. | |
| - **Under tmux the CLI gets a pty and enters its interactive REPL** after the first turn, hanging until the timeout | |
| instead of exiting. Every non-interactive CLI arm needs `< /dev/null`. `llama-cli` also writes a **0-byte log** | |
| under a tmux pty; capture CLI output over a plain ssh pipe (`llama-bench` is fine under tmux). | |
| - **Never point `cuobjdump` at a symlink and its target together**: given `libggml-cuda.so libggml-cuda.so.0 | |
| libggml-cuda.so.0.27.0` it prints a bogus mixed summary (`sm_50β¦sm_101`, 1β2 each) that looks like arch | |
| contamination. On one `readlink -f` path it reports the truth: 145 `sm_120` + 145 `sm_86`. | |
| - **Coherence capture belongs on non-interactive ssh**, not the tmux arm: under the pty the answer text is not | |
| captured even with stdin closed, so the gate is run over plain ssh where it has produced the correct answer | |
| repeatedly. | |
| - **Confirm the box is idle between arms** β a stale `llama-bench` survived an ssh timeout and competed with three | |
| subsequent measurements for 23 minutes (one produced a nonsense 1.31 t/s). | |
| - **CMake does not rebuild on a compile-definition change** β flipping `GGML_CPU_REPACK` rewrote `flags.make` and | |
| left every object in place, producing a fake "repack off" result. Delete the target's `*.o` and touch the sources. | |
| - **Never quote a rate from a profiled run** β `OLDIRON_OPPROF=1` costs ~1.6x (19.0 s of op time for 96 tokens vs | |
| 12.3 s), so a profiler run and a clean run are not comparable. | |
| ## Acceptance gates (a fast wrong answer is a fail) | |
| 1. **ISA** β `scripts/isa-audit.sh` on the shipped libs: 0 above the floor, `%ymm` present. | |
| 2. **Fatbin coverage** β `cuobjdump --list-elf` shows `sm_86` and `sm_120`. | |
| 3. **Coherence** β fixed prompts through each lane, text compared to a known-good engine. This fleet has shipped a | |
| floored build that answered `The capital of France is!!!!!!!!` at 65 t/s; speed is measured last, on purpose. | |
| 4. **Speed** β `llama-bench`, thread sweep on the CPU lane (expect an optimum, not the largest count). | |
| 5. **8 GB class** β lane C must peak under ~7.8 GB sampled during the run. | |
| ## What is in this repository | |
| Source, scripts and measurements. Not included, deliberately: | |
| - `src/` - the engine is an unmodified llama.cpp worktree at commit `2744e48d1`; clone it from upstream rather than | |
| vendoring it here. | |
| - weights - `Qwen3.6-35B-A3B` and `gpt-oss-20b` GGUFs are third-party downloads (HF), not redistributed. | |
| - build output, staged binaries, kernel test binaries. | |
| ``` | |
| kernels/ the AVX1 quantised GEMV/GEMM work (source only) and RESULTS.md, the waypoint log | |
| scripts/ build (AVX1 floor + sm_86;120), ISA audit, staging, weight transfer, acceptance matrix | |
| results/ the measured runs | |
| ``` | |
| ## License | |
| GPL-3.0-or-later. See `LICENSE`; the copyright notice and the third-party terms (llama.cpp is MIT and stays MIT) | |
| are in `NOTICE`. | |
| ## Layout | |
| ``` | |
| /home/bob/OldIron/ | |
| src/ llama.cpp worktree @ 2744e48d1 | |
| build/ the built engine (bin/ + lib/) | |
| stage/ patched-RUNPATH copy for shipping | |
| scripts/ | |
| build-oldiron-avx.sh build (AVX1 floor + sm_86;120) | |
| isa-audit.sh the ISA gate (see note above about the pattern) | |
| stage-to-target.sh relocatable copy + load check | |
| fetch-weights.sh parallel-stream weight transfer (link is wifi/WAN here) | |
| acceptance-hp-z820.sh the full matrix on the Z820 | |
| ``` | |
| On a target machine the engine sits at `<root>/engine/{bin,lib}` with the weight file wherever you keep it β nothing | |
| under this repository's tree. | |