1use crate::infer::GraphExt;
34use crate::op::{Activation, Op};
35use crate::{DType, Graph, NodeId, Shape};
36
37pub const SPD_JACOBI_SWEEPS: u32 = 6;
50
51pub fn spd_jacobi_sweeps() -> u32 {
53 std::env::var("RLX_SPD_JACOBI_SWEEPS")
54 .ok()
55 .and_then(|s| s.parse::<u32>().ok())
56 .filter(|&v| v > 0)
57 .unwrap_or(SPD_JACOBI_SWEEPS)
58}
59
60fn const_bytes(xs: &[f64], dtype: DType) -> Vec<u8> {
62 match dtype {
63 DType::F64 => xs.iter().flat_map(|v| v.to_le_bytes()).collect(),
64 _ => xs.iter().flat_map(|v| (*v as f32).to_le_bytes()).collect(),
65 }
66}
67
68fn cmat(g: &mut Graph, xs: &[f64], dims: &[usize], dtype: DType) -> NodeId {
70 g.add_node(
71 Op::Constant {
72 data: const_bytes(xs, dtype),
73 },
74 vec![],
75 Shape::new(dims, dtype),
76 )
77}
78
79fn cscalar(g: &mut Graph, v: f64, dtype: DType) -> NodeId {
81 cmat(g, &[v], &[1, 1], dtype)
82}
83
84fn round_robin_rounds(ne: usize) -> Vec<Vec<(usize, usize)>> {
90 let mut players: Vec<usize> = (0..ne).collect();
91 let mut rounds = Vec::with_capacity(ne - 1);
92 for _ in 0..ne - 1 {
93 let pairs: Vec<(usize, usize)> = (0..ne / 2)
94 .map(|i| {
95 let (a, b) = (players[i], players[ne - 1 - i]);
96 (a.min(b), a.max(b))
97 })
98 .collect();
99 rounds.push(pairs);
100 let last = players[ne - 1];
102 for i in (2..ne).rev() {
103 players[i] = players[i - 1];
104 }
105 players[1] = last;
106 }
107 rounds
108}
109
110fn diag_k(g: &mut Graph, m: NodeId, ident_k: NodeId) -> NodeId {
113 let d = g.mul(m, ident_k);
114 g.sum(d, vec![1], true) }
116
117#[allow(clippy::too_many_arguments)]
125fn one_round(
126 g: &mut Graph,
127 av: NodeId,
128 vv: NodeId,
129 spp: NodeId,
130 sqq: NodeId,
131 ident_n: NodeId,
132 ident_k: NodeId,
133 dt: DType,
134) -> (NodeId, NodeId) {
135 let one = cscalar(g, 1.0, dt);
136 let two = cscalar(g, 2.0, dt);
137 let tiny = cscalar(g, 1e-30, dt);
138 let sbias = cscalar(g, 1e-12, dt);
139 let floor = cscalar(g, 1e-6, dt);
140
141 let sppt = g.transpose_(spp, vec![1, 0]); let sqqt = g.transpose_(sqq, vec![1, 0]);
143 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);
147 let m_qq = g.mm(tqq, sqq);
148 let aqq = diag_k(g, m_qq, ident_k);
149 let m_pq = g.mm(tpp, sqq); let apq = diag_k(g, m_pq, ident_k);
151
152 let num = g.sub(aqq, app);
154 let den0 = g.mul(two, apq);
155 let den0b = g.add(den0, sbias); let den0sq = g.mul(den0b, den0b);
157 let den0abs = g.sqrt(den0sq);
158 let den0abst = g.add(den0abs, tiny);
159 let sgnden = g.div(den0b, den0abst);
160 let sfloor = g.mul(sgnden, floor);
161 let den = g.add(den0, sfloor);
162 let tau = g.div(num, den);
163 let tau2 = g.mul(tau, tau);
164 let atau = g.sqrt(tau2);
165 let satau = g.add(atau, tiny);
166 let sgn = g.div(tau, satau);
167 let tau2p1 = g.add(tau2, one);
168 let rt = g.sqrt(tau2p1);
169 let tden = g.add(atau, rt);
170 let t = g.div(sgn, tden);
171 let t2 = g.mul(t, t);
172 let t2p1 = g.add(t2, one);
173 let rc = g.sqrt(t2p1);
174 let c = g.div(one, rc);
175 let s = g.mul(t, c);
176 let cm1 = g.sub(c, one);
177 let negs = g.neg(s);
178
179 let d_cm1 = g.mul(cm1, ident_k); let d_s = g.mul(s, ident_k);
183 let d_negs = g.mul(negs, ident_k);
184 let pp_diag = {
185 let l = g.mm(spp, d_cm1);
186 g.mm(l, sppt)
187 };
188 let qq_diag = {
189 let l = g.mm(sqq, d_cm1);
190 g.mm(l, sqqt)
191 };
192 let pq = {
193 let l = g.mm(spp, d_s);
194 g.mm(l, sqqt)
195 };
196 let qp = {
197 let l = g.mm(sqq, d_negs);
198 g.mm(l, sppt)
199 };
200 let j1 = g.add(ident_n, pp_diag);
201 let j2 = g.add(j1, qq_diag);
202 let j3 = g.add(j2, pq);
203 let jj = g.add(j3, qp);
204 let jt = g.transpose_(jj, vec![1, 0]);
205 let jta = g.mm(jt, av);
206 let av2 = g.mm(jta, jj);
207 let vv2 = g.mm(vv, jj);
208 (av2, vv2)
209}
210
211fn round_body(n: usize, k: usize, dt: DType) -> Graph {
214 let mut body = Graph::new("jacobi_round");
215 let carry = body.input("carry", Shape::new(&[2 * n, n], dt));
216 let spp = body.input("spp", Shape::new(&[n, k], dt));
217 let sqq = body.input("sqq", Shape::new(&[n, k], dt));
218 let a_in = body.narrow_(carry, 0, 0, n);
219 let v_in = body.narrow_(carry, 0, n, n);
220 let mut ident = vec![0f64; n * n];
221 for i in 0..n {
222 ident[i * n + i] = 1.0;
223 }
224 let ident_n = cmat(&mut body, &ident, &[n, n], dt);
225 let mut idk = vec![0f64; k * k];
226 for i in 0..k {
227 idk[i * k + i] = 1.0;
228 }
229 let ident_k = cmat(&mut body, &idk, &[k, k], dt);
230 let (a_out, v_out) = one_round(&mut body, a_in, v_in, spp, sqq, ident_n, ident_k, dt);
231 let out = body.concat_(vec![a_out, v_out], 0);
232 body.set_outputs(vec![out]);
233 body
234}
235
236fn eigensolve(g: &mut Graph, a: NodeId, n: usize, sweeps: u32, dt: DType) -> (NodeId, NodeId) {
248 let mut ident = vec![0f64; n * n];
249 for i in 0..n {
250 ident[i * n + i] = 1.0;
251 }
252 let iv = cmat(g, &ident, &[n, n], dt);
253 if n < 2 {
254 return (a, iv); }
256 let mut carry = g.concat_(vec![a, iv], 0); let ne = if n.is_multiple_of(2) { n } else { n + 1 };
259 let k = ne / 2;
260 let rounds = round_robin_rounds(ne);
261 let nr = rounds.len(); if std::env::var("RLX_SPD_UNROLL").as_deref() == Ok("1") {
282 let mut idk = vec![0f64; k * k];
283 for i in 0..k {
284 idk[i * k + i] = 1.0;
285 }
286 let ident_k = cmat(g, &idk, &[k, k], dt);
287 let sel: Vec<(NodeId, NodeId)> = rounds
288 .iter()
289 .map(|round| {
290 let mut sp = vec![0f64; n * k];
291 let mut sq = vec![0f64; n * k];
292 for (i, &(p, q)) in round.iter().enumerate() {
293 if p < n {
294 sp[p * k + i] = 1.0;
295 }
296 if q < n {
297 sq[q * k + i] = 1.0;
298 }
299 }
300 (cmat(g, &sp, &[n, k], dt), cmat(g, &sq, &[n, k], dt))
301 })
302 .collect();
303 let (mut av, mut vv) = (a, iv);
306 for _ in 0..sweeps {
307 for &(spp_r, sqq_r) in &sel {
308 let (a2, v2) = one_round(g, av, vv, spp_r, sqq_r, iv, ident_k, dt);
309 av = a2;
310 vv = v2;
311 }
312 }
313 return (av, vv);
314 }
315
316 let total = nr * sweeps as usize;
327 let mut spp = vec![0f64; total * n * k];
328 let mut sqq = vec![0f64; total * n * k];
329 for s in 0..sweeps as usize {
330 for (r, round) in rounds.iter().enumerate() {
331 let base = (s * nr + r) * n * k;
332 for (i, &(p, q)) in round.iter().enumerate() {
333 if p < n {
334 spp[base + p * k + i] = 1.0;
335 }
336 if q < n {
337 sqq[base + q * k + i] = 1.0;
338 }
339 }
340 }
341 }
342 let spp_c = cmat(g, &spp, &[total, n, k], dt);
343 let sqq_c = cmat(g, &sqq, &[total, n, k], dt);
344 let body = round_body(n, k, dt);
345 carry = g.scan_with_xs(carry, &[spp_c, sqq_c], body, total as u32);
346 let av = g.narrow_(carry, 0, 0, n);
347 let vv = g.narrow_(carry, 0, n, n);
348 (av, vv)
349}
350
351#[derive(Clone, Copy, PartialEq, Eq)]
357pub enum SpectralFn {
358 Re,
359 Log,
360 Sqrt,
361 InvSqrt,
362}
363
364fn spectral_fmat(
368 g: &mut Graph,
369 av: NodeId,
370 n: usize,
371 eps: f64,
372 f: SpectralFn,
373 dt: DType,
374) -> (NodeId, Vec<NodeId>) {
375 let epsn = cscalar(g, eps, dt);
376 let half = cscalar(g, 0.5, dt);
377 let one = cscalar(g, 1.0, dt);
378 let mut terms: Vec<NodeId> = Vec::with_capacity(n);
379 let mut lam_parts: Vec<NodeId> = Vec::with_capacity(n);
380 for i in 0..n {
381 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);
386 let diff = g.sub(lam, epsn);
387 let d2 = g.mul(diff, diff);
388 let ad = g.sqrt(d2);
389 let sad = g.add(sumv, ad);
390 let mx = g.mul(half, sad);
391 let fl = match f {
392 SpectralFn::Re => mx,
393 SpectralFn::Log => g.activation(Activation::Log, mx, Shape::new(&[1, 1], dt)),
394 SpectralFn::Sqrt => g.sqrt(mx),
395 SpectralFn::InvSqrt => {
396 let s = g.sqrt(mx);
397 g.div(one, s)
398 }
399 };
400 let mut eii = vec![0f64; n * n];
401 eii[i * n + i] = 1.0;
402 let eii = cmat(g, &eii, &[n, n], dt);
403 terms.push(g.mul(fl, eii));
404 }
405 let mut fmat = terms[0];
406 for t in &terms[1..] {
407 fmat = g.add(fmat, *t);
408 }
409 (fmat, lam_parts)
410}
411
412pub fn spectral_matfn(
416 g: &mut Graph,
417 a: NodeId,
418 n: usize,
419 sweeps: u32,
420 eps: f64,
421 f: SpectralFn,
422) -> NodeId {
423 let dt = g.shape(a).dtype();
424 let (av, vv) = eigensolve(g, a, n, sweeps, dt);
425 let (fmat, _) = spectral_fmat(g, av, n, eps, f, dt);
426 let vt = g.transpose_(vv, vec![1, 0]);
427 let vf = g.mm(vv, fmat);
428 g.mm(vf, vt)
429}
430
431fn ident_mat3(g: &mut Graph, n: usize, dt: DType) -> NodeId {
444 let mut m = vec![0f64; n * n];
445 for i in 0..n {
446 m[i * n + i] = 1.0;
447 }
448 cmat(g, &m, &[1, n, n], dt)
449}
450
451fn jacobi_cs(
454 g: &mut Graph,
455 app: NodeId,
456 aqq: NodeId,
457 apq: NodeId,
458 dt: DType,
459) -> (NodeId, NodeId, NodeId) {
460 let one = cscalar(g, 1.0, dt);
461 let two = cscalar(g, 2.0, dt);
462 let tiny = cscalar(g, 1e-30, dt);
463 let sbias = cscalar(g, 1e-12, dt);
464 let floor = cscalar(g, 1e-6, dt);
465 let num = g.sub(aqq, app);
466 let den0 = g.mul(two, apq);
467 let den0b = g.add(den0, sbias);
468 let den0sq = g.mul(den0b, den0b);
469 let den0abs = g.sqrt(den0sq);
470 let den0abst = g.add(den0abs, tiny);
471 let sgnden = g.div(den0b, den0abst);
472 let sfloor = g.mul(sgnden, floor);
473 let den = g.add(den0, sfloor);
474 let tau = g.div(num, den);
475 let tau2 = g.mul(tau, tau);
476 let atau = g.sqrt(tau2);
477 let satau = g.add(atau, tiny);
478 let sgn = g.div(tau, satau);
479 let tau2p1 = g.add(tau2, one);
480 let rt = g.sqrt(tau2p1);
481 let tden = g.add(atau, rt);
482 let t = g.div(sgn, tden);
483 let t2 = g.mul(t, t);
484 let t2p1 = g.add(t2, one);
485 let rc = g.sqrt(t2p1);
486 let c = g.div(one, rc);
487 let s = g.mul(t, c);
488 let cm1 = g.sub(c, one);
489 let negs = g.neg(s);
490 (cm1, s, negs)
491}
492
493#[allow(clippy::too_many_arguments)]
496fn one_round_b(
497 g: &mut Graph,
498 av: NodeId,
499 vv: NodeId,
500 spp: NodeId,
501 sqq: NodeId,
502 ident_n: NodeId,
503 ident_k: NodeId,
504 dt: DType,
505) -> (NodeId, NodeId) {
506 let sppt = g.transpose_(spp, vec![0, 2, 1]); let sqqt = g.transpose_(sqq, vec![0, 2, 1]);
509 let tpp = g.mm(sppt, av); let m_pp = g.mm(tpp, spp); let app = {
512 let d = g.mul(m_pp, ident_k);
513 g.sum(d, vec![2], true)
514 }; let tqq = g.mm(sqqt, av);
516 let m_qq = g.mm(tqq, sqq);
517 let aqq = {
518 let d = g.mul(m_qq, ident_k);
519 g.sum(d, vec![2], true)
520 };
521 let m_pq = g.mm(tpp, sqq);
522 let apq = {
523 let d = g.mul(m_pq, ident_k);
524 g.sum(d, vec![2], true)
525 };
526 let (cm1, s, negs) = jacobi_cs(g, app, aqq, apq, dt);
527 let d_cm1 = g.mul(cm1, ident_k); let d_s = g.mul(s, ident_k);
529 let d_negs = g.mul(negs, ident_k);
530 let pp_diag = {
531 let l = g.mm(spp, d_cm1);
532 g.mm(l, sppt)
533 };
534 let qq_diag = {
535 let l = g.mm(sqq, d_cm1);
536 g.mm(l, sqqt)
537 };
538 let pq = {
539 let l = g.mm(spp, d_s);
540 g.mm(l, sqqt)
541 };
542 let qp = {
543 let l = g.mm(sqq, d_negs);
544 g.mm(l, sppt)
545 };
546 let j1 = g.add(ident_n, pp_diag);
547 let j2 = g.add(j1, qq_diag);
548 let j3 = g.add(j2, pq);
549 let jj = g.add(j3, qp);
550 let jt = g.transpose_(jj, vec![0, 2, 1]); let jta = g.mm(jt, av);
552 let av2 = g.mm(jta, jj);
553 let vv2 = g.mm(vv, jj);
554 (av2, vv2)
555}
556
557fn round_body_b(batch: usize, n: usize, k: usize, dt: DType) -> Graph {
559 let mut body = Graph::new("jacobi_round_b");
560 let carry = body.input("carry", Shape::new(&[batch, 2 * n, n], dt));
561 let spp = body.input("spp", Shape::new(&[n, k], dt));
562 let sqq = body.input("sqq", Shape::new(&[n, k], dt));
563 let a_in = body.narrow_(carry, 1, 0, n); let v_in = body.narrow_(carry, 1, n, n);
565 let _ = batch;
568 let spp3 = body.reshape_(spp, vec![1, n as i64, k as i64]);
569 let sqq3 = body.reshape_(sqq, vec![1, n as i64, k as i64]);
570 let ident_n = ident_mat3(&mut body, n, dt);
571 let ident_k = ident_mat3(&mut body, k, dt);
572 let (a_out, v_out) = one_round_b(&mut body, a_in, v_in, spp3, sqq3, ident_n, ident_k, dt);
573 let out = body.concat_(vec![a_out, v_out], 1); body.set_outputs(vec![out]);
575 body
576}
577
578fn eigensolve_b(
580 g: &mut Graph,
581 a: NodeId,
582 batch: usize,
583 n: usize,
584 sweeps: u32,
585 dt: DType,
586) -> (NodeId, NodeId) {
587 let mut ivdata = vec![0f64; batch * n * n];
590 for b in 0..batch {
591 for i in 0..n {
592 ivdata[b * n * n + i * n + i] = 1.0;
593 }
594 }
595 let iv = cmat(g, &ivdata, &[batch, n, n], dt);
596 if n < 2 {
597 return (a, iv);
598 }
599 let mut carry = g.concat_(vec![a, iv], 1); let ne = if n.is_multiple_of(2) { n } else { n + 1 };
602 let k = ne / 2;
603 let rounds = round_robin_rounds(ne);
604 let nr = rounds.len();
605 let mut spp = vec![0f64; nr * n * k];
606 let mut sqq = vec![0f64; nr * n * k];
607 for (r, round) in rounds.iter().enumerate() {
608 for (i, &(p, q)) in round.iter().enumerate() {
609 if p < n {
610 spp[r * n * k + p * k + i] = 1.0;
611 }
612 if q < n {
613 sqq[r * n * k + q * k + i] = 1.0;
614 }
615 }
616 }
617 let spp_c = cmat(g, &spp, &[nr, n, k], dt);
618 let sqq_c = cmat(g, &sqq, &[nr, n, k], dt);
619 let xs = [spp_c, sqq_c];
620 for _ in 0..sweeps {
621 let body = round_body_b(batch, n, k, dt);
622 carry = g.scan_with_xs(carry, &xs, body, nr as u32);
623 }
624 let av = g.narrow_(carry, 1, 0, n);
625 let vv = g.narrow_(carry, 1, n, n);
626 (av, vv)
627}
628
629pub fn spectral_matfn_batched(
632 g: &mut Graph,
633 a: NodeId,
634 batch: usize,
635 n: usize,
636 sweeps: u32,
637 eps: f64,
638 f: SpectralFn,
639) -> NodeId {
640 let dt = g.shape(a).dtype();
641 let ident_n = ident_mat3(g, n, dt); let (av, vv) = eigensolve_b(g, a, batch, n, sweeps, dt);
643 let epsn = cscalar(g, eps, dt);
645 let half = cscalar(g, 0.5, dt);
646 let one = cscalar(g, 1.0, dt);
647 let diag_av = {
648 let d = g.mul(av, ident_n);
649 g.sum(d, vec![2], true)
650 }; let sumv = g.add(diag_av, epsn);
652 let diff = g.sub(diag_av, epsn);
653 let d2 = g.mul(diff, diff);
654 let ad = g.sqrt(d2);
655 let sad = g.add(sumv, ad);
656 let mx = g.mul(half, sad); let fl = match f {
658 SpectralFn::Re => mx,
659 SpectralFn::Log => g.activation(Activation::Log, mx, Shape::new(&[batch, n, 1], dt)),
660 SpectralFn::Sqrt => g.sqrt(mx),
661 SpectralFn::InvSqrt => {
662 let s = g.sqrt(mx);
663 g.div(one, s)
664 }
665 };
666 let fmat = g.mul(fl, ident_n); let vt = g.transpose_(vv, vec![0, 2, 1]);
668 let vf = g.mm(vv, fmat);
669 g.mm(vf, vt)
670}
671
672impl Graph {
673 pub fn spd_reeig_batched(
676 &mut self,
677 x: NodeId,
678 batch: usize,
679 n: usize,
680 sweeps: u32,
681 eps: f64,
682 ) -> NodeId {
683 spectral_matfn_batched(self, x, batch, n, sweeps, eps, SpectralFn::Re)
684 }
685
686 pub fn spd_logeig_batched(
688 &mut self,
689 x: NodeId,
690 batch: usize,
691 n: usize,
692 sweeps: u32,
693 eps: f64,
694 ) -> NodeId {
695 spectral_matfn_batched(self, x, batch, n, sweeps, eps, SpectralFn::Log)
696 }
697}
698
699pub fn bimap(g: &mut Graph, w: NodeId, x: NodeId) -> NodeId {
702 let wt = g.transpose_(w, vec![1, 0]);
703 let wx = g.mm(w, x);
704 g.mm(wx, wt)
705}
706
707pub fn spd_batch_norm_transport(
711 g: &mut Graph,
712 x: NodeId,
713 mean: NodeId,
714 g_: NodeId,
715 n: usize,
716 batch: usize,
717 sweeps: u32,
718 eps: f64,
719) -> NodeId {
720 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);
723 for bi in 0..batch {
724 let xi = g.narrow_(x, 0, bi, 1);
725 let xi = g.reshape_(xi, vec![n as i64, n as i64]);
726 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]));
731 }
732 g.concat_(slices, 0)
733}
734
735pub fn spectral_reeig(g: &mut Graph, a: NodeId, n: usize, sweeps: u32, eps: f64) -> NodeId {
738 spectral_matfn(g, a, n, sweeps, eps, SpectralFn::Re)
739}
740
741pub fn spectral_logeig(g: &mut Graph, a: NodeId, n: usize, sweeps: u32, eps: f64) -> NodeId {
744 spectral_matfn(g, a, n, sweeps, eps, SpectralFn::Log)
745}
746
747pub fn spectral_packed(
752 g: &mut Graph,
753 a: NodeId,
754 n: usize,
755 sweeps: u32,
756 eps: f64,
757 log: bool,
758) -> NodeId {
759 let dt = g.shape(a).dtype();
760 let (av, vv) = eigensolve(g, a, n, sweeps, dt);
761 let f = if log { SpectralFn::Log } else { SpectralFn::Re };
762 let (fmat, lam_parts) = spectral_fmat(g, av, n, eps, f, dt);
763 let vt = g.transpose_(vv, vec![1, 0]);
764 let vf = g.mm(vv, fmat);
765 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) }
771
772impl Graph {
773 pub fn spectral_reeig(&mut self, x: NodeId, sweeps: u32, eps: f64) -> NodeId {
776 let n = self.shape(x).dim(0).unwrap_static();
777 spectral_reeig(self, x, n, sweeps, eps)
778 }
779
780 pub fn spectral_logeig(&mut self, x: NodeId, sweeps: u32, eps: f64) -> NodeId {
783 let n = self.shape(x).dim(0).unwrap_static();
784 spectral_logeig(self, x, n, sweeps, eps)
785 }
786}