nx_softfloat_cost_kat.nx
buildroot/runtime/nx_softfloat_cost_kat.nx
about
nx_softfloat_cost_kat.nx -- WHAT DOES SOFTWARE FLOAT ACTUALLY COST US? The most basic number in a
no-float ecosystem, and nobody had measured it.
WHY (2026-07-31): the perf lane converged on 'the inner loop is compute-bound on emulated float', but
that was INFERRED, not measured. The chain: nx_batchscale_kat showed the matmul FLAT across m (1.12x);
nx_matmul_tile_kat then read the weights 8x fewer times and gained 1.02x -- proving memory traffic was
NOT the constraint; and the implied MAC rate was ~164 MFLOP/s, absurd for a modern core. Meanwhile
nx_lw_cache_gate measured the Q4_K packed path at 4431 mflops on the SAME host -- ~27x faster, computing
on INTEGER MANTISSAS. All of that POINTS at software float as the tax. None of it MEASURES it.
THIS DOES: identical loop shape, identical trip count, identical accumulate-and-consume discipline --
one arm in emulated f32 (__f32_mul/__f32_add), one in native i64. The ratio IS the no-float tax per MAC.
WHY BOTH ACCUMULATORS ARE PRINTED: a dead-code eliminator that dropped an unused result would make the
integer arm look infinitely fast and produce a beautiful, meaningless number. Both accumulators are
consumed by the output, and T3/T4 assert them -- a zero or wrong accumulator means the loop did not run
and the timing is a lie. A benchmark that cannot prove its own loop executed is not a benchmark.
WHAT THE ANSWER CHANGES: if the tax is large then quantisation is not primarily a BYTES decision, it is
an ARITHMETIC decision -- integer/mantissa MACs skip the emulation entirely. Same action, different
reason, and the reason decides what gets built next.
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 3 imports · 0 importers
imports: nx_f32.nxnx_f32_cvt.nxnx_fmt.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 27 | const SF_N: i64 = 2000000 |
functions
| 29 | func sf_nl() -> i64 { fmt_puts("\n" as *u8); return 0 } |
| 31 | func sf_t(name: *u8, cond: i64, ctr: *i64) |
| 39 | func main() -> i64 |