code wiki / (root) / nx_actor_role_llm_v2.nx

nx_actor_role_llm_v2.nx source

↩ module page · 266 lines · 10430 B

1// nx_actor_role_llm_v2.nx -- REAL transformer-forward LLM actor. 2// 3// Where [[nx_actor_role_llm]] drives a tiny 3-step micro-pipeline 4// (embed + matmul + sample) over synthetic toy weights, THIS adapter 5// wraps the full Llama-class runner [[nx_llm_run_v2]] -- multi-layer 6// transformer forward including RoPE + RMSNorm + multi-head attention 7// + SwiGLU FFN + output projection + temperature + top_k + sample. 8// 9// Cooperative phases: 10// Phase 0 (INIT) : reserved 11// Phase 1 (RUN) : single call to nx_llm_generate_one_v2(spec, gguf, 12// hdr, bpe, prompt, ..., prng, rope_base, attn_scale) 13// -- this is the monolithic generate-one-token step 14// Phase 2 (EMIT) : fanout LLM_TOKEN with the generated token if 15// positive; else fanout ERROR_REPORT with verdict id 16// Phase 3 (DONE) : terminal 17// 18// V1 of this adapter is single-shot (one token per actor lifetime). 19// V2 will add a reset_for_next_token loop similar to nx_actor_role_llm. 20// 21// Composes [[project-conductor-arc-phases-c-d-integrate-with-nishi-ai 22// -2026-05-19]] -- the LLM substrate the other agent built becomes 23// callable from inside the parallel-companion actor system. 24 25import "nx_syscalls.nx" 26import "nx_tier.nx" 27import "nx_model_spec.nx" 28import "nx_gguf.nx" 29import "nx_bpe.nx" 30import "nx_llm_run_v2.nx" 31import "nx_actor.nx" 32import "nx_message.nx" 33 34// ===== Sealed enum: NxLlmV2ActorPhase ============================= 35 36const NX_LV_PHASE_INIT: nx_int = 0 37const NX_LV_PHASE_RUN: nx_int = 1 38const NX_LV_PHASE_EMIT: nx_int = 2 39const NX_LV_PHASE_DONE: nx_int = 3 40const NX_LV_PHASE_N: nx_int = 4 41 42// ===== Sealed enum: NxLlmV2ActorVerdict =========================== 43 44const NX_LV_V_STEPPED: nx_int = 0 45const NX_LV_V_COMPLETED: nx_int = 1 46const NX_LV_V_FAILED_RUN: nx_int = 2 47const NX_LV_V_INVALID: nx_int = 3 48const NX_LV_V_NULL: nx_int = 4 49const NX_LV_V_N: nx_int = 5 50 51// ===== Struct: NxLlmV2ActorCtx ==================================== 52// 53// All inputs caller-owned (spec / gguf / bpe / prompt). Adapter 54// stores the returned token + the underlying runner verdict for 55// caller inspection. 56 57struct NxLlmV2ActorCtx { 58 spec: *NxModelSpec, 59 gguf_buf: *u8, 60 hdr: *NxGgufHeader, 61 bpe: *NxBpeVocab, 62 prompt_text: *u8, 63 prompt_len: nx_int, 64 temperature_q10: nx_int, 65 top_k: nx_int, 66 prng_state: *i64, 67 rope_base: nx_int, 68 attn_scale_q10: nx_int, 69 runner_result: nx_int, // raw return: >=0 = token, <0 = -verdict 70 runner_verdict: nx_int, // decoded verdict (NX_LR2_OK on success) 71 current_phase: nx_int, 72 last_step_verdict: nx_int, 73} 74 75const NX_LV_CTX_BYTES: nx_int = 120 // 15 fields * 8 76 77// ===== Validators ================================================= 78 79func nx_lv_phase_is_valid(p: nx_int) -> nx_int { 80 if p < 0 { return 0 } 81 if p >= NX_LV_PHASE_N { return 0 } 82 return 1 83} 84 85func nx_lv_v_is_valid(v: nx_int) -> nx_int { 86 if v < 0 { return 0 } 87 if v >= NX_LV_V_N { return 0 } 88 return 1 89} 90 91// ===== Constructor ================================================ 92 93func nx_lv_actor_new(spec: *NxModelSpec, 94 gguf_buf: *u8, 95 hdr: *NxGgufHeader, 96 bpe: *NxBpeVocab, 97 prompt_text: *u8, 98 prompt_len: nx_int, 99 temperature_q10: nx_int, 100 top_k: nx_int, 101 prng_state: *i64, 102 rope_base: nx_int, 103 attn_scale_q10: nx_int) -> *NxLlmV2ActorCtx { 104 if (spec as i64) == 0 { return 0 as *NxLlmV2ActorCtx } 105 if (gguf_buf as i64) == 0 { return 0 as *NxLlmV2ActorCtx } 106 if (hdr as i64) == 0 { return 0 as *NxLlmV2ActorCtx } 107 if (bpe as i64) == 0 { return 0 as *NxLlmV2ActorCtx } 108 if (prompt_text as i64) == 0 { return 0 as *NxLlmV2ActorCtx } 109 if (prng_state as i64) == 0 { return 0 as *NxLlmV2ActorCtx } 110 if prompt_len <= 0 { return 0 as *NxLlmV2ActorCtx } 111 if temperature_q10 <= 0 { return 0 as *NxLlmV2ActorCtx } 112 if top_k <= 0 { return 0 as *NxLlmV2ActorCtx } 113 if rope_base <= 1 { return 0 as *NxLlmV2ActorCtx } 114 if attn_scale_q10 <= 0 { return 0 as *NxLlmV2ActorCtx } 115 let raw: *u8 = sys_mmap(NX_LV_CTX_BYTES) 116 let ctx: *NxLlmV2ActorCtx = raw as *NxLlmV2ActorCtx 117 ctx.spec = spec 118 ctx.gguf_buf = gguf_buf 119 ctx.hdr = hdr 120 ctx.bpe = bpe 121 ctx.prompt_text = prompt_text 122 ctx.prompt_len = prompt_len 123 ctx.temperature_q10 = temperature_q10 124 ctx.top_k = top_k 125 ctx.prng_state = prng_state 126 ctx.rope_base = rope_base 127 ctx.attn_scale_q10 = attn_scale_q10 128 ctx.runner_result = 0 129 ctx.runner_verdict = NX_LR2_OK 130 ctx.current_phase = NX_LV_PHASE_INIT 131 ctx.last_step_verdict = NX_LV_V_STEPPED 132 return ctx 133} 134 135// ===== Phase: RUN -- monolithic call into v2 runner =============== 136 137func _lv_step_run(ctx: *NxLlmV2ActorCtx) -> nx_int { 138 let r: nx_int = nx_llm_generate_one_v2( 139 ctx.spec, ctx.gguf_buf, ctx.hdr, ctx.bpe, 140 ctx.prompt_text, ctx.prompt_len, 141 ctx.temperature_q10, ctx.top_k, 142 ctx.prng_state, ctx.rope_base, ctx.attn_scale_q10) 143 ctx.runner_result = r 144 if r >= 0 { 145 ctx.runner_verdict = NX_LR2_OK 146 return NX_LV_V_STEPPED 147 } 148 // r < 0 means error verdict (encoded as 0 - verdict_id) 149 ctx.runner_verdict = 0 - r 150 return NX_LV_V_FAILED_RUN 151} 152 153// ===== Phase: EMIT ================================================ 154 155func _lv_step_emit(ctx: *NxLlmV2ActorCtx, 156 bus: *NxMessageBus, 157 sender_actor_id: nx_int, 158 now_us: nx_size) -> nx_int { 159 if (bus as i64) == 0 { return NX_LV_V_STEPPED } 160 if ctx.runner_result >= 0 { 161 // success: send LLM_TOKEN with token id 162 nx_ms_send_fanout(bus, sender_actor_id, NX_MS_KIND_LLM_TOKEN, 163 ctx.runner_result as nx_size, 8, now_us) 164 } 165 if ctx.runner_result < 0 { 166 // failure: send ERROR_REPORT with verdict id 167 nx_ms_send_fanout(bus, sender_actor_id, NX_MS_KIND_ERROR_REPORT, 168 ctx.runner_verdict as nx_size, 8, now_us) 169 } 170 return NX_LV_V_STEPPED 171} 172 173// ===== Public step ================================================ 174 175func nx_lv_actor_step(ctx: *NxLlmV2ActorCtx, 176 sched: *NxActorScheduler, 177 bus: *NxMessageBus, 178 actor_id: nx_int, 179 now_us: nx_size) -> nx_int { 180 if (ctx as i64) == 0 { return NX_LV_V_NULL } 181 if ctx.current_phase >= NX_LV_PHASE_DONE { return NX_LV_V_COMPLETED } 182 var v: nx_int = NX_LV_V_STEPPED 183 if ctx.current_phase == NX_LV_PHASE_INIT { v = NX_LV_V_STEPPED } 184 if ctx.current_phase == NX_LV_PHASE_RUN { v = _lv_step_run(ctx) } 185 if ctx.current_phase == NX_LV_PHASE_EMIT { v = _lv_step_emit(ctx, bus, actor_id, now_us) } 186 ctx.last_step_verdict = v 187 // Advance phase even if RUN failed -- caller still wants the EMIT 188 // path so the error gets reported through the message bus. 189 ctx.current_phase = ctx.current_phase + 1 190 if (sched as i64) != 0 { nx_ac_step(sched, actor_id, 1000, now_us) } 191 if ctx.current_phase >= NX_LV_PHASE_DONE { 192 if (sched as i64) != 0 { 193 if v == NX_LV_V_FAILED_RUN { 194 // FAIL the scheduler actor only if RUN failed -- caller can 195 // distinguish "completed with token" from "completed with error". 196 nx_ac_fail(sched, actor_id) 197 } 198 if v != NX_LV_V_FAILED_RUN { 199 nx_ac_complete(sched, actor_id) 200 } 201 } 202 return NX_LV_V_COMPLETED 203 } 204 return NX_LV_V_STEPPED 205} 206 207// ===== Autoregressive reset ======================================= 208// 209// Matches the v1 adapter's reset_for_next_token discipline. Caller 210// generated token N via this actor and wants token N+1 from the 211// same context, typically with an updated prompt that includes token 212// N's bytes. V2 of THIS reset takes a NEW prompt buffer + length 213// because the runner is monolithic (it doesn't expose KV cache to the 214// adapter), so each "next token" is a full re-tokenize + forward. 215// 216// Future v3 will compose with an adapter-level KV-cache primitive so 217// the reset can carry hidden state without re-running the prefix. 218// 219// Refuses if ctx is not in DONE state OR new prompt is null/empty. 220 221func nx_lv_actor_reset_for_next_token(ctx: *NxLlmV2ActorCtx, 222 sched: *NxActorScheduler, 223 actor_id: nx_int, 224 new_prompt: *u8, 225 new_prompt_len: nx_int) -> nx_int { 226 if (ctx as i64) == 0 { return NX_LV_V_NULL } 227 if ctx.current_phase < NX_LV_PHASE_DONE { return NX_LV_V_INVALID } 228 if (new_prompt as i64) == 0 { return NX_LV_V_INVALID } 229 if new_prompt_len <= 0 { return NX_LV_V_INVALID } 230 ctx.prompt_text = new_prompt 231 ctx.prompt_len = new_prompt_len 232 ctx.current_phase = NX_LV_PHASE_INIT 233 ctx.runner_result = 0 234 ctx.runner_verdict = NX_LR2_OK 235 ctx.last_step_verdict = NX_LV_V_STEPPED 236 // Restore scheduler actor from COMPLETED / FAILED back to READY. 237 if (sched as i64) != 0 { 238 let a: *NxActor = nx_ac_find(sched, actor_id) 239 if (a as i64) != 0 { a.state = NX_AC_STATE_READY } 240 } 241 return NX_LV_V_STEPPED 242} 243 244// ===== Accessors ================================================== 245 246func nx_lv_actor_phase(ctx: *NxLlmV2ActorCtx) -> nx_int { 247 if (ctx as i64) == 0 { return NX_LV_PHASE_DONE } 248 return ctx.current_phase 249} 250 251func nx_lv_actor_token(ctx: *NxLlmV2ActorCtx) -> nx_int { 252 if (ctx as i64) == 0 { return -1 } 253 if ctx.runner_result < 0 { return -1 } 254 return ctx.runner_result 255} 256 257func nx_lv_actor_runner_verdict(ctx: *NxLlmV2ActorCtx) -> nx_int { 258 if (ctx as i64) == 0 { return NX_LR2_ERR_BAD_SPEC } 259 return ctx.runner_verdict 260} 261 262func nx_lv_actor_is_done(ctx: *NxLlmV2ActorCtx) -> nx_int { 263 if (ctx as i64) == 0 { return 0 } 264 if ctx.current_phase >= NX_LV_PHASE_DONE { return 1 } 265 return 0 266}