1use crate::infer::GraphExt;
45use crate::op::{Activation, Op};
46use crate::{DType, Graph, NodeId, Shape};
47
48pub const SPD_JACOBI_SWEEPS: u32 = 6;
61
62pub fn spd_jacobi_sweeps() -> u32 {
64 std::env::var("RLX_SPD_JACOBI_SWEEPS")
65 .ok()
66 .and_then(|s| s.parse::<u32>().ok())
67 .filter(|&v| v > 0)
68 .unwrap_or(SPD_JACOBI_SWEEPS)
69}
70
71fn const_bytes(xs: &[f64], dtype: DType) -> Vec<u8> {
73 match dtype {
74 DType::F64 => xs.iter().flat_map(|v| v.to_le_bytes()).collect(),
75 _ => xs.iter().flat_map(|v| (*v as f32).to_le_bytes()).collect(),
76 }
77}
78
79fn cmat(g: &mut Graph, xs: &[f64], dims: &[usize], dtype: DType) -> NodeId {
81 g.add_node(
82 Op::Constant {
83 data: const_bytes(xs, dtype),
84 },
85 vec![],
86 Shape::new(dims, dtype),
87 )
88}
89
90fn cscalar(g: &mut Graph, v: f64, dtype: DType) -> NodeId {
92 cmat(g, &[v], &[1, 1], dtype)
93}
94
95fn round_robin_rounds(ne: usize) -> Vec<Vec<(usize, usize)>> {
101 let mut players: Vec<usize> = (0..ne).collect();
102 let mut rounds = Vec::with_capacity(ne - 1);
103 for _ in 0..ne - 1 {
104 let pairs: Vec<(usize, usize)> = (0..ne / 2)
105 .map(|i| {
106 let (a, b) = (players[i], players[ne - 1 - i]);
107 (a.min(b), a.max(b))
108 })
109 .collect();
110 rounds.push(pairs);
111 let last = players[ne - 1];
113 for i in (2..ne).rev() {
114 players[i] = players[i - 1];
115 }
116 players[1] = last;
117 }
118 rounds
119}
120
121fn diag_k(g: &mut Graph, m: NodeId, ident_k: NodeId) -> NodeId {
124 let d = g.mul(m, ident_k);
125 g.sum(d, vec![1], true) }
127
128#[allow(clippy::too_many_arguments)]
136fn one_round(
137 g: &mut Graph,
138 av: NodeId,
139 vv: NodeId,
140 spp: NodeId,
141 sqq: NodeId,
142 ident_n: NodeId,
143 ident_k: NodeId,
144 dt: DType,
145) -> (NodeId, NodeId) {
146 let one = cscalar(g, 1.0, dt);
147 let two = cscalar(g, 2.0, dt);
148 let tiny = cscalar(g, 1e-30, dt);
149 let sbias = cscalar(g, 1e-12, dt);
150 let floor = cscalar(g, 1e-6, dt);
151
152 let sppt = g.transpose_(spp, vec![1, 0]); let sqqt = g.transpose_(sqq, vec![1, 0]);
154 let tpp = g.mm(sppt, av); let m_pp = g.mm(tpp, spp); let app = diag_k(g, m_pp, ident_k); let tqq = g.mm(sqqt, av);
158 let m_qq = g.mm(tqq, sqq);
159 let aqq = diag_k(g, m_qq, ident_k);
160 let m_pq = g.mm(tpp, sqq); let apq = diag_k(g, m_pq, ident_k);
162
163 let num = g.sub(aqq, app);
165 let den0 = g.mul(two, apq);
166 let den0b = g.add(den0, sbias); let den0sq = g.mul(den0b, den0b);
168 let den0abs = g.sqrt(den0sq);
169 let den0abst = g.add(den0abs, tiny);
170 let sgnden = g.div(den0b, den0abst);
171 let sfloor = g.mul(sgnden, floor);
172 let den = g.add(den0, sfloor);
173 let tau = g.div(num, den);
174 let tau2 = g.mul(tau, tau);
175 let atau = g.sqrt(tau2);
176 let satau = g.add(atau, tiny);
177 let sgn = g.div(tau, satau);
178 let tau2p1 = g.add(tau2, one);
179 let rt = g.sqrt(tau2p1);
180 let tden = g.add(atau, rt);
181 let t = g.div(sgn, tden);
182 let t2 = g.mul(t, t);
183 let t2p1 = g.add(t2, one);
184 let rc = g.sqrt(t2p1);
185 let c = g.div(one, rc);
186 let s = g.mul(t, c);
187 let cm1 = g.sub(c, one);
188 let negs = g.neg(s);
189
190 let d_cm1 = g.mul(cm1, ident_k); let d_s = g.mul(s, ident_k);
194 let d_negs = g.mul(negs, ident_k);
195 let pp_diag = {
196 let l = g.mm(spp, d_cm1);
197 g.mm(l, sppt)
198 };
199 let qq_diag = {
200 let l = g.mm(sqq, d_cm1);
201 g.mm(l, sqqt)
202 };
203 let pq = {
204 let l = g.mm(spp, d_s);
205 g.mm(l, sqqt)
206 };
207 let qp = {
208 let l = g.mm(sqq, d_negs);
209 g.mm(l, sppt)
210 };
211 let j1 = g.add(ident_n, pp_diag);
212 let j2 = g.add(j1, qq_diag);
213 let j3 = g.add(j2, pq);
214 let jj = g.add(j3, qp);
215 let jt = g.transpose_(jj, vec![1, 0]);
216 let jta = g.mm(jt, av);
217 let av2 = g.mm(jta, jj);
218 let vv2 = g.mm(vv, jj);
219 (av2, vv2)
220}
221
222fn round_body(n: usize, k: usize, dt: DType) -> Graph {
225 let mut body = Graph::new("jacobi_round");
226 let carry = body.input("carry", Shape::new(&[2 * n, n], dt));
227 let spp = body.input("spp", Shape::new(&[n, k], dt));
228 let sqq = body.input("sqq", Shape::new(&[n, k], dt));
229 let a_in = body.narrow_(carry, 0, 0, n);
230 let v_in = body.narrow_(carry, 0, n, n);
231 let mut ident = vec![0f64; n * n];
232 for i in 0..n {
233 ident[i * n + i] = 1.0;
234 }
235 let ident_n = cmat(&mut body, &ident, &[n, n], dt);
236 let mut idk = vec![0f64; k * k];
237 for i in 0..k {
238 idk[i * k + i] = 1.0;
239 }
240 let ident_k = cmat(&mut body, &idk, &[k, k], dt);
241 let (a_out, v_out) = one_round(&mut body, a_in, v_in, spp, sqq, ident_n, ident_k, dt);
242 let out = body.concat_(vec![a_out, v_out], 0);
243 body.set_outputs(vec![out]);
244 body
245}
246
247fn eigensolve(g: &mut Graph, a: NodeId, n: usize, sweeps: u32, dt: DType) -> (NodeId, NodeId) {
259 let mut ident = vec![0f64; n * n];
260 for i in 0..n {
261 ident[i * n + i] = 1.0;
262 }
263 let iv = cmat(g, &ident, &[n, n], dt);
264 if n < 2 {
265 return (a, iv); }
267 let mut carry = g.concat_(vec![a, iv], 0); let ne = if n.is_multiple_of(2) { n } else { n + 1 };
270 let k = ne / 2;
271 let rounds = round_robin_rounds(ne);
272 let nr = rounds.len(); if std::env::var("RLX_SPD_UNROLL").as_deref() == Ok("1") {
293 let mut idk = vec![0f64; k * k];
294 for i in 0..k {
295 idk[i * k + i] = 1.0;
296 }
297 let ident_k = cmat(g, &idk, &[k, k], dt);
298 let sel: Vec<(NodeId, NodeId)> = rounds
299 .iter()
300 .map(|round| {
301 let mut sp = vec![0f64; n * k];
302 let mut sq = vec![0f64; n * k];
303 for (i, &(p, q)) in round.iter().enumerate() {
304 if p < n {
305 sp[p * k + i] = 1.0;
306 }
307 if q < n {
308 sq[q * k + i] = 1.0;
309 }
310 }
311 (cmat(g, &sp, &[n, k], dt), cmat(g, &sq, &[n, k], dt))
312 })
313 .collect();
314 let (mut av, mut vv) = (a, iv);
317 for _ in 0..sweeps {
318 for &(spp_r, sqq_r) in &sel {
319 let (a2, v2) = one_round(g, av, vv, spp_r, sqq_r, iv, ident_k, dt);
320 av = a2;
321 vv = v2;
322 }
323 }
324 return (av, vv);
325 }
326
327 let total = nr * sweeps as usize;
338 let mut spp = vec![0f64; total * n * k];
339 let mut sqq = vec![0f64; total * n * k];
340 for s in 0..sweeps as usize {
341 for (r, round) in rounds.iter().enumerate() {
342 let base = (s * nr + r) * n * k;
343 for (i, &(p, q)) in round.iter().enumerate() {
344 if p < n {
345 spp[base + p * k + i] = 1.0;
346 }
347 if q < n {
348 sqq[base + q * k + i] = 1.0;
349 }
350 }
351 }
352 }
353 let spp_c = cmat(g, &spp, &[total, n, k], dt);
354 let sqq_c = cmat(g, &sqq, &[total, n, k], dt);
355 let body = round_body(n, k, dt);
356 carry = g.scan_with_xs(carry, &[spp_c, sqq_c], body, total as u32);
357 let av = g.narrow_(carry, 0, 0, n);
358 let vv = g.narrow_(carry, 0, n, n);
359 (av, vv)
360}
361
362#[derive(Clone, Copy, PartialEq, Eq)]
368pub enum SpectralFn {
369 Re,
370 Log,
371 Sqrt,
372 InvSqrt,
373}
374
375fn spectral_fmat(
379 g: &mut Graph,
380 av: NodeId,
381 n: usize,
382 eps: f64,
383 f: SpectralFn,
384 dt: DType,
385) -> (NodeId, Vec<NodeId>) {
386 let epsn = cscalar(g, eps, dt);
387 let half = cscalar(g, 0.5, dt);
388 let one = cscalar(g, 1.0, dt);
389 let mut terms: Vec<NodeId> = Vec::with_capacity(n);
390 let mut lam_parts: Vec<NodeId> = Vec::with_capacity(n);
391 for i in 0..n {
392 let ri = g.narrow_(av, 0, i, 1); let lam = g.narrow_(ri, 1, i, 1); lam_parts.push(g.reshape_(lam, vec![1])); let sumv = g.add(lam, epsn);
397 let diff = g.sub(lam, epsn);
398 let d2 = g.mul(diff, diff);
399 let ad = g.sqrt(d2);
400 let sad = g.add(sumv, ad);
401 let mx = g.mul(half, sad);
402 let fl = match f {
403 SpectralFn::Re => mx,
404 SpectralFn::Log => g.activation(Activation::Log, mx, Shape::new(&[1, 1], dt)),
405 SpectralFn::Sqrt => g.sqrt(mx),
406 SpectralFn::InvSqrt => {
407 let s = g.sqrt(mx);
408 g.div(one, s)
409 }
410 };
411 let mut eii = vec![0f64; n * n];
412 eii[i * n + i] = 1.0;
413 let eii = cmat(g, &eii, &[n, n], dt);
414 terms.push(g.mul(fl, eii));
415 }
416 let mut fmat = terms[0];
417 for t in &terms[1..] {
418 fmat = g.add(fmat, *t);
419 }
420 (fmat, lam_parts)
421}
422
423pub fn spectral_matfn(
427 g: &mut Graph,
428 a: NodeId,
429 n: usize,
430 sweeps: u32,
431 eps: f64,
432 f: SpectralFn,
433) -> NodeId {
434 let dt = g.shape(a).dtype();
435 let (av, vv) = eigensolve(g, a, n, sweeps, dt);
436 let (fmat, _) = spectral_fmat(g, av, n, eps, f, dt);
437 let vt = g.transpose_(vv, vec![1, 0]);
438 let vf = g.mm(vv, fmat);
439 g.mm(vf, vt)
440}
441
442fn ident_mat3(g: &mut Graph, n: usize, dt: DType) -> NodeId {
455 let mut m = vec![0f64; n * n];
456 for i in 0..n {
457 m[i * n + i] = 1.0;
458 }
459 cmat(g, &m, &[1, n, n], dt)
460}
461
462fn jacobi_cs(
465 g: &mut Graph,
466 app: NodeId,
467 aqq: NodeId,
468 apq: NodeId,
469 dt: DType,
470) -> (NodeId, NodeId, NodeId) {
471 let one = cscalar(g, 1.0, dt);
472 let two = cscalar(g, 2.0, dt);
473 let tiny = cscalar(g, 1e-30, dt);
474 let sbias = cscalar(g, 1e-12, dt);
475 let floor = cscalar(g, 1e-6, dt);
476 let num = g.sub(aqq, app);
477 let den0 = g.mul(two, apq);
478 let den0b = g.add(den0, sbias);
479 let den0sq = g.mul(den0b, den0b);
480 let den0abs = g.sqrt(den0sq);
481 let den0abst = g.add(den0abs, tiny);
482 let sgnden = g.div(den0b, den0abst);
483 let sfloor = g.mul(sgnden, floor);
484 let den = g.add(den0, sfloor);
485 let tau = g.div(num, den);
486 let tau2 = g.mul(tau, tau);
487 let atau = g.sqrt(tau2);
488 let satau = g.add(atau, tiny);
489 let sgn = g.div(tau, satau);
490 let tau2p1 = g.add(tau2, one);
491 let rt = g.sqrt(tau2p1);
492 let tden = g.add(atau, rt);
493 let t = g.div(sgn, tden);
494 let t2 = g.mul(t, t);
495 let t2p1 = g.add(t2, one);
496 let rc = g.sqrt(t2p1);
497 let c = g.div(one, rc);
498 let s = g.mul(t, c);
499 let cm1 = g.sub(c, one);
500 let negs = g.neg(s);
501 (cm1, s, negs)
502}
503
504#[allow(clippy::too_many_arguments)]
507fn one_round_b(
508 g: &mut Graph,
509 av: NodeId,
510 vv: NodeId,
511 spp: NodeId,
512 sqq: NodeId,
513 ident_n: NodeId,
514 ident_k: NodeId,
515 dt: DType,
516) -> (NodeId, NodeId) {
517 let sppt = g.transpose_(spp, vec![0, 2, 1]); let sqqt = g.transpose_(sqq, vec![0, 2, 1]);
520 let tpp = g.mm(sppt, av); let m_pp = g.mm(tpp, spp); let app = {
523 let d = g.mul(m_pp, ident_k);
524 g.sum(d, vec![2], true)
525 }; let tqq = g.mm(sqqt, av);
527 let m_qq = g.mm(tqq, sqq);
528 let aqq = {
529 let d = g.mul(m_qq, ident_k);
530 g.sum(d, vec![2], true)
531 };
532 let m_pq = g.mm(tpp, sqq);
533 let apq = {
534 let d = g.mul(m_pq, ident_k);
535 g.sum(d, vec![2], true)
536 };
537 let (cm1, s, negs) = jacobi_cs(g, app, aqq, apq, dt);
538 let d_cm1 = g.mul(cm1, ident_k); let d_s = g.mul(s, ident_k);
540 let d_negs = g.mul(negs, ident_k);
541 let pp_diag = {
542 let l = g.mm(spp, d_cm1);
543 g.mm(l, sppt)
544 };
545 let qq_diag = {
546 let l = g.mm(sqq, d_cm1);
547 g.mm(l, sqqt)
548 };
549 let pq = {
550 let l = g.mm(spp, d_s);
551 g.mm(l, sqqt)
552 };
553 let qp = {
554 let l = g.mm(sqq, d_negs);
555 g.mm(l, sppt)
556 };
557 let j1 = g.add(ident_n, pp_diag);
558 let j2 = g.add(j1, qq_diag);
559 let j3 = g.add(j2, pq);
560 let jj = g.add(j3, qp);
561 let jt = g.transpose_(jj, vec![0, 2, 1]); let jta = g.mm(jt, av);
563 let av2 = g.mm(jta, jj);
564 let vv2 = g.mm(vv, jj);
565 (av2, vv2)
566}
567
568fn round_body_b(batch: usize, n: usize, k: usize, dt: DType) -> Graph {
570 let mut body = Graph::new("jacobi_round_b");
571 let carry = body.input("carry", Shape::new(&[batch, 2 * n, n], dt));
572 let spp = body.input("spp", Shape::new(&[n, k], dt));
573 let sqq = body.input("sqq", Shape::new(&[n, k], dt));
574 let a_in = body.narrow_(carry, 1, 0, n); let v_in = body.narrow_(carry, 1, n, n);
576 let _ = batch;
579 let spp3 = body.reshape_(spp, vec![1, n as i64, k as i64]);
580 let sqq3 = body.reshape_(sqq, vec![1, n as i64, k as i64]);
581 let ident_n = ident_mat3(&mut body, n, dt);
582 let ident_k = ident_mat3(&mut body, k, dt);
583 let (a_out, v_out) = one_round_b(&mut body, a_in, v_in, spp3, sqq3, ident_n, ident_k, dt);
584 let out = body.concat_(vec![a_out, v_out], 1); body.set_outputs(vec![out]);
586 body
587}
588
589fn eigensolve_b(
591 g: &mut Graph,
592 a: NodeId,
593 batch: usize,
594 n: usize,
595 sweeps: u32,
596 dt: DType,
597) -> (NodeId, NodeId) {
598 let mut ivdata = vec![0f64; batch * n * n];
601 for b in 0..batch {
602 for i in 0..n {
603 ivdata[b * n * n + i * n + i] = 1.0;
604 }
605 }
606 let iv = cmat(g, &ivdata, &[batch, n, n], dt);
607 if n < 2 {
608 return (a, iv);
609 }
610 let mut carry = g.concat_(vec![a, iv], 1); let ne = if n.is_multiple_of(2) { n } else { n + 1 };
613 let k = ne / 2;
614 let rounds = round_robin_rounds(ne);
615 let nr = rounds.len();
616 let mut spp = vec![0f64; nr * n * k];
617 let mut sqq = vec![0f64; nr * n * k];
618 for (r, round) in rounds.iter().enumerate() {
619 for (i, &(p, q)) in round.iter().enumerate() {
620 if p < n {
621 spp[r * n * k + p * k + i] = 1.0;
622 }
623 if q < n {
624 sqq[r * n * k + q * k + i] = 1.0;
625 }
626 }
627 }
628 let spp_c = cmat(g, &spp, &[nr, n, k], dt);
629 let sqq_c = cmat(g, &sqq, &[nr, n, k], dt);
630 let xs = [spp_c, sqq_c];
631 for _ in 0..sweeps {
632 let body = round_body_b(batch, n, k, dt);
633 carry = g.scan_with_xs(carry, &xs, body, nr as u32);
634 }
635 let av = g.narrow_(carry, 1, 0, n);
636 let vv = g.narrow_(carry, 1, n, n);
637 (av, vv)
638}
639
640pub fn spectral_matfn_batched(
643 g: &mut Graph,
644 a: NodeId,
645 batch: usize,
646 n: usize,
647 sweeps: u32,
648 eps: f64,
649 f: SpectralFn,
650) -> NodeId {
651 let dt = g.shape(a).dtype();
652 let ident_n = ident_mat3(g, n, dt); let (av, vv) = eigensolve_b(g, a, batch, n, sweeps, dt);
654 let epsn = cscalar(g, eps, dt);
656 let half = cscalar(g, 0.5, dt);
657 let one = cscalar(g, 1.0, dt);
658 let diag_av = {
659 let d = g.mul(av, ident_n);
660 g.sum(d, vec![2], true)
661 }; let sumv = g.add(diag_av, epsn);
663 let diff = g.sub(diag_av, epsn);
664 let d2 = g.mul(diff, diff);
665 let ad = g.sqrt(d2);
666 let sad = g.add(sumv, ad);
667 let mx = g.mul(half, sad); let fl = match f {
669 SpectralFn::Re => mx,
670 SpectralFn::Log => g.activation(Activation::Log, mx, Shape::new(&[batch, n, 1], dt)),
671 SpectralFn::Sqrt => g.sqrt(mx),
672 SpectralFn::InvSqrt => {
673 let s = g.sqrt(mx);
674 g.div(one, s)
675 }
676 };
677 let fmat = g.mul(fl, ident_n); let vt = g.transpose_(vv, vec![0, 2, 1]);
679 let vf = g.mm(vv, fmat);
680 g.mm(vf, vt)
681}
682
683impl Graph {
684 pub fn spd_reeig_batched(
687 &mut self,
688 x: NodeId,
689 batch: usize,
690 n: usize,
691 sweeps: u32,
692 eps: f64,
693 ) -> NodeId {
694 spectral_matfn_batched(self, x, batch, n, sweeps, eps, SpectralFn::Re)
695 }
696
697 pub fn spd_logeig_batched(
699 &mut self,
700 x: NodeId,
701 batch: usize,
702 n: usize,
703 sweeps: u32,
704 eps: f64,
705 ) -> NodeId {
706 spectral_matfn_batched(self, x, batch, n, sweeps, eps, SpectralFn::Log)
707 }
708}
709
710pub fn bimap(g: &mut Graph, w: NodeId, x: NodeId) -> NodeId {
713 let wt = g.transpose_(w, vec![1, 0]);
714 let wx = g.mm(w, x);
715 g.mm(wx, wt)
716}
717
718pub fn spd_batch_norm_transport(
722 g: &mut Graph,
723 x: NodeId,
724 mean: NodeId,
725 g_: NodeId,
726 n: usize,
727 batch: usize,
728 sweeps: u32,
729 eps: f64,
730) -> NodeId {
731 let ms = spectral_matfn(g, mean, n, sweeps, eps, SpectralFn::InvSqrt); let gs = spectral_matfn(g, g_, n, sweeps, eps, SpectralFn::Sqrt); let mut slices = Vec::with_capacity(batch);
734 for bi in 0..batch {
735 let xi = g.narrow_(x, 0, bi, 1);
736 let xi = g.reshape_(xi, vec![n as i64, n as i64]);
737 let mx = g.mm(ms, xi); let ci = g.mm(mx, ms); let gc = g.mm(gs, ci); let yi = g.mm(gc, gs); slices.push(g.reshape_(yi, vec![1, n as i64, n as i64]));
742 }
743 g.concat_(slices, 0)
744}
745
746pub fn spectral_reeig(g: &mut Graph, a: NodeId, n: usize, sweeps: u32, eps: f64) -> NodeId {
749 spectral_matfn(g, a, n, sweeps, eps, SpectralFn::Re)
750}
751
752pub fn spectral_logeig(g: &mut Graph, a: NodeId, n: usize, sweeps: u32, eps: f64) -> NodeId {
755 spectral_matfn(g, a, n, sweeps, eps, SpectralFn::Log)
756}
757
758pub fn spectral_packed(
763 g: &mut Graph,
764 a: NodeId,
765 n: usize,
766 sweeps: u32,
767 eps: f64,
768 log: bool,
769) -> NodeId {
770 let dt = g.shape(a).dtype();
771 let (av, vv) = eigensolve(g, a, n, sweeps, dt);
772 let f = if log { SpectralFn::Log } else { SpectralFn::Re };
773 let (fmat, lam_parts) = spectral_fmat(g, av, n, eps, f, dt);
774 let vt = g.transpose_(vv, vec![1, 0]);
775 let vf = g.mm(vv, fmat);
776 let y = g.mm(vf, vt); let y_flat = g.reshape_(y, vec![(n * n) as i64]); let lam = g.concat_(lam_parts, 0); let u_flat = g.reshape_(vv, vec![(n * n) as i64]); g.concat_(vec![y_flat, lam, u_flat], 0) }
782
783impl Graph {
784 pub fn spectral_reeig(&mut self, x: NodeId, sweeps: u32, eps: f64) -> NodeId {
787 let n = self.shape(x).dim(0).unwrap_static();
788 spectral_reeig(self, x, n, sweeps, eps)
789 }
790
791 pub fn spectral_logeig(&mut self, x: NodeId, sweeps: u32, eps: f64) -> NodeId {
794 let n = self.shape(x).dim(0).unwrap_static();
795 spectral_logeig(self, x, n, sweeps, eps)
796 }
797}