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}