spark_model/layers/deepseek_v41_ref/
moe.rs1use super::to_bf16_rne;
17
18fn softplus(x: f32) -> f32 {
20 if x > 20.0 { x } else { x.exp().ln_1p() }
21}
22
23fn silu(x: f32) -> f32 {
24 x / (1.0 + (-x).exp())
25}
26
27pub fn linear_bf16(x: &[f32], w: &[f32], rows: usize, in_dim: usize, out_dim: usize) -> Vec<f32> {
30 let mut y = vec![0f32; rows * out_dim];
31 for i in 0..rows {
32 let xi = &x[i * in_dim..(i + 1) * in_dim];
33 for o in 0..out_dim {
34 let wo = &w[o * in_dim..(o + 1) * in_dim];
35 let acc: f32 = xi.iter().zip(wo).map(|(a, b)| a * b).sum();
36 y[i * out_dim + o] = to_bf16_rne(acc);
37 }
38 }
39 y
40}
41
42pub fn gate(
47 x: &[f32],
48 w: &[f32],
49 bias: &[f32],
50 tokens: usize,
51 dim: usize,
52 n_routed: usize,
53 topk: usize,
54 gate_temp: f32,
55 norm_topk_prob: bool,
56 route_scale: f32,
57) -> (Vec<f32>, Vec<usize>) {
58 let mut weights = Vec::with_capacity(tokens * topk);
59 let mut indices = Vec::with_capacity(tokens * topk);
60 for t in 0..tokens {
61 let xt = &x[t * dim..(t + 1) * dim];
62 let scores: Vec<f32> = (0..n_routed)
63 .map(|e| {
64 let s: f32 = xt
65 .iter()
66 .zip(&w[e * dim..(e + 1) * dim])
67 .map(|(a, b)| a * b)
68 .sum();
69 softplus(s / gate_temp).sqrt()
70 })
71 .collect();
72 let mut order: Vec<usize> = (0..n_routed).collect();
74 order.sort_by(|&a, &b| {
75 (scores[b] + bias[b])
76 .partial_cmp(&(scores[a] + bias[a]))
77 .expect("finite scores")
78 });
79 let picked = &order[..topk];
80 let mut wt: Vec<f32> = picked.iter().map(|&e| scores[e]).collect();
81 if norm_topk_prob && topk > 1 {
82 let sum: f32 = wt.iter().sum::<f32>() + 1e-20;
83 for v in &mut wt {
84 *v /= sum;
85 }
86 }
87 for v in &mut wt {
88 *v *= route_scale;
89 }
90 weights.extend(wt);
91 indices.extend_from_slice(picked);
92 }
93 (weights, indices)
94}
95
96pub fn expert(
100 x: &[f32],
101 w1: &[f32],
102 w2: &[f32],
103 w3: &[f32],
104 rows: usize,
105 dim: usize,
106 inter: usize,
107 swiglu_limit: f32,
108 weights: Option<&[f32]>,
109) -> Vec<f32> {
110 let gate_b = linear_bf16(x, w1, rows, dim, inter);
111 let up_b = linear_bf16(x, w3, rows, dim, inter);
112 let mut h = vec![0f32; rows * inter];
113 for r in 0..rows {
114 for j in 0..inter {
115 let mut g = gate_b[r * inter + j];
116 let mut u = up_b[r * inter + j];
117 if swiglu_limit > 0.0 {
118 u = u.clamp(-swiglu_limit, swiglu_limit);
119 g = g.min(swiglu_limit);
120 }
121 let mut v = silu(g) * u;
122 if let Some(wr) = weights {
123 v *= wr[r];
124 }
125 h[r * inter + j] = to_bf16_rne(v);
126 }
127 }
128 linear_bf16(&h, w2, rows, inter, dim)
129}
130
131pub struct MoeWeights<'a> {
133 pub gate_w: &'a [f32],
134 pub gate_bias: &'a [f32],
135 pub experts: Vec<(&'a [f32], &'a [f32], &'a [f32])>,
136 pub shared: (&'a [f32], &'a [f32], &'a [f32]),
137}
138
139pub struct MoeCfg {
140 pub dim: usize,
141 pub inter: usize,
142 pub n_routed: usize,
143 pub topk: usize,
144 pub gate_temp: f32,
145 pub norm_topk_prob: bool,
146 pub route_scale: f32,
147 pub swiglu_limit: f32,
148}
149
150pub fn moe(
153 x: &[f32],
154 tokens: usize,
155 w: &MoeWeights,
156 c: &MoeCfg,
157) -> (Vec<f32>, Vec<f32>, Vec<usize>) {
158 let (weights, indices) = gate(
159 x,
160 w.gate_w,
161 w.gate_bias,
162 tokens,
163 c.dim,
164 c.n_routed,
165 c.topk,
166 c.gate_temp,
167 c.norm_topk_prob,
168 c.route_scale,
169 );
170 let mut y = vec![0f32; tokens * c.dim];
171 for (e, (w1, w2, w3)) in w.experts.iter().enumerate() {
172 let mut rows = Vec::new();
174 let mut rw = Vec::new();
175 for t in 0..tokens {
176 for k in 0..c.topk {
177 if indices[t * c.topk + k] == e {
178 rows.push(t);
179 rw.push(weights[t * c.topk + k]);
180 }
181 }
182 }
183 if rows.is_empty() {
184 continue;
185 }
186 let xs: Vec<f32> = rows
187 .iter()
188 .flat_map(|&t| x[t * c.dim..(t + 1) * c.dim].iter().copied())
189 .collect();
190 let out = expert(
191 &xs,
192 w1,
193 w2,
194 w3,
195 rows.len(),
196 c.dim,
197 c.inter,
198 c.swiglu_limit,
199 Some(&rw),
200 );
201 for (i, &t) in rows.iter().enumerate() {
202 for d in 0..c.dim {
203 y[t * c.dim + d] += out[i * c.dim + d];
204 }
205 }
206 }
207 let (s1, s2, s3) = w.shared;
208 let shared = expert(x, s1, s2, s3, tokens, c.dim, c.inter, c.swiglu_limit, None);
209 for (yv, sv) in y.iter_mut().zip(&shared) {
210 *yv += sv;
211 }
212 (y.into_iter().map(to_bf16_rne).collect(), weights, indices)
213}
214
215#[cfg(test)]
216#[path = "moe_tests.rs"]
217mod tests;