code wiki / (root) / nx_bench_scale_test.nx

nx_bench_scale_test.nx source

↩ module page · 168 lines · 6724 B

1// nx_bench_scale_test.nx -- substrate scaling under increasing workload. 2// 3// Runs the v2 LLM actor at 3 different iteration counts (10 / 30 / 100) 4// and emits ONE TSV row + wallclock_elapsed_us per scale to stdout. 5// Each scale is an INDEPENDENT measurement: fresh session, fresh 6// fixture, fresh PRNG seed, fresh wall-clock window. 7// 8// What this answers: 9// * Does wall-clock scale ROUGHLY LINEARLY with iteration count? 10// If yes -> substrate has no quadratic overhead under load. 11// If no -> there's a scaling bug we need to find. 12// * Do counters scale exactly linearly? (They should: 3 phases per 13// iteration * N iterations = 3*N actor steps; 1000us logical per 14// step = 3000*N us logical runtime.) 15// 16// Output format: standard nx_bench TSV header once, then one row per 17// scale (labels "scale_v2_10" / "scale_v2_30" / "scale_v2_100") plus 18// per-scale wallclock_elapsed_us lines. Operator can pipe to file + 19// sort -t$'\t' -k7n to see the scaling curve. 20// 21// HONEST SCOPE: qemu-riscv64 wall-clock includes interpreter overhead; 22// the SHAPE of the scaling (linear vs quadratic) is what matters, not 23// the absolute numbers. Physical-hardware run will refine the 24// absolute numbers; the shape claim should hold across both. 25 26import "nx_syscalls.nx" 27import "nx_tier.nx" 28import "nx_clock.nx" 29import "nx_actor.nx" 30import "nx_message.nx" 31import "nx_session.nx" 32import "nx_bench_companion.nx" 33import "nx_bench_companion_timed.nx" 34import "nx_bench_companion_tsv.nx" 35import "nx_gguf_fixture_tiny.nx" 36import "nx_actor_role_llm_v2.nx" 37 38func _emit_dec_i64(fd: i64, n: i64) -> i64 { 39 let scratch: *u8 = sys_mmap(32) 40 var v: i64 = n 41 if v < 0 { v = 0 - v } 42 var k: i64 = 0 43 if v == 0 { scratch[0] = 0x30 as u8; k = 1 } 44 while v > 0 { 45 scratch[k] = (0x30 + (v - (v / 10) * 10)) as u8 46 v = v / 10 47 k = k + 1 48 } 49 let rev: *u8 = sys_mmap(32) 50 var j: i64 = 0 51 while j < k { rev[j] = scratch[k - 1 - j]; j = j + 1 } 52 rev[k] = 0x0A as u8 53 sys_write(fd, rev, k + 1) 54 return 0 55} 56 57func _emit_elapsed_label(fd: i64) -> i64 { 58 let elab: *u8 = sys_mmap(21) 59 elab[0]=0x77 as u8; elab[1]=0x61 as u8; elab[2]=0x6C as u8; elab[3]=0x6C as u8 60 elab[4]=0x63 as u8; elab[5]=0x6C as u8; elab[6]=0x6F as u8; elab[7]=0x63 as u8 61 elab[8]=0x6B as u8; elab[9]=0x5F as u8; elab[10]=0x65 as u8; elab[11]=0x6C as u8 62 elab[12]=0x61 as u8; elab[13]=0x70 as u8; elab[14]=0x73 as u8; elab[15]=0x65 as u8 63 elab[16]=0x64 as u8; elab[17]=0x5F as u8; elab[18]=0x75 as u8; elab[19]=0x73 as u8 64 elab[20]=0x09 as u8 65 sys_write(fd, elab, 21) 66 return 0 67} 68 69// ===== Drive one scale + emit one TSV row ===== 70// 71// Builds a fresh session + fixture + adapter, runs n_iters of v2 LLM 72// autoreg, captures wall-clock + counters, emits to stdout. Returns 73// 0 on success, non-zero verdict on internal failure. 74 75func _run_scale(n_iters: nx_int, 76 prng_seed: i64, 77 label: *u8, label_len: nx_int, 78 tsv_buf: *u8, tsv_cap: nx_int, 79 now: nx_size) -> nx_int { 80 let fix: *NxGgufFixtureBundle = nx_gft_build_tiny_llama(prng_seed) 81 if nx_gft_is_built(fix) != 1 { return 1 } 82 let s: *NxSession = nx_session_new_default(now) 83 if nx_session_is_ready(s) != 1 { return 2 } 84 let LLM: nx_int = 7001 85 let LIS: nx_int = 7099 86 nx_session_spawn_actor(s, LLM, 1, 80, 0, now) 87 nx_session_spawn_actor(s, LIS, 5, 50, 0, now) 88 nx_session_subscribe(s, LIS, NX_MS_KIND_LLM_TOKEN) 89 90 let prompt: *u8 = sys_mmap(1); prompt[0] = 0x61 91 let ctx: *NxLlmV2ActorCtx = nx_lv_actor_new( 92 fix.spec, fix.gguf_buf, fix.hdr, fix.bpe, prompt, 1, 93 1024, 4, fix.prng_state, 10000, 724) 94 if (ctx as i64) == 0 { return 3 } 95 96 let base: *NxBenchReport = nx_bc_report_new() 97 let timed: *NxBenchTimedReport = nx_bctm_new(base) 98 timed.wallclock_start_ns = nx_clock_monotonic_ns() 99 100 var iter: nx_int = 0 101 var tick: nx_size = now + 100 102 while iter < n_iters { 103 nx_lv_actor_step(ctx, s.scheduler, s.bus, LLM, tick); tick = tick + 50 104 nx_lv_actor_step(ctx, s.scheduler, s.bus, LLM, tick); tick = tick + 50 105 if ctx.runner_result < 0 { return 4 } 106 nx_lv_actor_step(ctx, s.scheduler, s.bus, LLM, tick); tick = tick + 50 107 if iter < (n_iters - 1) { 108 prompt[0] = nx_gft_vocab_byte(fix, ctx.runner_result) as u8 109 nx_lv_actor_reset_for_next_token(ctx, s.scheduler, LLM, prompt, 1) 110 } 111 iter = iter + 1 112 } 113 114 if nx_bctm_capture_finish(timed, s, 11) != NX_BCTM_OK { return 5 } 115 116 // Substrate invariant: n_iters * 3 phases = total_actor_steps 117 if base.total_actor_steps != (n_iters * 3) { return 6 } 118 119 // Emit TSV row 120 let n_row: nx_int = nx_bc_emit_tsv_row(tsv_buf, tsv_cap, base, label, label_len) 121 if n_row <= 0 { return 7 } 122 sys_write(1, tsv_buf, n_row as i64) 123 124 // Emit wallclock_elapsed_us line 125 _emit_elapsed_label(1) 126 _emit_dec_i64(1, timed.wallclock_elapsed_us) 127 128 return 0 129} 130 131func main() -> i64 { 132 let now: nx_size = 1000000 133 let buf: *u8 = sys_mmap(512) 134 135 // ===== One TSV header for all three rows ===== 136 let n_hdr: nx_int = nx_bc_emit_tsv_header(buf, 512) 137 if n_hdr <= 0 { return 1 } 138 sys_write(1, buf, n_hdr as i64) 139 140 // Labels: "scale_v2_10" (11), "scale_v2_30" (11), "scale_v2_100" (12) 141 let label_10: *u8 = sys_mmap(11) 142 label_10[0]=0x73 as u8; label_10[1]=0x63 as u8; label_10[2]=0x61 as u8 143 label_10[3]=0x6C as u8; label_10[4]=0x65 as u8; label_10[5]=0x5F as u8 144 label_10[6]=0x76 as u8; label_10[7]=0x32 as u8; label_10[8]=0x5F as u8 145 label_10[9]=0x31 as u8; label_10[10]=0x30 as u8 146 147 let label_30: *u8 = sys_mmap(11) 148 label_30[0]=0x73 as u8; label_30[1]=0x63 as u8; label_30[2]=0x61 as u8 149 label_30[3]=0x6C as u8; label_30[4]=0x65 as u8; label_30[5]=0x5F as u8 150 label_30[6]=0x76 as u8; label_30[7]=0x32 as u8; label_30[8]=0x5F as u8 151 label_30[9]=0x33 as u8; label_30[10]=0x30 as u8 152 153 let label_100: *u8 = sys_mmap(12) 154 label_100[0]=0x73 as u8; label_100[1]=0x63 as u8; label_100[2]=0x61 as u8 155 label_100[3]=0x6C as u8; label_100[4]=0x65 as u8; label_100[5]=0x5F as u8 156 label_100[6]=0x76 as u8; label_100[7]=0x32 as u8; label_100[8]=0x5F as u8 157 label_100[9]=0x31 as u8; label_100[10]=0x30 as u8; label_100[11]=0x30 as u8 158 159 // ===== Three independent scales ===== 160 let v10: nx_int = _run_scale(10, 0xA001, label_10, 11, buf, 512, now) 161 if v10 != 0 { return 10 } 162 let v30: nx_int = _run_scale(30, 0xA002, label_30, 11, buf, 512, now) 163 if v30 != 0 { return 30 } 164 let v100: nx_int = _run_scale(100, 0xA003, label_100, 12, buf, 512, now) 165 if v100 != 0 { return 100 } 166 167 return 0 168}