code wiki / (root) / nx_resource_arbiter.nx

nx_resource_arbiter.nx source

↩ module page · 317 lines · 11092 B

1// nx_resource_arbiter.nx -- allocation under contention with priority + treaty. 2// 3// Per CARDINAL [[feedback-conductor-heterogeneous-compute-no-second-class- 4// resources]] + [[feedback-parallel-companion-multimodal-dnd-real-time]]: 5// when multiple cells want the same scarce resource (VRAM bytes, 6// CPU microseconds, NVMe iops), the arbiter decides who gets it based 7// on priority + active treaty + current allocation share. REFUSES the 8// "first-come-first-served leads to starvation of low-rate streams" and 9// "highest-priority-always-wins leads to background-cell death" failure 10// modes. 11// 12// V1 ships sealed resource enum + request enum + grant verdict + 13// per-cell allocation tracking + max-share fairness floor (no cell gets 14// >X% in a single epoch) + per-resource budget pool. 15 16import "nx_syscalls.nx" 17import "nx_tier.nx" 18 19// ===== Sealed enum: NxResourceKind ================================ 20 21const NX_RA_KIND_VRAM_BYTES: nx_int = 0 22const NX_RA_KIND_RAM_BYTES: nx_int = 1 23const NX_RA_KIND_DISK_BYTES: nx_int = 2 24const NX_RA_KIND_CPU_MICROSECONDS: nx_int = 3 25const NX_RA_KIND_GPU_MICROSECONDS: nx_int = 4 26const NX_RA_KIND_NETWORK_BYTES: nx_int = 5 27const NX_RA_KIND_FILE_HANDLES: nx_int = 6 28const NX_RA_KIND_N: nx_int = 7 29 30// ===== Sealed enum: NxGrantVerdict ================================ 31 32const NX_RA_V_GRANTED_FULL: nx_int = 0 33const NX_RA_V_GRANTED_PARTIAL: nx_int = 1 // less than requested 34const NX_RA_V_DENIED_NO_BUDGET: nx_int = 2 35const NX_RA_V_DENIED_MAX_SHARE: nx_int = 3 // would exceed fairness cap 36const NX_RA_V_DENIED_PREEMPTED: nx_int = 4 // higher-pri cell took it back 37const NX_RA_V_INVALID: nx_int = 5 38const NX_RA_V_NULL: nx_int = 6 39const NX_RA_V_N: nx_int = 7 40 41// ===== Struct: NxAllocation ======================================= 42 43struct NxAllocation { 44 cell_id: nx_int, 45 resource_kind: nx_int, 46 units_held: nx_size, 47 priority: nx_int, 48 granted_at_us: nx_size, 49 last_renewed_us: nx_size, 50 treaty_id: nx_int, 51} 52 53const NX_RA_A_BYTES: nx_int = 56 54 55struct NxResourceArbiter { 56 allocations: *u8, 57 n_allocations: nx_int, 58 capacity: nx_int, 59 // Per-resource budget (i64[NX_RA_KIND_N]) 60 budgets: *u8, 61 // Per-resource max-share-q10 (e.g. 717 = 70%) 62 max_share_q10: *u8, 63 grants_full: nx_int, 64 grants_partial: nx_int, 65 denials: nx_int, 66 preemptions: nx_int, 67} 68 69const NX_RA_BYTES: nx_int = 56 70 71// ===== Validators ================================================= 72 73func nx_ra_kind_is_valid(k: nx_int) -> nx_int { 74 if k < 0 { return 0 } 75 if k >= NX_RA_KIND_N { return 0 } 76 return 1 77} 78 79func nx_ra_v_is_valid(v: nx_int) -> nx_int { 80 if v < 0 { return 0 } 81 if v >= NX_RA_V_N { return 0 } 82 return 1 83} 84 85// ===== Constructor ================================================ 86 87func nx_ra_new(capacity: nx_int) -> *NxResourceArbiter { 88 if capacity <= 0 { return 0 as *NxResourceArbiter } 89 let raw: *u8 = sys_mmap(NX_RA_BYTES) 90 let a: *NxResourceArbiter = raw as *NxResourceArbiter 91 a.allocations = sys_mmap(capacity * NX_RA_A_BYTES) 92 a.n_allocations = 0 93 a.capacity = capacity 94 a.budgets = sys_mmap(NX_RA_KIND_N * 8) 95 a.max_share_q10 = sys_mmap(NX_RA_KIND_N * 8) 96 let bud: *i64 = a.budgets as *i64 97 let shr: *i64 = a.max_share_q10 as *i64 98 var i: nx_int = 0 99 while i < NX_RA_KIND_N { 100 bud[i] = 0 101 shr[i] = 717 // 70% default fairness ceiling 102 i = i + 1 103 } 104 a.grants_full = 0 105 a.grants_partial = 0 106 a.denials = 0 107 a.preemptions = 0 108 return a 109} 110 111func _ra_alloc_at(a: *NxResourceArbiter, idx: nx_int) -> *NxAllocation { 112 if idx < 0 { return 0 as *NxAllocation } 113 if idx >= a.n_allocations { return 0 as *NxAllocation } 114 let off: nx_int = idx * NX_RA_A_BYTES 115 return (a.allocations + off) as *NxAllocation 116} 117 118// ===== Budget config ============================================== 119 120func nx_ra_set_budget(a: *NxResourceArbiter, 121 resource_kind: nx_int, 122 budget: nx_size) -> nx_int { 123 if (a as i64) == 0 { return NX_RA_V_NULL } 124 if nx_ra_kind_is_valid(resource_kind) == 0 { return NX_RA_V_INVALID } 125 let bud: *i64 = a.budgets as *i64 126 bud[resource_kind] = budget as i64 127 return NX_RA_V_GRANTED_FULL 128} 129 130func nx_ra_set_max_share(a: *NxResourceArbiter, 131 resource_kind: nx_int, 132 max_share_q10: nx_int) -> nx_int { 133 if (a as i64) == 0 { return NX_RA_V_NULL } 134 if nx_ra_kind_is_valid(resource_kind) == 0 { return NX_RA_V_INVALID } 135 if max_share_q10 <= 0 { return NX_RA_V_INVALID } 136 if max_share_q10 > 1024 { return NX_RA_V_INVALID } 137 let shr: *i64 = a.max_share_q10 as *i64 138 shr[resource_kind] = max_share_q10 as i64 139 return NX_RA_V_GRANTED_FULL 140} 141 142func nx_ra_get_budget(a: *NxResourceArbiter, resource_kind: nx_int) -> nx_size { 143 if (a as i64) == 0 { return 0 } 144 if nx_ra_kind_is_valid(resource_kind) == 0 { return 0 } 145 let bud: *i64 = a.budgets as *i64 146 return bud[resource_kind] as nx_size 147} 148 149// ===== Aggregations =============================================== 150 151func nx_ra_total_held(a: *NxResourceArbiter, resource_kind: nx_int) -> nx_size { 152 if (a as i64) == 0 { return 0 } 153 var sum: nx_size = 0 154 var i: nx_int = 0 155 while i < a.n_allocations { 156 let al: *NxAllocation = _ra_alloc_at(a, i) 157 if al.resource_kind == resource_kind { sum = sum + al.units_held } 158 i = i + 1 159 } 160 return sum 161} 162 163func nx_ra_held_by_cell(a: *NxResourceArbiter, 164 cell_id: nx_int, 165 resource_kind: nx_int) -> nx_size { 166 if (a as i64) == 0 { return 0 } 167 var sum: nx_size = 0 168 var i: nx_int = 0 169 while i < a.n_allocations { 170 let al: *NxAllocation = _ra_alloc_at(a, i) 171 if al.cell_id == cell_id { 172 if al.resource_kind == resource_kind { sum = sum + al.units_held } 173 } 174 i = i + 1 175 } 176 return sum 177} 178 179// ===== Request resource =========================================== 180// 181// Returns verdict + writes actual units_granted via *out_granted (0 on 182// deny / null). Granted_partial when budget allows < requested. 183 184func nx_ra_request(a: *NxResourceArbiter, 185 cell_id: nx_int, 186 resource_kind: nx_int, 187 units: nx_size, 188 priority: nx_int, 189 treaty_id: nx_int, 190 out_granted: *i64, 191 now_us: nx_size) -> nx_int { 192 if (a as i64) == 0 { return NX_RA_V_NULL } 193 if (out_granted as i64) == 0 { return NX_RA_V_NULL } 194 if nx_ra_kind_is_valid(resource_kind) == 0 { return NX_RA_V_INVALID } 195 if cell_id == 0 { return NX_RA_V_INVALID } 196 if units == 0 { return NX_RA_V_INVALID } 197 out_granted[0] = 0 198 let bud: *i64 = a.budgets as *i64 199 let shr: *i64 = a.max_share_q10 as *i64 200 let budget: nx_size = bud[resource_kind] as nx_size 201 if budget == 0 { 202 a.denials = a.denials + 1 203 return NX_RA_V_DENIED_NO_BUDGET 204 } 205 let used: nx_size = nx_ra_total_held(a, resource_kind) 206 let free_pool: nx_size = budget - used 207 if used >= budget { 208 a.denials = a.denials + 1 209 return NX_RA_V_DENIED_NO_BUDGET 210 } 211 // Max-share check: cell already holds X, request would bring to Y; 212 // Y / budget must be <= max_share_q10 / 1024. 213 let cell_held: nx_size = nx_ra_held_by_cell(a, cell_id, resource_kind) 214 let new_total_for_cell: nx_size = cell_held + units 215 let cell_q10: nx_int = (((new_total_for_cell as nx_int) * 1024) / (budget as nx_int)) 216 let max_share: nx_int = shr[resource_kind] as nx_int 217 if cell_q10 > max_share { 218 a.denials = a.denials + 1 219 return NX_RA_V_DENIED_MAX_SHARE 220 } 221 // Grant what we can (full if free_pool >= units, else partial) 222 var to_grant: nx_size = units 223 if free_pool < units { to_grant = free_pool } 224 if a.n_allocations >= a.capacity { 225 a.denials = a.denials + 1 226 return NX_RA_V_DENIED_NO_BUDGET 227 } 228 let off: nx_int = a.n_allocations * NX_RA_A_BYTES 229 let al: *NxAllocation = (a.allocations + off) as *NxAllocation 230 al.cell_id = cell_id 231 al.resource_kind = resource_kind 232 al.units_held = to_grant 233 al.priority = priority 234 al.granted_at_us = now_us 235 al.last_renewed_us = now_us 236 al.treaty_id = treaty_id 237 a.n_allocations = a.n_allocations + 1 238 out_granted[0] = to_grant as i64 239 if to_grant == units { 240 a.grants_full = a.grants_full + 1 241 return NX_RA_V_GRANTED_FULL 242 } 243 a.grants_partial = a.grants_partial + 1 244 return NX_RA_V_GRANTED_PARTIAL 245} 246 247// ===== Release resource =========================================== 248 249func nx_ra_release(a: *NxResourceArbiter, 250 cell_id: nx_int, 251 resource_kind: nx_int) -> nx_size { 252 if (a as i64) == 0 { return 0 } 253 var released: nx_size = 0 254 var i: nx_int = 0 255 while i < a.n_allocations { 256 let al: *NxAllocation = _ra_alloc_at(a, i) 257 if al.cell_id == cell_id { 258 if al.resource_kind == resource_kind { 259 released = released + al.units_held 260 // Mark zero -- compaction deferred 261 al.units_held = 0 262 } 263 } 264 i = i + 1 265 } 266 return released 267} 268 269// ===== Preempt: take from lower-priority cell ===================== 270 271func nx_ra_preempt(a: *NxResourceArbiter, 272 resource_kind: nx_int, 273 requesting_priority: nx_int, 274 units_needed: nx_size) -> nx_size { 275 if (a as i64) == 0 { return 0 } 276 if nx_ra_kind_is_valid(resource_kind) == 0 { return 0 } 277 var reclaimed: nx_size = 0 278 var i: nx_int = 0 279 while i < a.n_allocations { 280 if reclaimed < units_needed { 281 let al: *NxAllocation = _ra_alloc_at(a, i) 282 if al.resource_kind == resource_kind { 283 if al.priority < requesting_priority { 284 if al.units_held > 0 { 285 reclaimed = reclaimed + al.units_held 286 al.units_held = 0 287 a.preemptions = a.preemptions + 1 288 } 289 } 290 } 291 } 292 i = i + 1 293 } 294 return reclaimed 295} 296 297// ===== Stats accessors ============================================ 298 299func nx_ra_grants_full(a: *NxResourceArbiter) -> nx_int { 300 if (a as i64) == 0 { return 0 } 301 return a.grants_full 302} 303 304func nx_ra_grants_partial(a: *NxResourceArbiter) -> nx_int { 305 if (a as i64) == 0 { return 0 } 306 return a.grants_partial 307} 308 309func nx_ra_denials(a: *NxResourceArbiter) -> nx_int { 310 if (a as i64) == 0 { return 0 } 311 return a.denials 312} 313 314func nx_ra_preemptions(a: *NxResourceArbiter) -> nx_int { 315 if (a as i64) == 0 { return 0 } 316 return a.preemptions 317}