File size: 18,836 Bytes
b4adf81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b039daa
 
56c1df5
a66a9c7
56c1df5
 
a66a9c7
 
 
56c1df5
a66a9c7
 
 
 
b039daa
 
 
 
 
56c1df5
 
a66a9c7
b039daa
a66a9c7
 
 
b039daa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56c1df5
 
b039daa
 
 
 
 
 
 
a66a9c7
b039daa
 
 
a66a9c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56c1df5
a66a9c7
56c1df5
 
 
 
 
a66a9c7
56c1df5
 
 
 
a66a9c7
56c1df5
 
 
a66a9c7
56c1df5
 
 
a66a9c7
 
 
 
 
b039daa
 
 
a66a9c7
 
 
 
 
 
 
 
b039daa
a66a9c7
b039daa
 
 
a66a9c7
 
 
56c1df5
a66a9c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56c1df5
a66a9c7
 
 
 
 
 
 
 
 
b039daa
 
a66a9c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56c1df5
 
a66a9c7
 
 
 
 
 
 
 
 
 
 
 
56c1df5
a66a9c7
b039daa
 
a66a9c7
 
 
 
 
 
b039daa
 
 
 
 
 
a66a9c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56c1df5
a66a9c7
56c1df5
 
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
---
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.