pub struct NystromState { /* private fields */ }Expand description
Streaming Nyström attention state for ONE GQA group.
State splits along the GQA grain, because the operator does:
- SHARED per KV group (
NystromGroup) — the exact window ring, the sink buffer and the key landmarks K̃. Under GQA every Q head of a group reads the SAME k/v rows, so all three are bit-identical across the group; storing them once per group instead of once per Q head is the point of this split (identical arithmetic, ×heads_per_kv less window memory). K̃ = seg_means(ks, t, d, m_eff) is a pure function of the group’s keys and oft(which fixes m_eff), so it is shareable for the same reason the keys are. - PRIVATE per Q head (
NystromHead) — the far accumulators T̂/Ẑ and their per-landmark running maxima, the QUERY landmarks Q̃, and the mixing matrix M = pinv(exp(Q̃K̃ᵀ/√d)). Q̃ is built from that head’s own queries, so M and the far field it drives are per-Q-head and cannot be shared: the far mass a head accumulates is contracted against its own query landmarks.
Lifecycle: new(m, w, sink) → prefill(prompt) once → step() per
decode token (single-head façade), or new_group/prefill_group/
step_group for a whole GQA group at once. All buffers are flat
Vec<f32>, row-major; the skeleton path performs no allocations
inside step().
Implementations§
Source§impl NystromState
impl NystromState
pub fn device_view(&self) -> O1DeviceView<'_>
Source§impl NystromState
impl NystromState
Sourcepub fn new(m: usize, w: usize, sink: usize) -> Self
pub fn new(m: usize, w: usize, sink: usize) -> Self
Single-head state (heads_per_kv == 1, and the shape the kernel
unit tests use).
m — landmark budget (≥ 4; see O1_DEFAULT_M),
w — exact window width (validated setting is 128),
sink — permanent exact sink keys (validated default is 4;
0 reproduces the sink-free kernel bit-for-bit).
Rectifier defaults to O1_DEFAULT_RECT; override with
with_rect (the golden-parity test pins it explicitly).
Examples found in repository?
63fn trial(m: usize, w: usize, sink: usize, amp: f32, depth: usize, seed: u64) -> f32 {
64 let (d, dv) = (64usize, 8usize);
65 let t = 8 * m + w + depth;
66 let mut s = seed;
67 let rd = (d as f32).sqrt();
68
69 let mut unit = |s: &mut u64| -> Vec<f32> {
70 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
71 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
72 v.iter().map(|x| x / n).collect()
73 };
74 let qhat = unit(&mut s);
75 let q: Vec<f32> = qhat.iter().map(|x| x * rd).collect();
76
77 let mut qs = Vec::with_capacity(t * d);
78 for _ in 0..t {
79 let u = unit(&mut s);
80 qs.extend(u.iter().map(|x| x * rd));
81 }
82
83 let mut ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
84 let mut vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
85 for j in 0..t {
86 vs[j * dv] = 0.0;
87 }
88 let p = t - depth;
89 for c in 0..d {
90 ks[p * d + c] += amp * qhat[c];
91 }
92 vs[p * dv] = 1.0;
93
94 let mut st = NystromState::new(m, w, sink);
95 st.prefill(&qs, &ks, &vs, t, d, dv);
96
97 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
98 let mut v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
99 v_new[0] = 0.0;
100 let mut got = vec![0f32; dv];
101 st.step(&q, &k_new, &v_new, &mut got);
102
103 let mut logits = Vec::with_capacity(t + 1);
104 for j in 0..t {
105 let dot: f32 = (0..d).map(|c| q[c] * ks[j * d + c]).sum();
106 logits.push(dot / rd);
107 }
108 let dot: f32 = (0..d).map(|c| q[c] * k_new[c]).sum();
109 logits.push(dot / rd);
110 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
111 let (mut den, mut num0) = (0f64, 0f64);
112 for (j, &l) in logits.iter().enumerate() {
113 let e = ((l - mx) as f64).exp();
114 den += e;
115 num0 += e * if j == t { v_new[0] } else { vs[j * dv] } as f64;
116 }
117 let want0 = (num0 / den) as f32;
118 if want0.abs() < 1e-4 {
119 return f32::NAN;
120 }
121 got[0] / want0
122}
123
124
125/// The background channel on its own: no needle, no signal, just the
126/// skeleton's error in estimating a far field of ordinary keys.
127///
128/// The recovery statistic divides by the exact answer, so it mixes two
129/// error sources — the needle's own estimate and the denominator built
130/// from every background key. Removing the needle isolates the second,
131/// and it is the one that should follow the central-limit law: a sum
132/// over ~n far keys approximated from m landmarks.
133///
134/// Returned: ‖o_o1 − o_exact‖ / ‖o_exact‖ over the whole output vector.
135/// An AMPLITUDE, deliberately — its square is the energy, and the two
136/// differ by exactly a factor of two in any log-log slope, which is why
137/// only one of them needs measuring.
138fn background_error(m: usize, w: usize, sink: usize, depth: usize, seed: u64) -> f32 {
139 let (d, dv) = (64usize, 8usize);
140 // Sequence length is FIXED, not 8*m + …. Tying it to m — which the
141 // recovery sweep above does, to guarantee m_eff reaches m — means a
142 // larger budget also gets a longer far field, and the sweep varies
143 // two things at once. Here only m moves. `8 * 64` keeps m_eff = m up
144 // to the largest budget measured.
145 let t = 8 * 64 + w + depth;
146 let _ = m;
147 let mut s = seed;
148 let rd = (d as f32).sqrt();
149
150 let mut unit = |s: &mut u64| -> Vec<f32> {
151 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
152 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
153 v.iter().map(|x| x / n).collect()
154 };
155 let q: Vec<f32> = unit(&mut s).iter().map(|x| x * rd).collect();
156 let mut qs = Vec::with_capacity(t * d);
157 for _ in 0..t {
158 let u = unit(&mut s);
159 qs.extend(u.iter().map(|x| x * rd));
160 }
161 let ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
162 let vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
163
164 let mut st = NystromState::new(m, w, sink);
165 st.prefill(&qs, &ks, &vs, t, d, dv);
166 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
167 let v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
168 let mut got = vec![0f32; dv];
169 st.step(&q, &k_new, &v_new, &mut got);
170
171 let mut logits = Vec::with_capacity(t + 1);
172 for j in 0..t {
173 logits.push((0..d).map(|c| q[c] * ks[j * d + c]).sum::<f32>() / rd);
174 }
175 logits.push((0..d).map(|c| q[c] * k_new[c]).sum::<f32>() / rd);
176 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
177 let mut den = 0f64;
178 let mut want = vec![0f64; dv];
179 for (j, &l) in logits.iter().enumerate() {
180 let e = ((l - mx) as f64).exp();
181 den += e;
182 let src = if j == t { &v_new[..] } else { &vs[j * dv..(j + 1) * dv] };
183 for (c, wc) in want.iter_mut().enumerate() {
184 *wc += e * src[c] as f64;
185 }
186 }
187 let want: Vec<f32> = want.iter().map(|x| (x / den) as f32).collect();
188 let num: f32 = got.iter().zip(&want).map(|(g, x)| (g - x) * (g - x)).sum::<f32>().sqrt();
189 let den2: f32 = want.iter().map(|x| x * x).sum::<f32>().sqrt();
190 num / den2.max(1e-9)
191}Sourcepub fn new_group(m: usize, w: usize, sink: usize, q_heads: usize) -> Self
pub fn new_group(m: usize, w: usize, sink: usize, q_heads: usize) -> Self
State for one GQA group of q_heads query heads sharing a KV
head. The window/sink/K̃ are stored ONCE for the group; each Q
head keeps its own far field, Q̃ and M.
Sourcepub fn with_rect(self, rect: O1Rect) -> Self
pub fn with_rect(self, rect: O1Rect) -> Self
Select the skeleton rectifier for every head of the group
(builder; see O1Rect).
Sourcepub fn num_q_heads(&self) -> usize
pub fn num_q_heads(&self) -> usize
Query heads in this group.
Sourcepub fn far_len(&self, head: usize) -> usize
pub fn far_len(&self, head: usize) -> usize
Keys absorbed into head head’s far field. Exposed for the
delayed-insertion invariant test: eviction is a GROUP event, but
each head must absorb the evicted key EXACTLY once, so this must
equal the number of evictions — never a multiple of it.
Sourcepub fn prefill(
&mut self,
qs: &[f32],
ks: &[f32],
vs: &[f32],
t: usize,
d: usize,
dv: usize,
)
pub fn prefill( &mut self, qs: &[f32], ks: &[f32], vs: &[f32], t: usize, d: usize, dv: usize, )
Absorb the whole prompt for a single-head state — see
prefill_group.
Examples found in repository?
63fn trial(m: usize, w: usize, sink: usize, amp: f32, depth: usize, seed: u64) -> f32 {
64 let (d, dv) = (64usize, 8usize);
65 let t = 8 * m + w + depth;
66 let mut s = seed;
67 let rd = (d as f32).sqrt();
68
69 let mut unit = |s: &mut u64| -> Vec<f32> {
70 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
71 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
72 v.iter().map(|x| x / n).collect()
73 };
74 let qhat = unit(&mut s);
75 let q: Vec<f32> = qhat.iter().map(|x| x * rd).collect();
76
77 let mut qs = Vec::with_capacity(t * d);
78 for _ in 0..t {
79 let u = unit(&mut s);
80 qs.extend(u.iter().map(|x| x * rd));
81 }
82
83 let mut ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
84 let mut vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
85 for j in 0..t {
86 vs[j * dv] = 0.0;
87 }
88 let p = t - depth;
89 for c in 0..d {
90 ks[p * d + c] += amp * qhat[c];
91 }
92 vs[p * dv] = 1.0;
93
94 let mut st = NystromState::new(m, w, sink);
95 st.prefill(&qs, &ks, &vs, t, d, dv);
96
97 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
98 let mut v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
99 v_new[0] = 0.0;
100 let mut got = vec![0f32; dv];
101 st.step(&q, &k_new, &v_new, &mut got);
102
103 let mut logits = Vec::with_capacity(t + 1);
104 for j in 0..t {
105 let dot: f32 = (0..d).map(|c| q[c] * ks[j * d + c]).sum();
106 logits.push(dot / rd);
107 }
108 let dot: f32 = (0..d).map(|c| q[c] * k_new[c]).sum();
109 logits.push(dot / rd);
110 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
111 let (mut den, mut num0) = (0f64, 0f64);
112 for (j, &l) in logits.iter().enumerate() {
113 let e = ((l - mx) as f64).exp();
114 den += e;
115 num0 += e * if j == t { v_new[0] } else { vs[j * dv] } as f64;
116 }
117 let want0 = (num0 / den) as f32;
118 if want0.abs() < 1e-4 {
119 return f32::NAN;
120 }
121 got[0] / want0
122}
123
124
125/// The background channel on its own: no needle, no signal, just the
126/// skeleton's error in estimating a far field of ordinary keys.
127///
128/// The recovery statistic divides by the exact answer, so it mixes two
129/// error sources — the needle's own estimate and the denominator built
130/// from every background key. Removing the needle isolates the second,
131/// and it is the one that should follow the central-limit law: a sum
132/// over ~n far keys approximated from m landmarks.
133///
134/// Returned: ‖o_o1 − o_exact‖ / ‖o_exact‖ over the whole output vector.
135/// An AMPLITUDE, deliberately — its square is the energy, and the two
136/// differ by exactly a factor of two in any log-log slope, which is why
137/// only one of them needs measuring.
138fn background_error(m: usize, w: usize, sink: usize, depth: usize, seed: u64) -> f32 {
139 let (d, dv) = (64usize, 8usize);
140 // Sequence length is FIXED, not 8*m + …. Tying it to m — which the
141 // recovery sweep above does, to guarantee m_eff reaches m — means a
142 // larger budget also gets a longer far field, and the sweep varies
143 // two things at once. Here only m moves. `8 * 64` keeps m_eff = m up
144 // to the largest budget measured.
145 let t = 8 * 64 + w + depth;
146 let _ = m;
147 let mut s = seed;
148 let rd = (d as f32).sqrt();
149
150 let mut unit = |s: &mut u64| -> Vec<f32> {
151 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
152 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
153 v.iter().map(|x| x / n).collect()
154 };
155 let q: Vec<f32> = unit(&mut s).iter().map(|x| x * rd).collect();
156 let mut qs = Vec::with_capacity(t * d);
157 for _ in 0..t {
158 let u = unit(&mut s);
159 qs.extend(u.iter().map(|x| x * rd));
160 }
161 let ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
162 let vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
163
164 let mut st = NystromState::new(m, w, sink);
165 st.prefill(&qs, &ks, &vs, t, d, dv);
166 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
167 let v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
168 let mut got = vec![0f32; dv];
169 st.step(&q, &k_new, &v_new, &mut got);
170
171 let mut logits = Vec::with_capacity(t + 1);
172 for j in 0..t {
173 logits.push((0..d).map(|c| q[c] * ks[j * d + c]).sum::<f32>() / rd);
174 }
175 logits.push((0..d).map(|c| q[c] * k_new[c]).sum::<f32>() / rd);
176 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
177 let mut den = 0f64;
178 let mut want = vec![0f64; dv];
179 for (j, &l) in logits.iter().enumerate() {
180 let e = ((l - mx) as f64).exp();
181 den += e;
182 let src = if j == t { &v_new[..] } else { &vs[j * dv..(j + 1) * dv] };
183 for (c, wc) in want.iter_mut().enumerate() {
184 *wc += e * src[c] as f64;
185 }
186 }
187 let want: Vec<f32> = want.iter().map(|x| (x / den) as f32).collect();
188 let num: f32 = got.iter().zip(&want).map(|(g, x)| (g - x) * (g - x)).sum::<f32>().sqrt();
189 let den2: f32 = want.iter().map(|x| x * x).sum::<f32>().sqrt();
190 num / den2.max(1e-9)
191}Sourcepub fn prefill_group(
&mut self,
qs: &[&[f32]],
ks: &[f32],
vs: &[f32],
t: usize,
d: usize,
dv: usize,
)
pub fn prefill_group( &mut self, qs: &[&[f32]], ks: &[f32], vs: &[f32], t: usize, d: usize, dv: usize, )
Absorb the whole prompt for a GQA group: freeze each head’s
landmarks and M, then replay the prompt through the step() state
semantics (window fill + delayed far insertion). qs[h] is that
head’s [t][d] query block; ks is [t][d] and vs is
[t][dv] — the group’s shared keys/values, row-major.
Sourcepub fn step(&mut self, q: &[f32], k: &[f32], v: &[f32], out: &mut [f32])
pub fn step(&mut self, q: &[f32], k: &[f32], v: &[f32], out: &mut [f32])
One decode step for a single-head state — see step_group.
Examples found in repository?
63fn trial(m: usize, w: usize, sink: usize, amp: f32, depth: usize, seed: u64) -> f32 {
64 let (d, dv) = (64usize, 8usize);
65 let t = 8 * m + w + depth;
66 let mut s = seed;
67 let rd = (d as f32).sqrt();
68
69 let mut unit = |s: &mut u64| -> Vec<f32> {
70 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
71 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
72 v.iter().map(|x| x / n).collect()
73 };
74 let qhat = unit(&mut s);
75 let q: Vec<f32> = qhat.iter().map(|x| x * rd).collect();
76
77 let mut qs = Vec::with_capacity(t * d);
78 for _ in 0..t {
79 let u = unit(&mut s);
80 qs.extend(u.iter().map(|x| x * rd));
81 }
82
83 let mut ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
84 let mut vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
85 for j in 0..t {
86 vs[j * dv] = 0.0;
87 }
88 let p = t - depth;
89 for c in 0..d {
90 ks[p * d + c] += amp * qhat[c];
91 }
92 vs[p * dv] = 1.0;
93
94 let mut st = NystromState::new(m, w, sink);
95 st.prefill(&qs, &ks, &vs, t, d, dv);
96
97 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
98 let mut v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
99 v_new[0] = 0.0;
100 let mut got = vec![0f32; dv];
101 st.step(&q, &k_new, &v_new, &mut got);
102
103 let mut logits = Vec::with_capacity(t + 1);
104 for j in 0..t {
105 let dot: f32 = (0..d).map(|c| q[c] * ks[j * d + c]).sum();
106 logits.push(dot / rd);
107 }
108 let dot: f32 = (0..d).map(|c| q[c] * k_new[c]).sum();
109 logits.push(dot / rd);
110 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
111 let (mut den, mut num0) = (0f64, 0f64);
112 for (j, &l) in logits.iter().enumerate() {
113 let e = ((l - mx) as f64).exp();
114 den += e;
115 num0 += e * if j == t { v_new[0] } else { vs[j * dv] } as f64;
116 }
117 let want0 = (num0 / den) as f32;
118 if want0.abs() < 1e-4 {
119 return f32::NAN;
120 }
121 got[0] / want0
122}
123
124
125/// The background channel on its own: no needle, no signal, just the
126/// skeleton's error in estimating a far field of ordinary keys.
127///
128/// The recovery statistic divides by the exact answer, so it mixes two
129/// error sources — the needle's own estimate and the denominator built
130/// from every background key. Removing the needle isolates the second,
131/// and it is the one that should follow the central-limit law: a sum
132/// over ~n far keys approximated from m landmarks.
133///
134/// Returned: ‖o_o1 − o_exact‖ / ‖o_exact‖ over the whole output vector.
135/// An AMPLITUDE, deliberately — its square is the energy, and the two
136/// differ by exactly a factor of two in any log-log slope, which is why
137/// only one of them needs measuring.
138fn background_error(m: usize, w: usize, sink: usize, depth: usize, seed: u64) -> f32 {
139 let (d, dv) = (64usize, 8usize);
140 // Sequence length is FIXED, not 8*m + …. Tying it to m — which the
141 // recovery sweep above does, to guarantee m_eff reaches m — means a
142 // larger budget also gets a longer far field, and the sweep varies
143 // two things at once. Here only m moves. `8 * 64` keeps m_eff = m up
144 // to the largest budget measured.
145 let t = 8 * 64 + w + depth;
146 let _ = m;
147 let mut s = seed;
148 let rd = (d as f32).sqrt();
149
150 let mut unit = |s: &mut u64| -> Vec<f32> {
151 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
152 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
153 v.iter().map(|x| x / n).collect()
154 };
155 let q: Vec<f32> = unit(&mut s).iter().map(|x| x * rd).collect();
156 let mut qs = Vec::with_capacity(t * d);
157 for _ in 0..t {
158 let u = unit(&mut s);
159 qs.extend(u.iter().map(|x| x * rd));
160 }
161 let ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
162 let vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
163
164 let mut st = NystromState::new(m, w, sink);
165 st.prefill(&qs, &ks, &vs, t, d, dv);
166 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
167 let v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
168 let mut got = vec![0f32; dv];
169 st.step(&q, &k_new, &v_new, &mut got);
170
171 let mut logits = Vec::with_capacity(t + 1);
172 for j in 0..t {
173 logits.push((0..d).map(|c| q[c] * ks[j * d + c]).sum::<f32>() / rd);
174 }
175 logits.push((0..d).map(|c| q[c] * k_new[c]).sum::<f32>() / rd);
176 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
177 let mut den = 0f64;
178 let mut want = vec![0f64; dv];
179 for (j, &l) in logits.iter().enumerate() {
180 let e = ((l - mx) as f64).exp();
181 den += e;
182 let src = if j == t { &v_new[..] } else { &vs[j * dv..(j + 1) * dv] };
183 for (c, wc) in want.iter_mut().enumerate() {
184 *wc += e * src[c] as f64;
185 }
186 }
187 let want: Vec<f32> = want.iter().map(|x| (x / den) as f32).collect();
188 let num: f32 = got.iter().zip(&want).map(|(g, x)| (g - x) * (g - x)).sum::<f32>().sqrt();
189 let den2: f32 = want.iter().map(|x| x * x).sum::<f32>().sqrt();
190 num / den2.max(1e-9)
191}Sourcepub fn step_group(
&mut self,
q_all: &[f32],
k: &[f32],
v: &[f32],
out_all: &mut [f32],
)
pub fn step_group( &mut self, q_all: &[f32], k: &[f32], v: &[f32], out_all: &mut [f32], )
One decode step for the whole GQA group. Inserts the group’s
(k, v) ONCE, evicting the oldest window key into every head’s far
accumulators, then writes each head’s attention output.
q_all is [q_heads][d], out_all is [q_heads][dv].
Sourcepub fn memory_bytes(&self) -> usize
pub fn memory_bytes(&self) -> usize
Heap bytes held by this group’s state (shared window + sinks +
K̃, plus each head’s skeleton and scratch) — feeds the honest
“KV+state” memory line, same discipline as counting
linear_state for the linear core.
Trait Implementations§
Source§impl Clone for NystromState
impl Clone for NystromState
Source§fn clone(&self) -> NystromState
fn clone(&self) -> NystromState
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more