code wiki / (root) / nx_select.nx

nx_select.nx source

↩ module page · 290 lines · 11317 B

1// nx_select.nx -- single-class variant selector engine. 2// 3// SA-4 milestone of NISHI_SELF_ASSEMBLY_ROADMAP.md. Consumes 4// (candidates, probe, calibration, policy) and returns the index of 5// the best-match variant under the policy vector subject to the 6// candidate's requires_* constraints intersecting the device's probe. 7// 8// Selection function (single-class, MVP): 9// 10// S(C, P, K, π) = argmin_{v ∈ candidates} 11// Σ_axis π[axis] · cost_axis(v) · K[axis_norm] 12// s.t. requires_isa(v) ⊆ P.actual_isa_family 13// requires_isa_ext(v) ⊆ P.actual_isa_ext 14// requires_ram_min(v) ≤ P.ram_proxy 15// tier_floor(v) ≤ K.inferred_tier 16// tier_ceiling(v) ≥ K.inferred_tier 17// 18// Joint-DAG cross-primitive selection deferred to SA-5 19// (nx_select_joint). Per-primitive smoke gating deferred to SA-6 20// (nx_verify). Byzantine N-of-M deferred to SA-7. 21// 22// V1 manifest schema is INTEGER-ENCODED (sealed-enum bitmasks for 23// requires_isa, Q10 fixed-point for cost coefficients) so the 24// selector runs without a manifest-header parser. SA-5 lands the 25// parser that lifts authored headers in `runtime/*.nx` into these 26// integer-encoded records. 27// 28// genealogy_id: petabricks_2009 + halide_schedule_search_2019 + 29// cardinal_2026-05-19_self_assembly 30// lineage_id: substrate_select_v1 31// 32// nx_capability_manifest: 33// variant_class: capability_select 34// variant_id: capability_select_v1_single_class 35// requires_isa: [rv64imac, x86_64] 36// requires_syscalls: [mmap] 37// requires_ram_min_b: 4096 38// tier_floor: NX_TIER_MOBILE 39// tier_ceiling: NX_TIER_HPC 40// cost_model: 41// flops_per_n: 1.0 // ~constant per candidate 42// bytes_per_n: 160.0 // NxManifest size per candidate 43// syscalls_per_n: 0.0 44// adversary_class: THREAT_AI_ADVERSARY 45// 46// nx_safety_envelope: 47// intended_use: "Single-class variant selector; pure function of 48// (manifests, probe, calibration, policy)" 49// sil_target: SIL2 50// evidence: [pure_function, deterministic_argmin, 51// filter_then_score, no_side_effects] 52// verdict: NOT_YET_EVALUATED 53 54import "nx_syscalls.nx" 55import "nx_probe.nx" 56import "nx_calibrate.nx" 57const NX_MAGIC_1024: i64 = 1024 58const NX_MAGIC_9223372036854775000: i64 = 9223372036854775000 59 60// ===== ISA bitmask ================================================= 61// Maps NX_ISA_* sealed-enum values into bit positions so a single 62// i64 mask can express "this variant runs on RV64 OR x86_64". 63// requires_isa_mask = (1 << NX_ISA_RV64) | (1 << NX_ISA_X86_64) 64func nx_isa_bit(isa: i64) -> i64 { 65 if isa < 0 { return 0 } 66 if isa >= 63 { return 0 } 67 return 1 << isa 68} 69 70// ===== Data layout sealed enum (for joint-DAG composition) ======= 71// SA-5 extension. A variant declares which input layout it CAN 72// CONSUME (in_layout) and which output layout it PRODUCES (out_layout). 73// nx_select_joint's edge constraint requires out_layout(producer) to 74// match in_layout(consumer). 75// 76// NX_LAYOUT_OPAQUE_PASS matches anything (used by variants that 77// don't care about layout, e.g., byte-stream consumers). 78const NX_LAYOUT_OPAQUE_PASS: i64 = 0 79const NX_LAYOUT_ROW_MAJOR: i64 = 1 80const NX_LAYOUT_COL_MAJOR: i64 = 2 81const NX_LAYOUT_I64_PACKED: i64 = 3 82const NX_LAYOUT_I32_PACKED: i64 = 4 83const NX_LAYOUT_I8_PACKED: i64 = 5 84const NX_LAYOUT_Q10_FIXED: i64 = 6 85const NX_LAYOUT_F16: i64 = 7 86const NX_LAYOUT_F32: i64 = 8 87const NX_LAYOUT_N: i64 = 9 88 89// Layout compatibility: 1 if producer's out_layout can flow into 90// consumer's in_layout without transform; 0 otherwise. OPAQUE_PASS 91// is a wildcard on either side. 92func nx_layout_compatible(out_layout: i64, in_layout: i64) -> i64 { 93 if out_layout == NX_LAYOUT_OPAQUE_PASS { return 1 } 94 if in_layout == NX_LAYOUT_OPAQUE_PASS { return 1 } 95 if out_layout == in_layout { return 1 } 96 return 0 97} 98 99// ===== ISA-extension bitmask ====================================== 100const NX_ISA_EXT_AVX2: i64 = 0 101const NX_ISA_EXT_AVX512: i64 = 1 102const NX_ISA_EXT_BMI2: i64 = 2 103const NX_ISA_EXT_SHA_NI: i64 = 3 104const NX_ISA_EXT_AES_NI: i64 = 4 105const NX_ISA_EXT_NEON: i64 = 5 106const NX_ISA_EXT_SVE: i64 = 6 107const NX_ISA_EXT_RVV: i64 = 7 108const NX_ISA_EXT_N: i64 = 8 109 110// ===== NxManifest (selector-consumable form) ====================== 111// Subset of the source-header schema (NISHI_MANIFEST_AUTHOR_GUIDE.md) 112// reduced to integer fields the selector consumes without a parser. 113// Cost coefficients are Q10 fixed-point (multiplied by 1024 at 114// authoring time) so the selector can compute in integer arithmetic. 115 116struct NxManifest { 117 variant_class: i64, 118 variant_id: i64, 119 requires_isa_mask: i64, // OR of (1 << NX_ISA_*) 120 requires_isa_ext_mask: i64, // OR of (1 << NX_ISA_EXT_*) 121 requires_ram_min_b: i64, 122 tier_floor: i64, // NX_TIER_INF_* 123 tier_ceiling: i64, // NX_TIER_INF_* 124 cost_flops_per_n_q10: i64, // Q10 fixed-point 125 cost_bytes_per_n_q10: i64, 126 cost_syscalls_per_n_q10: i64, 127 cost_energy_per_n_q10: i64, 128 in_layout: i64, // NX_LAYOUT_* (joint-DAG SA-5) 129 out_layout: i64, // NX_LAYOUT_* 130} 131 132// ===== NxPolicy =================================================== 133// Policy weights -- non-negative integers, larger = more important. 134// All in Q10 fixed-point so weights compose with cost coefficients 135// without floating-point. 136 137struct NxPolicy { 138 weight_throughput: i64, // weight on flops_per_n 139 weight_bytes: i64, // weight on bytes_per_n 140 weight_syscalls: i64, // weight on syscalls_per_n 141 weight_energy: i64, // weight on energy_per_n 142 weight_ram: i64, // weight on requires_ram_min_b 143} 144 145// ===== Convenience constructors =================================== 146func nx_policy_throughput_default() -> *NxPolicy { 147 let p: *NxPolicy = (sys_mmap(48)) as *NxPolicy 148 p.weight_throughput = NX_MAGIC_1024 // 1.0 in Q10 149 p.weight_bytes = 256 // 0.25 150 p.weight_syscalls = 256 151 p.weight_energy = 128 // 0.125 152 p.weight_ram = 128 153 return p 154} 155 156func nx_policy_energy_default() -> *NxPolicy { 157 let p: *NxPolicy = (sys_mmap(48)) as *NxPolicy 158 p.weight_throughput = 128 // 0.125 (de-prioritized) 159 p.weight_bytes = 256 160 p.weight_syscalls = 256 161 p.weight_energy = NX_MAGIC_1024 // 1.0 162 p.weight_ram = 256 163 return p 164} 165 166func nx_manifest_new( 167 variant_class: i64, 168 variant_id: i64, 169 requires_isa_mask: i64, 170 requires_isa_ext_mask: i64, 171 requires_ram_min_b: i64, 172 tier_floor: i64, 173 tier_ceiling: i64, 174 cost_flops_q10: i64, 175 cost_bytes_q10: i64, 176 cost_syscalls_q10: i64, 177 cost_energy_q10: i64 178) -> *NxManifest { 179 let m: *NxManifest = (sys_mmap(128)) as *NxManifest 180 m.variant_class = variant_class 181 m.variant_id = variant_id 182 m.requires_isa_mask = requires_isa_mask 183 m.requires_isa_ext_mask = requires_isa_ext_mask 184 m.requires_ram_min_b = requires_ram_min_b 185 m.tier_floor = tier_floor 186 m.tier_ceiling = tier_ceiling 187 m.cost_flops_per_n_q10 = cost_flops_q10 188 m.cost_bytes_per_n_q10 = cost_bytes_q10 189 m.cost_syscalls_per_n_q10 = cost_syscalls_q10 190 m.cost_energy_per_n_q10 = cost_energy_q10 191 m.in_layout = NX_LAYOUT_OPAQUE_PASS 192 m.out_layout = NX_LAYOUT_OPAQUE_PASS 193 return m 194} 195 196// Convenience: set layout on an existing manifest (single-class 197// selector ignores these; joint selector consumes them). 198func nx_manifest_set_layout(m: *NxManifest, in_layout: i64, out_layout: i64) -> i64 { 199 m.in_layout = in_layout 200 m.out_layout = out_layout 201 return 0 202} 203 204// ===== Sealed enum: selection verdict ============================= 205const NX_SEL_NONE_MATCH: i64 = -1 206const NX_SEL_BAD_INPUT: i64 = -2 207const NX_SEL_OVER_CAPACITY: i64 = -3 208 209// ===== Filter: does this variant fit the device? ================== 210// Returns 1 if all requires-clauses are satisfied; 0 otherwise. 211 212func nx_select_variant_fits( 213 v: *NxManifest, 214 probe: *NxProbeRecord, 215 inferred_tier: i64, 216 probe_ram_proxy: i64, 217 probe_isa_ext_mask: i64 218) -> i64 { 219 // ISA filter. 220 let probe_isa_bit: i64 = nx_isa_bit(probe.actual_isa_family) 221 if (v.requires_isa_mask & probe_isa_bit) == 0 { return 0 } 222 // ISA-extension filter: every requires bit must be present in probe. 223 if (v.requires_isa_ext_mask & probe_isa_ext_mask) != v.requires_isa_ext_mask { 224 return 0 225 } 226 // RAM floor. 227 if probe_ram_proxy < v.requires_ram_min_b { return 0 } 228 // Tier floor + ceiling. 229 if inferred_tier < v.tier_floor { return 0 } 230 if inferred_tier > v.tier_ceiling { return 0 } 231 return 1 232} 233 234// ===== Scoring ==================================================== 235// Q10 weighted sum. Lower score = better. All Q10 multiplications 236// stay within i64 range as long as individual factors stay below ~2^30, 237// which they do for any realistic cost-model coefficient. 238 239func nx_select_score(v: *NxManifest, policy: *NxPolicy) -> i64 { 240 let s_t: i64 = policy.weight_throughput * v.cost_flops_per_n_q10 241 let s_b: i64 = policy.weight_bytes * v.cost_bytes_per_n_q10 242 let s_s: i64 = policy.weight_syscalls * v.cost_syscalls_per_n_q10 243 let s_e: i64 = policy.weight_energy * v.cost_energy_per_n_q10 244 let s_r: i64 = policy.weight_ram * v.requires_ram_min_b 245 return s_t + s_b + s_s + s_e + s_r 246} 247 248// ===== Single-class selection ==================================== 249// Pure-function MVP: scans candidates, filters by requires, scores 250// the survivors, returns argmin. Returns NX_SEL_NONE_MATCH (-1) if 251// no candidate fits the device. 252// 253// `manifest_ptrs` is a *i64 array where each element holds a 254// (i64-cast) pointer to an NxManifest. Caller pre-populates the 255// array; selector reads the pointers via *i64 indexing (the 256// substrate's clean array convention). Re-entrant; no global state. 257 258func nx_select( 259 manifest_ptrs: *i64, n_candidates: i64, 260 probe: *NxProbeRecord, 261 inferred_tier: i64, 262 probe_ram_proxy: i64, 263 probe_isa_ext_mask: i64, 264 policy: *NxPolicy 265) -> i64 { 266 if n_candidates <= 0 { return NX_SEL_BAD_INPUT } 267 if (probe as i64) == 0 { return NX_SEL_BAD_INPUT } 268 if (policy as i64) == 0 { return NX_SEL_BAD_INPUT } 269 if (manifest_ptrs as i64) == 0 { return NX_SEL_BAD_INPUT } 270 271 var best_idx: i64 = NX_SEL_NONE_MATCH 272 var best_score: i64 = NX_MAGIC_9223372036854775000 // close to MAX_I64 273 var i: i64 = 0 274 while i < n_candidates { 275 let v_addr: i64 = manifest_ptrs[i] 276 if v_addr == 0 { i = i + 1; continue } 277 let v: *NxManifest = v_addr as *NxManifest 278 let fits: i64 = nx_select_variant_fits(v, probe, inferred_tier, 279 probe_ram_proxy, probe_isa_ext_mask) 280 if fits == 1 { 281 let s: i64 = nx_select_score(v, policy) 282 if s < best_score { 283 best_score = s 284 best_idx = i 285 } 286 } 287 i = i + 1 288 } 289 return best_idx 290}