code wiki / _hdl_build / nx_roofline_gate.nx
nx_roofline_gate.nx source
↩ module page · 195 lines · 7755 B
1// nx_roofline_gate.nx -- IS DECODE COMPUTE-BOUND OR MEMORY-BANDWIDTH-BOUND?
2//
3// WHY (2026-07-31): an open sev-9 calls the sovereign embedder '~1000x too slow' at 400ms/token and declares
4// the path infeasible. That was read as a CODE defect. But a batch=1 decode step must read EVERY weight once,
5// so the floor is bytes/bandwidth and nothing else: 0.5B params x 4 bytes (f32) = 2.0 GB READ PER TOKEN.
6// At a few GB/s that is hundreds of ms/token BY ARITHMETIC, with a perfect kernel. If measured is near the
7// floor then the code is not slow, it is AT ROOFLINE, and the only levers are FEWER BYTES (4-bit weights) or
8// MORE WORK PER BYTE (batch m>1). Tuning the dot product would buy nothing.
9//
10// THE CODEBASE ALREADY REASONED THIS OUT, INDEPENDENTLY: nx_f32_lazy_weight.nx:706 states 'the 2.72x
11// single-dot gate was L1-resident = misleading. The real decode lever is FEWER BYTES read (keep weights
12// QUANTIZED 4-bit + fast SIMD dequant-dot), or batch tokens (m>1 = compute-bound), NOT f32 packing.'
13// AND IT MATCHES 2026 EXTERNAL SOTA: Neural Speed reports >90pct of MEMORY BANDWIDTH utilisation on INT4
14// GEMV (the good engines are already AT the bandwidth bound, so bandwidth IS the ceiling), and
15// Graviton3/Llama3-8B-4bit measures 45.5 tok/s at batch=1 vs 184.8 tok/s at batch=8 -- a ~4x win from
16// BATCHING ALONE, which is exactly the signature of a bandwidth-bound decode turning compute-bound.
17//
18// WHY THIS ORGAN EXISTS RATHER THAN A NOTE: a small model will never re-derive this. The insight has to live
19// in an INSTRUMENT that fires for whoever comes next, or it is rediscovered by hand every time -- which is
20// how 400ms/token got recorded as a defect instead of a roofline.
21//
22// nx_roofline_gate -- selftest teeth only
23// nx_roofline_gate est <Mparams> [ms] -- floors for f32/int8/int4; with [ms], the roofline fraction
24// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
25import "nx_syscalls.nx"
26
27const RF_BUFMB: i64 = 64
28const RF_MB: i64 = 1048576
29const RF_STRIDE: i64 = 64
30const RF_NEAR_PERMIL: i64 = 700
31
32func rf_puts(s: *u8) {
33 var n: i64 = 0
34 while s[n] != (0 as u8) { n = n + 1 }
35 sys_write(1, s, n)
36}
37
38func rf_puti(x: i64) {
39 var buf: *u8 = sys_mmap(64) as *u8
40 var v: i64 = x
41 var neg: i64 = 0
42 if v < 0 {
43 neg = 1
44 v = 0 - v
45 }
46 var i: i64 = 40
47 if v == 0 {
48 i = i - 1
49 buf[i] = 48 as u8
50 }
51 while v > 0 {
52 let d: i64 = v - (v / 10) * 10
53 i = i - 1
54 buf[i] = (d + 48) as u8
55 v = v / 10
56 }
57 if neg == 1 {
58 i = i - 1
59 buf[i] = 45 as u8
60 }
61 sys_write(1, ((buf as i64) + i) as *u8, 40 - i)
62}
63
64func rf_atoi(s: *u8) -> i64 {
65 var r: i64 = 0
66 var i: i64 = 0
67 while s[i] != (0 as u8) {
68 let c: i64 = s[i] as i64
69 if c >= 48 {
70 if c <= 57 { r = r * 10 + (c - 48) }
71 }
72 i = i + 1
73 }
74 return r
75}
76
77// PURE: floor in MICROseconds to stream `bytes` at `mb_per_s` megabytes/sec.
78// Separated from the measurement so the gate can prove the arithmetic without touching a clock.
79func rf_floor_us(bytes: i64, mb_per_s: i64) -> i64 {
80 if mb_per_s <= 0 { return 0 - 1 }
81 return bytes / mb_per_s
82}
83
84// PURE: bytes read per decoded token = params * bytes_per_weight. batch=1 reads every weight once.
85func rf_bytes_per_token(mparams: i64, bits: i64) -> i64 {
86 return mparams * 1000000 * bits / 8
87}
88
89// MEASURE achievable sequential read bandwidth, in MB/s.
90// The buffer is FAULTED IN FIRST: timing a cold mmap measures page faults, not bandwidth -- that mistake
91// would report a bandwidth far below the truth and make every roofline look falsely close.
92func rf_measure_mb_s() -> i64 {
93 let n: i64 = RF_BUFMB * RF_MB
94 let b: *u8 = sys_mmap(n)
95 if b as i64 == 0 { return 0 - 1 }
96 var i: i64 = 0
97 while i < n {
98 b[i] = (i - (i / 251) * 251) as u8
99 i = i + RF_STRIDE
100 }
101 let t0: i64 = sys_now_us()
102 var acc: i64 = 0
103 var p: i64 = 0
104 while p < n {
105 acc = acc + (b[p] as i64)
106 p = p + RF_STRIDE
107 }
108 let dt: i64 = sys_now_us() - t0
109 if acc == 0 - 1 { rf_puts("" as *u8) }
110 if dt <= 0 { return 0 - 1 }
111 return (RF_BUFMB * 1000000) / dt
112}
113
114func rf_row(label: *u8, mparams: i64, bits: i64, mb_s: i64, measured_ms: i64) {
115 let by: i64 = rf_bytes_per_token(mparams, bits)
116 let us: i64 = rf_floor_us(by, mb_s)
117 rf_puts(" " as *u8)
118 rf_puts(label)
119 rf_puts(" bytes/token=" as *u8); rf_puti(by)
120 rf_puts(" floor_ms=" as *u8); rf_puti(us / 1000)
121 if measured_ms > 0 {
122 let floor_ms: i64 = us / 1000
123 if floor_ms > 0 {
124 rf_puts(" measured/floor=" as *u8); rf_puti(measured_ms * 100 / floor_ms)
125 rf_puts("x" as *u8)
126 }
127 }
128 rf_puts("\n" as *u8)
129}
130
131func rf_t(name: *u8, cond: i64, ctr: *i64) {
132 if cond == 1 {
133 rf_puts(" ok " as *u8)
134 ctr[0] = ctr[0] + 1
135 } else {
136 rf_puts(" FAIL " as *u8)
137 }
138 rf_puts(name)
139 rf_puts("\n" as *u8)
140 ctr[1] = ctr[1] + 1
141}
142
143func main(argc: i64, argv: *i64) -> i64 {
144 var ctr: *i64 = sys_mmap(64) as *i64
145 ctr[0] = 0
146 ctr[1] = 0
147
148 rf_puts("=== nx_roofline_gate -- decode floor = bytes/bandwidth, measured on THIS host ===\n" as *u8)
149
150 rf_t("T1 f32 0.5B reads 2.0GB per token (params x 4 bytes)" as *u8,
151 rf_bytes_per_token(500, 32) == 2000000000, ctr)
152 rf_t("T2 int4 reads 8x FEWER bytes than f32 (the whole lever)" as *u8,
153 rf_bytes_per_token(500, 32) == rf_bytes_per_token(500, 4) * 8, ctr)
154 rf_t("T3 floor scales with bytes: 2GB at 1000MB/s = 2000ms" as *u8,
155 rf_floor_us(2000000000, 1000) == 2000000, ctr)
156 rf_t("T4 FAIL-CLOSED: zero bandwidth cannot produce a floor" as *u8,
157 rf_floor_us(2000000000, 0) == 0 - 1, ctr)
158 rf_t("T5 NEG-CONTROL: int4 floor is NOT equal to f32 floor (a flat model would hide the lever)" as *u8,
159 rf_floor_us(rf_bytes_per_token(500, 4), 1000) != rf_floor_us(rf_bytes_per_token(500, 32), 1000), ctr)
160
161 let mb_s: i64 = rf_measure_mb_s()
162 rf_t("T6 LIVE: sequential read bandwidth measured and plausible (>100 MB/s)" as *u8, mb_s > 100, ctr)
163
164 rf_puts("\nMEASURED sequential read bandwidth = " as *u8); rf_puti(mb_s)
165 rf_puts(" MB/s (buffer faulted in first, so this is bandwidth, not page-fault cost)\n" as *u8)
166
167 var mp: i64 = 500
168 var measured_ms: i64 = 0
169 if argc > 2 { mp = rf_atoi(argv[2] as *u8) }
170 if argc > 3 { measured_ms = rf_atoi(argv[3] as *u8) }
171
172 rf_puts("\n-- DECODE FLOOR at " as *u8); rf_puti(mp)
173 rf_puts("M params, batch=1 (every weight read once per token) --\n" as *u8)
174 rf_row("f32 " as *u8, mp, 32, mb_s, measured_ms)
175 rf_row("int8" as *u8, mp, 8, mb_s, measured_ms)
176 rf_row("int4" as *u8, mp, 4, mb_s, measured_ms)
177
178 rf_puts("\nREAD THIS BEFORE OPTIMISING A KERNEL: if measured/floor is near 1x on the f32 row, the code is\n" as *u8)
179 rf_puts(" AT ROOFLINE and a faster dot product buys NOTHING. The levers are FEWER BYTES (int4 row above)\n" as *u8)
180 rf_puts(" or MORE WORK PER BYTE (batch m>1, which turns a bandwidth-bound decode compute-bound --\n" as *u8)
181 rf_puts(" externally: Graviton3 Llama3-8B-4bit 45.5 tok/s at batch=1 vs 184.8 at batch=8).\n" as *u8)
182 rf_puts("envelope: bandwidth is ONE sample of a strided sequential read on a shared NAS -- a FLOOR estimate,\n" as *u8)
183 rf_puts(" not a spec sheet; re-run under quiet load before quoting it. One window is not a rate.\n" as *u8)
184
185 rf_puts("\nROOFLINE-GATE " as *u8)
186 rf_puti(ctr[0])
187 rf_puts("/" as *u8)
188 rf_puti(ctr[1])
189 if ctr[0] == ctr[1] {
190 rf_puts(" GREEN\n" as *u8)
191 return 0
192 }
193 rf_puts(" RED\n" as *u8)
194 return 1
195}