1#![allow(clippy::too_many_arguments)]
14
15use super::attnres::rms_norm;
16use super::situ::{sigmoid, situ_glu_vec};
17use crate::config::ModelConfig;
18
19#[derive(Clone, Copy, Debug)]
20pub struct LatentMoeConfig {
21 pub hidden: usize,
22 pub latent: usize,
23 pub expert_hidden: usize,
24 pub n_routed: usize,
25 pub top_k: usize,
26 pub n_shared: usize,
27 pub situ_beta: f32,
28 pub situ_linear_beta: f32,
29 pub use_norm: bool,
30 pub renormalize: bool,
31}
32
33impl LatentMoeConfig {
34 pub fn production() -> Self {
35 Self {
36 hidden: 7168,
37 latent: 3584,
38 expert_hidden: 3072,
39 n_routed: 896,
40 top_k: 16,
41 n_shared: 2,
42 situ_beta: 4.0,
43 situ_linear_beta: 25.0,
44 use_norm: true,
45 renormalize: true,
46 }
47 }
48}
49
50pub fn moe_from(c: &ModelConfig) -> LatentMoeConfig {
51 LatentMoeConfig {
52 hidden: c.hidden_size,
53 latent: c.moe_latent_size,
54 expert_hidden: c.moe_intermediate_size,
55 n_routed: c.num_experts,
56 top_k: c.num_experts_per_tok,
57 n_shared: c.n_shared_experts,
58 situ_beta: c.activation_situ_beta,
59 situ_linear_beta: c.activation_situ_linear_beta,
60 use_norm: c.latent_moe_use_norm,
61 renormalize: c.norm_topk_prob,
62 }
63}
64
65pub fn sigmoid_topk(logits: &[f32], bias: &[f32], k: usize) -> (Vec<usize>, Vec<f32>) {
67 assert_eq!(logits.len(), bias.len());
68 let n = logits.len();
69 let k = k.min(n);
70 let scores: Vec<f32> = logits.iter().map(|l| sigmoid(*l)).collect();
71 let mut idx: Vec<usize> = (0..n).collect();
72 idx.sort_by(|&a, &b| {
73 let ca = scores[a] + bias[a];
74 let cb = scores[b] + bias[b];
75 cb.partial_cmp(&ca)
76 .unwrap_or(std::cmp::Ordering::Equal)
77 .then(a.cmp(&b))
78 });
79 idx.truncate(k);
80 let mut w: Vec<f32> = idx.iter().map(|&i| scores[i]).collect();
81 let z: f32 = w.iter().sum();
82 if z > 0.0 {
83 for ww in &mut w {
84 *ww /= z;
85 }
86 }
87 (idx, w)
88}
89
90pub fn expert_situ(
92 x: &[f32],
93 w1: &[f32],
94 w2: &[f32],
95 w3: &[f32],
96 in_dim: usize,
97 hidden: usize,
98 beta: f32,
99 beta_lin: f32,
100) -> Vec<f32> {
101 let gate = matvec(w1, x, hidden, in_dim);
102 let up = matvec(w3, x, hidden, in_dim);
103 let mid = situ_glu_vec(&gate, &up, beta, beta_lin);
104 matvec(w2, &mid, in_dim, hidden)
105}
106
107pub fn mix_routed_experts(
110 latent: &[f32],
111 ids: &[usize],
112 weights: &[f32],
113 experts: &[(Vec<f32>, Vec<f32>, Vec<f32>)],
114 cfg: &LatentMoeConfig,
115) -> Vec<f32> {
116 let mut mixed = vec![0.0f32; cfg.latent];
117 for (&id, &w) in ids.iter().zip(weights) {
118 assert!(id < experts.len(), "expert id {id} >= {}", experts.len());
119 let (w1, w2, w3) = &experts[id];
120 assert!(
121 !w1.is_empty(),
122 "K3 packed expert {id} has no host w1; CUDA grouped GEMM required"
123 );
124 let eh = if cfg.latent == 0 {
125 cfg.expert_hidden
126 } else {
127 w1.len() / cfg.latent
128 };
129 let y = expert_situ(
130 latent,
131 w1,
132 w2,
133 w3,
134 cfg.latent,
135 eh,
136 cfg.situ_beta,
137 cfg.situ_linear_beta,
138 );
139 for (m, yy) in mixed.iter_mut().zip(y) {
140 *m += w * yy;
141 }
142 }
143 mixed
144}
145
146fn matvec(w: &[f32], x: &[f32], out: usize, inn: usize) -> Vec<f32> {
147 assert_eq!(w.len(), out * inn);
148 assert_eq!(x.len(), inn);
149 let mut y = vec![0.0f32; out];
150 for o in 0..out {
151 let mut acc = 0.0f32;
152 let row = &w[o * inn..(o + 1) * inn];
153 for i in 0..inn {
154 acc += row[i] * x[i];
155 }
156 y[o] = acc;
157 }
158 y
159}
160
161pub fn latent_moe_forward(
163 h: &[f32],
164 down: &[f32],
165 up: &[f32],
166 norm_w: &[f32],
167 logits: &[f32],
168 bias: &[f32],
169 experts: &[(Vec<f32>, Vec<f32>, Vec<f32>)],
170 shared: Option<&[f32]>,
171 cfg: &LatentMoeConfig,
172 eps: f32,
173) -> (Vec<f32>, Vec<usize>) {
174 let latent = matvec(down, h, cfg.latent, cfg.hidden);
175 let (ids, weights) = sigmoid_topk(logits, bias, cfg.top_k);
176 let mut mixed = mix_routed_experts(&latent, &ids, &weights, experts, cfg);
177 if cfg.use_norm {
178 mixed = rms_norm(&mixed, norm_w, eps);
179 }
180 let mut out = matvec(up, &mixed, cfg.hidden, cfg.latent);
181 if let Some(s) = shared {
182 for (o, ss) in out.iter_mut().zip(s) {
183 *o += ss;
184 }
185 }
186 (out, ids)
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn sigmoid_topk_picks_highest() {
195 let logits = [0.1, 5.0, 0.2, 4.0];
196 let bias = [0.0, 0.0, 0.0, 0.0];
197 let (ids, w) = sigmoid_topk(&logits, &bias, 2);
198 assert_eq!(ids, vec![1, 3]);
199 assert!((w[0] + w[1] - 1.0).abs() < 1e-6);
200 assert!(w[0] > w[1]);
201 }
202
203 #[test]
204 fn noaux_tc_bias_ranks_not_weighted() {
205 let logits = [0.0f32, 0.0, 0.0];
207 let bias = [0.0, 5.0, 4.0];
208 let (ids, w) = sigmoid_topk(&logits, &bias, 2);
209 assert_eq!(ids, vec![1, 2]);
210 assert!(
211 (w[0] - w[1]).abs() < 1e-5,
212 "equal logits must keep equal mix weights, got {w:?}"
213 );
214 }
215
216 #[test]
217 fn force_expert_zero_mutant_diverges() {
218 let cfg = LatentMoeConfig {
220 hidden: 2,
221 latent: 2,
222 expert_hidden: 2,
223 n_routed: 2,
224 top_k: 1,
225 n_shared: 0,
226 situ_beta: 4.0,
227 situ_linear_beta: 25.0,
228 use_norm: false,
229 renormalize: true,
230 };
231 let h = vec![1.0, 0.0];
232 let down = vec![1.0, 0.0, 0.0, 1.0];
233 let up = down.clone();
234 let ident = |out: usize, inn: usize| -> Vec<f32> {
235 let mut w = vec![0.0; out * inn];
236 for i in 0..out.min(inn) {
237 w[i * inn + i] = 1.0;
238 }
239 w
240 };
241 let e0 = (ident(2, 2), ident(2, 2), ident(2, 2));
242 let mut w3 = ident(2, 2);
244 w3[0] = 3.0;
245 let e1 = (ident(2, 2), ident(2, 2), w3);
246 let experts = [e0, e1];
247 let logits = [0.0, 4.0];
248 let bias = [0.0, 0.0];
249 let (y, ids) = latent_moe_forward(
250 &h,
251 &down,
252 &up,
253 &[1.0, 1.0],
254 &logits,
255 &bias,
256 &experts,
257 None,
258 &cfg,
259 1e-5,
260 );
261 assert_eq!(ids, vec![1]);
262 let (y0, _) = latent_moe_forward(
263 &h,
264 &down,
265 &up,
266 &[1.0, 1.0],
267 &[4.0, 0.0],
268 &bias,
269 &experts,
270 None,
271 &cfg,
272 1e-5,
273 );
274 let err: f32 = y.iter().zip(&y0).map(|(a, b)| (a - b).abs()).sum();
275 assert!(err > 1e-4, "forcing expert 0 must diverge, err={err}");
276 }
277}