code wiki / _hdl_build / nx_converge_lib.nx
nx_converge_lib.nx source
↩ module page · 154 lines · 7413 B
1// nx_converge_lib.nx -- THE CONVERGENCE CONTROLLER: hit a RETENTION TARGET instead of guessing a denoise.
2//
3// THE PROBLEM. Reference-conditioned generation has one continuous knob (denoise strength) and one
4// thing you actually care about (how much of the reference survived). Today an operator guesses a
5// denoise, waits 25-50s for the GPU, eyeballs the result, and guesses again. The guess is the whole
6// workflow, and it is neither reproducible nor auditable.
7//
8// THE INSIGHT THAT MAKES THIS EXACT, NOT HEURISTIC. nx_refbench_gate's T4 proves the ruler ORDERS
9// correctly, and the measured curve falls monotonically with denoise (859 -> 647 -> 359). A monotonic
10// function on a bounded interval is exactly the case where BISECTION IS GUARANTEED TO CONVERGE, and to
11// do so in ceil(log2(range/tolerance)) steps. So this is not a tuned heuristic with magic gains -- it
12// is a bracketing search with a proof behind it, and its step count is known BEFORE the first render.
13// Retention DECREASES as denoise rises, so the bracket updates are inverted relative to a naive search:
14// measuring ABOVE target means we kept too much and must push denoise UP.
15//
16// WHY IT IS STATELESS. The render happens on the GPU host; only the DECISION belongs in the sovereign
17// organ. So `next` takes the whole bracket as arguments and returns the next probe -- an agent, a
18// workflow, or a human can drive it one render at a time over MCP with nothing persisted between calls.
19// State that lives in a daemon cannot be replayed; state that travels in the arguments can.
20//
21// NO-FLOAT: every quantity is permil (0..1000) integer, per the sovereign arithmetic law.
22// license_tier: ORIGINAL
23import "nx_syscalls.nx"
24
25const CV_MIN: i64 = 0 // denoise floor, permil (0 = return the source untouched)
26const CV_MAX: i64 = 1000 // denoise ceiling, permil (1000 = ignore the source entirely)
27const CV_MAX_STEPS: i64 = 12 // ceil(log2(1000)) = 10, +2 slack; a HARD bound so a non-monotonic
28 // or noisy oracle can never spin the GPU forever
29const CV_DONE: i64 = 0 - 1 // sentinel returned instead of a probe when the search has finished
30
31// Status codes -- distinct so a caller can branch without parsing prose.
32const CV_OK: i64 = 0 // converged: |measured - target| <= tol
33const CV_CONTINUE: i64 = 1 // keep going, a new probe is available
34const CV_EXHAUSTED: i64 = 2 // step budget spent without landing inside tolerance
35const CV_UNREACHABLE: i64 = 3 // target lies outside what the knob can produce
36
37// The first probe. Midpoint of the bracket -- with no measurement yet there is no information that
38// would justify starting anywhere else, and pretending otherwise would be a tuned constant.
39func cv_first_probe() -> i64 {
40 return (CV_MIN + CV_MAX) / 2
41}
42
43// Is this target achievable at all? Retention is bounded by construction: the ruler cannot report
44// more than 1000 (perfect) and a target at/below what maximum denoise yields is not a real request.
45// Answering this BEFORE any render is the difference between a fast refusal and a wasted GPU minute.
46func cv_reachable(target: i64) -> i64 {
47 if target > CV_MAX { return 0 }
48 if target < CV_MIN { return 0 }
49 return 1
50}
51
52// One control step. Given the bracket [lo,hi], the probe just rendered, and what the ruler MEASURED
53// for it, return the next probe (or CV_DONE) and write the status to out_status, the new bracket to
54// out_lo/out_hi.
55//
56// The inversion is the whole subtlety: retention falls as denoise rises, so
57// measured ABOVE target -> too much of the reference survived -> raise the FLOOR (lo = probe)
58// measured BELOW target -> too little survived -> lower the CEILING (hi = probe)
59func cv_step(target: i64, tol: i64, lo: i64, hi: i64, probe: i64, measured: i64,
60 steps_used: i64, out_status: *i64, out_lo: *i64, out_hi: *i64) -> i64 {
61 out_lo[0] = lo
62 out_hi[0] = hi
63
64 if cv_reachable(target) == 0 { out_status[0] = CV_UNREACHABLE; return CV_DONE }
65
66 var err: i64 = measured - target
67 if err < 0 { err = 0 - err }
68 if err <= tol { out_status[0] = CV_OK; return CV_DONE }
69
70 var nlo: i64 = lo
71 var nhi: i64 = hi
72 if measured > target { nlo = probe } else { nhi = probe }
73 out_lo[0] = nlo
74 out_hi[0] = nhi
75
76 if steps_used >= CV_MAX_STEPS { out_status[0] = CV_EXHAUSTED; return CV_DONE }
77
78 // Bracket collapsed to adjacent integers: the knob has no finer setting to offer. Reporting
79 // EXHAUSTED here is honest -- the target sits between two achievable outputs.
80 if nhi - nlo <= 1 { out_status[0] = CV_EXHAUSTED; return CV_DONE }
81
82 out_status[0] = CV_CONTINUE
83 return (nlo + nhi) / 2
84}
85
86// ---- The grounded oracle -------------------------------------------------------------------
87// A synthetic stand-in for the GPU used by `simulate` and by the gate, so the controller can be
88// proven WITHOUT burning renders. It is NOT invented: it interpolates the RETENTION VALUES ACTUALLY
89// MEASURED on the deployed engine (denoise 350 -> 859, 550 -> 647, 750 -> 359), anchored at the two
90// endpoints the definition forces (denoise 0 returns the source, so retention is perfect; maximum
91// denoise keeps only whatever the prompt happens to reproduce).
92// Using the real curve means a controller that converges here is solving the real problem shape.
93const CV_K0_D: i64 = 0
94const CV_K0_R: i64 = 1000
95const CV_K1_D: i64 = 350
96const CV_K1_R: i64 = 859
97const CV_K2_D: i64 = 550
98const CV_K2_R: i64 = 647
99const CV_K3_D: i64 = 750
100const CV_K3_R: i64 = 359
101const CV_K4_D: i64 = 1000
102const CV_K4_R: i64 = 200
103
104// integer linear interpolation between two keypoints
105func cv_lerp(d: i64, d0: i64, r0: i64, d1: i64, r1: i64) -> i64 {
106 let span: i64 = d1 - d0
107 if span <= 0 { return r0 }
108 return r0 + ((r1 - r0) * (d - d0)) / span
109}
110
111func cv_oracle(denoise: i64) -> i64 {
112 var d: i64 = denoise
113 if d < CV_MIN { d = CV_MIN }
114 if d > CV_MAX { d = CV_MAX }
115 if d <= CV_K1_D { return cv_lerp(d, CV_K0_D, CV_K0_R, CV_K1_D, CV_K1_R) }
116 if d <= CV_K2_D { return cv_lerp(d, CV_K1_D, CV_K1_R, CV_K2_D, CV_K2_R) }
117 if d <= CV_K3_D { return cv_lerp(d, CV_K2_D, CV_K2_R, CV_K3_D, CV_K3_R) }
118 return cv_lerp(d, CV_K3_D, CV_K3_R, CV_K4_D, CV_K4_R)
119}
120
121// Drive the controller to completion against the oracle. Returns the final status; writes the
122// landing denoise, its retention, and the step count. This is the same control path a real GPU run
123// takes -- only the measurement source differs.
124func cv_run(target: i64, tol: i64, out_denoise: *i64, out_ret: *i64, out_steps: *i64) -> i64 {
125 var lo: i64 = CV_MIN
126 var hi: i64 = CV_MAX
127 var probe: i64 = cv_first_probe()
128 var steps: i64 = 0
129 let st: *i64 = sys_mmap(8)
130 let nlo: *i64 = sys_mmap(8)
131 let nhi: *i64 = sys_mmap(8)
132 st[0] = CV_CONTINUE
133
134 if cv_reachable(target) == 0 {
135 out_denoise[0] = 0 - 1
136 out_ret[0] = 0 - 1
137 out_steps[0] = 0
138 return CV_UNREACHABLE
139 }
140
141 var measured: i64 = 0
142 while st[0] == CV_CONTINUE {
143 measured = cv_oracle(probe)
144 steps = steps + 1
145 let nxt: i64 = cv_step(target, tol, lo, hi, probe, measured, steps, st, nlo, nhi)
146 lo = nlo[0]
147 hi = nhi[0]
148 if nxt != CV_DONE { probe = nxt }
149 }
150 out_denoise[0] = probe
151 out_ret[0] = measured
152 out_steps[0] = steps
153 return st[0]
154}