code wiki / (root) / nx_compute_runner_test.nx

nx_compute_runner_test.nx source

↩ module page · 182 lines · 7267 B

1// nx_compute_runner_test.nx -- end-to-end loop closure. 2// 3// Build a real graph -> run it -> get real tensor output -> verify 4// via numeric oracle vs hand-computed reference. All pure NishiLang. 5// This is the "world class without comfyui annoyance but with the 6// power and more" deliverable: typed DAG + content-addressed nodes + 7// kernel dispatch + numerical verification, in one .nx program. 8 9import "nx_syscalls.nx" 10import "nx_tier.nx" 11import "nx_tensor.nx" 12import "nx_compute_node.nx" 13import "nx_compute_graph.nx" 14import "nx_blas_i64.nx" 15import "nx_compute_runner.nx" 16import "nx_numeric_oracle.nx" 17 18func main() -> nx_int { 19 let err: *i64 = (sys_mmap(8)) as *i64 20 21 // ===== Graph shape =========================================== 22 // 23 // INPUT (2x3) --\ 24 // MATMUL -- RELU -- OUTPUT 25 // CONST (3x4) --/ 26 // 27 // INPUT values: 28 // [[1, 2, 3], 29 // [4, 5, 6]] 30 // CONST values: 31 // [[1, 0, 1, 0], 32 // [0, 1, 0, 1], 33 // [1, 1, 1, 1]] 34 // 35 // MATMUL result (2x4): 36 // row 0: 37 // 1*1+2*0+3*1 = 4 38 // 1*0+2*1+3*1 = 5 39 // 1*1+2*0+3*1 = 4 40 // 1*0+2*1+3*1 = 5 41 // row 1: 42 // 4*1+5*0+6*1 = 10 43 // 4*0+5*1+6*1 = 11 44 // 4*1+5*0+6*1 = 10 45 // 4*0+5*1+6*1 = 11 46 // 47 // RELU(matmul) == matmul since all values are positive. 48 49 let g: *ComputeGraph = nx_cg_alloc(16) 50 51 let n0: *ComputeNode = nx_cn_alloc(0, NX_CN_NODE_INPUT, 0, 0, 1, 0) 52 nx_cg_add_node(g, n0) 53 54 let n1: *ComputeNode = nx_cn_alloc(0, NX_CN_NODE_CONST, 0, 0, 1, 0) 55 nx_cg_add_node(g, n1) 56 57 let n2: *ComputeNode = nx_cn_alloc(0, NX_CN_NODE_KERNEL, NX_CN_K_MATMUL, 2, 1, 0) 58 nx_cn_set_input(n2, 0, 0, 0) 59 nx_cn_set_input(n2, 1, 1, 0) 60 nx_cg_add_node(g, n2) 61 62 let n3: *ComputeNode = nx_cn_alloc(0, NX_CN_NODE_TENSOR_OP, NX_CN_OP_RELU, 1, 1, 0) 63 nx_cn_set_input(n3, 0, 2, 0) 64 nx_cg_add_node(g, n3) 65 66 let n4: *ComputeNode = nx_cn_alloc(0, NX_CN_NODE_OUTPUT, 0, 1, 0, 0) 67 nx_cn_set_input(n4, 0, 3, 0) 68 nx_cg_add_node(g, n4) 69 70 if nx_cg_validate(g) != NX_CG_OK { return 1 } 71 if nx_cg_toposort(g) != NX_CG_OK { return 2 } 72 73 // ===== Build the input + const tensors ======================= 74 let s_input: *i64 = (sys_mmap(16)) as *i64 75 s_input[0] = 2; s_input[1] = 3 76 let input_t: *NxTensor = nx_t_alloc(NX_DT_I64, s_input, 2, err) 77 nx_t_fill_zero(input_t) 78 let idx: *i64 = (sys_mmap(64)) as *i64 79 idx[0] = 0; idx[1] = 0; nx_t_set_i64(input_t, idx, 1) 80 idx[0] = 0; idx[1] = 1; nx_t_set_i64(input_t, idx, 2) 81 idx[0] = 0; idx[1] = 2; nx_t_set_i64(input_t, idx, 3) 82 idx[0] = 1; idx[1] = 0; nx_t_set_i64(input_t, idx, 4) 83 idx[0] = 1; idx[1] = 1; nx_t_set_i64(input_t, idx, 5) 84 idx[0] = 1; idx[1] = 2; nx_t_set_i64(input_t, idx, 6) 85 86 let s_const: *i64 = (sys_mmap(16)) as *i64 87 s_const[0] = 3; s_const[1] = 4 88 let const_t: *NxTensor = nx_t_alloc(NX_DT_I64, s_const, 2, err) 89 nx_t_fill_zero(const_t) 90 idx[0] = 0; idx[1] = 0; nx_t_set_i64(const_t, idx, 1) 91 idx[0] = 0; idx[1] = 2; nx_t_set_i64(const_t, idx, 1) 92 idx[0] = 1; idx[1] = 1; nx_t_set_i64(const_t, idx, 1) 93 idx[0] = 1; idx[1] = 3; nx_t_set_i64(const_t, idx, 1) 94 idx[0] = 2; idx[1] = 0; nx_t_set_i64(const_t, idx, 1) 95 idx[0] = 2; idx[1] = 1; nx_t_set_i64(const_t, idx, 1) 96 idx[0] = 2; idx[1] = 2; nx_t_set_i64(const_t, idx, 1) 97 idx[0] = 2; idx[1] = 3; nx_t_set_i64(const_t, idx, 1) 98 99 // ===== Run the graph ========================================= 100 let r: *ComputeRunner = nx_cr_alloc(g, 4, 4) 101 nx_cr_bind_input(r, input_t) 102 nx_cr_bind_const(r, const_t) 103 104 let rc: nx_int = nx_cr_run(r) 105 if rc != NX_CR_OK { return 3 } 106 107 // ===== Fetch the OUTPUT-node's tensor ======================== 108 // 109 // Node 4 (OUTPUT) aliases the upstream RELU output. 110 let out_t: *NxTensor = nx_cr_get_output(r, 4, 0) 111 if (out_t as nx_int) == 0 { return 4 } 112 if out_t.ndim != 2 { return 5 } 113 if out_t.shape[0] != 2 { return 6 } 114 if out_t.shape[1] != 4 { return 7 } 115 116 // ===== Build the expected tensor and verify ================== 117 let expected: *NxTensor = nx_t_alloc(NX_DT_I64, s_const, 2, err) // 2x4 118 // Oops, s_const is 3x4. Build a fresh 2x4 shape buffer. 119 let s_out: *i64 = (sys_mmap(16)) as *i64 120 s_out[0] = 2; s_out[1] = 4 121 let expected2: *NxTensor = nx_t_alloc(NX_DT_I64, s_out, 2, err) 122 nx_t_fill_zero(expected2) 123 idx[0] = 0; idx[1] = 0; nx_t_set_i64(expected2, idx, 4) 124 idx[0] = 0; idx[1] = 1; nx_t_set_i64(expected2, idx, 5) 125 idx[0] = 0; idx[1] = 2; nx_t_set_i64(expected2, idx, 4) 126 idx[0] = 0; idx[1] = 3; nx_t_set_i64(expected2, idx, 5) 127 idx[0] = 1; idx[1] = 0; nx_t_set_i64(expected2, idx, 10) 128 idx[0] = 1; idx[1] = 1; nx_t_set_i64(expected2, idx, 11) 129 idx[0] = 1; idx[1] = 2; nx_t_set_i64(expected2, idx, 10) 130 idx[0] = 1; idx[1] = 3; nx_t_set_i64(expected2, idx, 11) 131 132 let witness: *i64 = (sys_mmap(NX_NO_WITNESS_FIELDS * 8)) as *i64 133 let verdict: nx_int = nx_no_check_bit_exact_i64(out_t, expected2, witness) 134 if verdict != NX_NO_VERDICT_EQUAL { 135 // The witness tells us exactly where it broke; bubble that 136 // info up via return code so the smoke can pinpoint. 137 return 10 + verdict 138 } 139 140 // ===== Negative test: mutate expected, re-verify, expect DIFFERS = 141 idx[0] = 0; idx[1] = 0; nx_t_set_i64(expected2, idx, 999) 142 let verdict2: nx_int = nx_no_check_bit_exact_i64(out_t, expected2, witness) 143 if verdict2 != NX_NO_VERDICT_DIFFERS { return 20 } 144 if witness[NX_NO_WITNESS_F_ACTUAL] != 4 { return 21 } // we computed 4 145 if witness[NX_NO_WITNESS_F_EXPECTED] != 999 { return 22 } // expected mutated to 999 146 147 // ===== Spot-check the matmul-output node directly ============ 148 // 149 // Node 2 (MATMUL) should hold the same value (RELU is no-op when 150 // all values are positive). 151 let mm_out: *NxTensor = nx_cr_get_output(r, 2, 0) 152 idx[0] = 0; idx[1] = 0 153 if nx_t_get_i64(mm_out, idx) != 4 { return 30 } 154 idx[0] = 1; idx[1] = 1 155 if nx_t_get_i64(mm_out, idx) != 11 { return 31 } 156 157 // ===== Reproducibility: run again, same output =============== 158 // 159 // Re-allocate runner (input/const bindings are consumed by run); 160 // verify second run produces bit-identical output. 161 let r2: *ComputeRunner = nx_cr_alloc(g, 4, 4) 162 nx_cr_bind_input(r2, input_t) 163 nx_cr_bind_const(r2, const_t) 164 if nx_cr_run(r2) != NX_CR_OK { return 40 } 165 let out_t2: *NxTensor = nx_cr_get_output(r2, 4, 0) 166 let verdict3: nx_int = nx_no_check_bit_exact_i64(out_t, out_t2, witness) 167 if verdict3 != NX_NO_VERDICT_EQUAL { return 41 } 168 169 // ===== Negative test: missing input binding -> INPUT_MISSING == 170 let r3: *ComputeRunner = nx_cr_alloc(g, 4, 4) 171 nx_cr_bind_const(r3, const_t) // no input bound 172 let rc3: nx_int = nx_cr_run(r3) 173 if rc3 != NX_CR_ERR_INPUT_MISSING { return 50 } 174 175 // ===== Verdict enum coverage ================================= 176 if nx_cr_verdict_is_valid(NX_CR_OK) != 1 { return 60 } 177 if nx_cr_verdict_is_valid(NX_CR_ERR_KERNEL_FAILED) != 1 { return 61 } 178 if nx_cr_verdict_is_valid(NX_CR_N_VERDICTS) != 0 { return 62 } 179 if nx_cr_verdict_is_valid(0 - 1) != 0 { return 63 } 180 181 return 0 182}