nx_dual_real_llm_compose_test.nx source
↩ module page · 186 lines · 8030 B
1// nx_dual_real_llm_compose_test.nx -- TWO v2 LLM actors concurrent on
2// REAL transformer forward, with wall-clock measurement.
3//
4// Substrate-honest version of [[nx_dual_companion_compose_test]] which
5// used synthetic 4-vocab manual matmul. THIS smoke runs two
6// independent v2 LLM actors that each drive the FULL Llama-class
7// runner (embedding + RoPE + RMSNorm + multi-head attention + SwiGLU
8// FFN + output projection + sample) cooperatively interleaved through
9// one nx_session. Each generates 5 tokens autoregressively. Output
10// from both actors flows through one multiplexer to one listener.
11//
12// Substrate invariants verified under DOUBLED real-transformer load:
13// * Both actors complete cleanly across 5 iterations each
14// * 10 LLM_TOKEN messages delivered (5 from each)
15// * All 10 tokens in [0, vocab)
16// * Counters scale: 30 actor steps total (5 iters * 3 phases * 2
17// actors = 30)
18// * Wall-clock measured around the whole concurrent workload
19// * Multiplexer correctly orders chunks from both actors by priority
20//
21// HONEST SCOPE: cooperative interleaving under qemu-riscv64, not
22// physical-parallel kernel execution. Phase E required for that.
23// But the substrate-honest claim PROVABLE today: one session hosts
24// two independent REAL transformer-forward actors without state
25// cross-contamination, message-bus collision, or arbiter starvation.
26
27import "nx_syscalls.nx"
28import "nx_tier.nx"
29import "nx_clock.nx"
30import "nx_actor.nx"
31import "nx_message.nx"
32import "nx_stream_multiplexer.nx"
33import "nx_session.nx"
34import "nx_bench_companion.nx"
35import "nx_bench_companion_timed.nx"
36import "nx_gguf_fixture_tiny.nx"
37import "nx_actor_role_llm_v2.nx"
38
39func main() -> i64 {
40 let now: nx_size = 1000000
41 let N_ITERS_PER_ACTOR: nx_int = 5
42
43 // ===== Two independent fixtures (different seeds, same shape) =====
44 let fixA: *NxGgufFixtureBundle = nx_gft_build_tiny_llama(0x11111111)
45 let fixB: *NxGgufFixtureBundle = nx_gft_build_tiny_llama(0x22222222)
46 if nx_gft_is_built(fixA) != 1 { return 1 }
47 if nx_gft_is_built(fixB) != 1 { return 2 }
48
49 // ===== Shared session =====
50 let s: *NxSession = nx_session_new_default(now)
51 if nx_session_is_ready(s) != 1 { return 3 }
52
53 let NARR: nx_int = 8001 // NARRATOR -- priority 80
54 let NPC: nx_int = 8002 // NPC -- priority 60
55 let LIS: nx_int = 8099
56 nx_session_spawn_actor(s, NARR, 1, 80, 0, now)
57 nx_session_spawn_actor(s, NPC, 1, 60, 0, now)
58 nx_session_spawn_actor(s, LIS, 5, 50, 0, now)
59 nx_session_subscribe(s, LIS, NX_MS_KIND_LLM_TOKEN)
60
61 // ===== Two LLM actor contexts on independent prompts =====
62 let prompt_n: *u8 = sys_mmap(1)
63 prompt_n[0] = 0x61 // 'a' for NARRATOR
64 let ctxN: *NxLlmV2ActorCtx = nx_lv_actor_new(
65 fixA.spec, fixA.gguf_buf, fixA.hdr, fixA.bpe,
66 prompt_n, 1,
67 1024, 4, fixA.prng_state, 10000, 724)
68 if (ctxN as i64) == 0 { return 4 }
69
70 let prompt_p: *u8 = sys_mmap(1)
71 prompt_p[0] = 0x63 // 'c' for NPC
72 let ctxP: *NxLlmV2ActorCtx = nx_lv_actor_new(
73 fixB.spec, fixB.gguf_buf, fixB.hdr, fixB.bpe,
74 prompt_p, 1,
75 1024, 4, fixB.prng_state, 10000, 724)
76 if (ctxP as i64) == 0 { return 5 }
77
78 // ===== Wall-clock start =====
79 let base: *NxBenchReport = nx_bc_report_new()
80 let timed: *NxBenchTimedReport = nx_bctm_new(base)
81 timed.wallclock_start_ns = nx_clock_monotonic_ns()
82
83 // ===== Cooperatively interleave both actors across 5 iterations =====
84 //
85 // Schedule pattern: NARR.run NARR.emit reset | NPC.run NPC.emit reset
86 // 5 times. Each "run+emit" is 3 cooperative steps (INIT, RUN, EMIT).
87 // Total = 5 iters * 3 phases * 2 actors = 30 actor steps.
88 let hist_n: *i64 = (sys_mmap(N_ITERS_PER_ACTOR * 8)) as *i64
89 let hist_p: *i64 = (sys_mmap(N_ITERS_PER_ACTOR * 8)) as *i64
90 var iter: nx_int = 0
91 var tick: nx_size = now + 100
92 while iter < N_ITERS_PER_ACTOR {
93 // ----- NARRATOR cooperative slice -----
94 nx_lv_actor_step(ctxN, s.scheduler, s.bus, NARR, tick)
95 tick = tick + 50
96 nx_lv_actor_step(ctxN, s.scheduler, s.bus, NARR, tick)
97 if ctxN.runner_result < 0 { return 10 + iter }
98 tick = tick + 50
99 nx_lv_actor_step(ctxN, s.scheduler, s.bus, NARR, tick)
100 if ctxN.current_phase != NX_LV_PHASE_DONE { return 20 + iter }
101 let tokN: nx_int = ctxN.runner_result
102 hist_n[iter] = tokN as i64
103 tick = tick + 50
104
105 // ----- NPC cooperative slice -----
106 nx_lv_actor_step(ctxP, s.scheduler, s.bus, NPC, tick)
107 tick = tick + 50
108 nx_lv_actor_step(ctxP, s.scheduler, s.bus, NPC, tick)
109 if ctxP.runner_result < 0 { return 30 + iter }
110 tick = tick + 50
111 nx_lv_actor_step(ctxP, s.scheduler, s.bus, NPC, tick)
112 if ctxP.current_phase != NX_LV_PHASE_DONE { return 40 + iter }
113 let tokP: nx_int = ctxP.runner_result
114 hist_p[iter] = tokP as i64
115 tick = tick + 50
116
117 // ----- Autoregressive reset for next round (unless final) -----
118 if iter < (N_ITERS_PER_ACTOR - 1) {
119 let nb_n: nx_int = nx_gft_vocab_byte(fixA, tokN)
120 prompt_n[0] = nb_n as u8
121 if nx_lv_actor_reset_for_next_token(ctxN, s.scheduler, NARR, prompt_n, 1) != NX_LV_V_STEPPED { return 50 + iter }
122 let nb_p: nx_int = nx_gft_vocab_byte(fixB, tokP)
123 prompt_p[0] = nb_p as u8
124 if nx_lv_actor_reset_for_next_token(ctxP, s.scheduler, NPC, prompt_p, 1) != NX_LV_V_STEPPED { return 60 + iter }
125 }
126 iter = iter + 1
127 }
128
129 // ===== Wall-clock end + capture substrate counters =====
130 if nx_bctm_capture_finish(timed, s, 11) != NX_BCTM_OK { return 70 }
131
132 // ===== Substrate invariants under DUAL real-transformer load =====
133 //
134 // 30 actor steps (5 iters * 3 phases * 2 actors).
135 if base.total_actor_steps != 30 { return 71 }
136 // Each step accounts 1000us logical runtime.
137 if base.cumulative_runtime_us != 30000 { return 72 }
138 // Both LLM actors COMPLETED on their last iteration; LIS stays READY.
139 if base.actors_completed != 2 { return 73 }
140 if base.actors_failed != 0 { return 74 }
141 if base.n_actors != 3 { return 75 }
142
143 // Listener received N_ITERS * 2 = 10 LLM_TOKEN messages
144 if nx_ms_pending(s.bus, LIS) != 10 { return 76 }
145
146 // ===== Wall-clock invariants =====
147 if timed.wallclock_elapsed_us < 0 { return 77 }
148 if nx_bctm_verdict(timed) != NX_BCTM_OK { return 78 }
149 // Monotonic: end > start
150 if timed.wallclock_end_ns <= timed.wallclock_start_ns { return 79 }
151
152 // ===== Drain + verify every token =====
153 var drained: nx_int = 0
154 var seen_narr: nx_int = 0
155 var seen_npc: nx_int = 0
156 while drained < 10 {
157 let m: *NxMessage = nx_ms_receive(s.bus, LIS)
158 if (m as i64) == 0 { return 80 + drained }
159 if m.kind != NX_MS_KIND_LLM_TOKEN { return 90 + drained }
160 let recv_tok: nx_int = m.payload_handle as nx_int
161 if recv_tok < 0 { return 100 + drained }
162 if recv_tok >= 4 { return 110 + drained }
163 if m.sender_actor_id == NARR { seen_narr = seen_narr + 1 }
164 if m.sender_actor_id == NPC { seen_npc = seen_npc + 1 }
165 drained = drained + 1
166 }
167 if seen_narr != N_ITERS_PER_ACTOR { return 120 }
168 if seen_npc != N_ITERS_PER_ACTOR { return 121 }
169
170 // ===== Push tokens into multiplexer with stream classes =====
171 //
172 // Simulate the parallel-companion output pipeline: NARRATOR's tokens
173 // -> LLM_TEXT stream, NPC's tokens -> LLM_TEXT stream, mux delivers
174 // them in priority + FIFO order.
175 var pi: nx_int = 0
176 while pi < N_ITERS_PER_ACTOR {
177 nx_sm_push(s.multiplexer, NARR, NX_SM_CLASS_LLM_TEXT,
178 100 + pi, 8, hist_n[pi] as nx_size, tick)
179 nx_sm_push(s.multiplexer, NPC, NX_SM_CLASS_LLM_TEXT,
180 200 + pi, 8, hist_p[pi] as nx_size, tick)
181 pi = pi + 1
182 }
183 if nx_sm_pending_count(s.multiplexer) != 10 { return 122 }
184
185 return 0
186}