spark_model/layers/glm5next_dsa/
build.rs1use anyhow::{Result, bail};
29use spark_runtime::gpu::{DevicePtr, GpuBackend};
30
31use super::Glm5NextDsaConfig;
32use super::layer::Glm5NextDsaWeights;
33use super::tp::{DsaShard, DsaTpPlan};
34
35pub type LoadFn<'a> = &'a dyn Fn(&str) -> Result<Vec<f32>>;
37
38fn absorb_threads(rows: usize) -> usize {
48 let want = std::env::var("ATLAS_MLA_ABSORB_THREADS")
49 .ok()
50 .and_then(|v| v.trim().parse::<usize>().ok())
51 .filter(|&n| n > 0)
52 .unwrap_or_else(|| {
53 std::thread::available_parallelism()
54 .map(|n| n.get())
55 .unwrap_or(1)
56 });
57 want.clamp(1, rows.max(1))
58}
59
60#[allow(clippy::too_many_arguments)]
69fn absorb_q_rows(
70 out: &mut [f32],
71 row0: usize,
72 kv_b: &[f32],
73 q_b: &[f32],
74 kvl: usize,
75 ql: usize,
76 nope: usize,
77 vd: usize,
78) {
79 for (i, acc) in out.chunks_exact_mut(ql).enumerate() {
80 let row = row0 + i;
81 let h = row / kvl;
82 let c = row % kvl;
83 let kv_base = h * (nope + vd);
84 let qb_base = h * nope;
85 acc.fill(0.0);
86 for r in 0..nope {
87 let w = kv_b[(kv_base + r) * kvl + c];
88 let q_row = &q_b[(qb_base + r) * ql..][..ql];
89 for (a, &q) in acc.iter_mut().zip(q_row.iter()) {
90 *a += q * w;
91 }
92 }
93 }
94}
95
96pub fn absorb_q(
99 cfg: &Glm5NextDsaConfig,
100 q_b: &[f32],
101 kv_b: &[f32],
102 full_heads: usize,
103) -> Result<Vec<f32>> {
104 let (nope, vd, kvl, ql) = (
105 cfg.qk_nope_head_dim,
106 cfg.v_head_dim,
107 cfg.kv_lora_rank,
108 cfg.q_lora_rank,
109 );
110 let qk = cfg.qk_head_dim();
111 if q_b.len() != full_heads * qk * ql {
112 bail!(
113 "absorb_q: q_b_proj has {} elems, expected {}",
114 q_b.len(),
115 full_heads * qk * ql
116 );
117 }
118 if kv_b.len() != full_heads * (nope + vd) * kvl {
119 bail!(
120 "absorb_q: kv_b_proj has {} elems, expected {}",
121 kv_b.len(),
122 full_heads * (nope + vd) * kvl
123 );
124 }
125 if cfg.qk_rope_head_dim != 0 {
128 bail!(
129 "absorb_q: NoPE only; qk_rope_head_dim is {}",
130 cfg.qk_rope_head_dim
131 );
132 }
133
134 let rows = full_heads * kvl;
135 let mut out = vec![0f32; rows * ql];
136 let threads = absorb_threads(rows);
137 if threads <= 1 {
138 absorb_q_rows(&mut out, 0, kv_b, q_b, kvl, ql, nope, vd);
139 } else {
140 let rows_per = rows.div_ceil(threads);
141 std::thread::scope(|scope| {
142 for (chunk_idx, chunk) in out.chunks_mut(rows_per * ql).enumerate() {
143 let row0 = chunk_idx * rows_per;
144 scope.spawn(move || {
145 absorb_q_rows(chunk, row0, kv_b, q_b, kvl, ql, nope, vd);
146 });
147 }
148 });
149 }
150 Ok(out)
151}
152
153pub fn absorb_o(
170 cfg: &Glm5NextDsaConfig,
171 o_local: &[f32],
172 kv_b_local: &[f32],
173 local_heads: usize,
174) -> Result<Vec<f32>> {
175 let (nope, vd, kvl, hidden) = (
176 cfg.qk_nope_head_dim,
177 cfg.v_head_dim,
178 cfg.kv_lora_rank,
179 cfg.hidden,
180 );
181 if cfg.qk_rope_head_dim != 0 {
182 bail!(
183 "absorb_o: NoPE only; qk_rope_head_dim is {}",
184 cfg.qk_rope_head_dim
185 );
186 }
187 let in_w = local_heads * vd;
188 let out_w = local_heads * kvl;
189 if o_local.len() != hidden * in_w {
190 bail!(
191 "absorb_o: o_proj has {} elems, expected {} ([{hidden}, {in_w}])",
192 o_local.len(),
193 hidden * in_w
194 );
195 }
196 if kv_b_local.len() != local_heads * (nope + vd) * kvl {
197 bail!(
198 "absorb_o: kv_b_proj slice has {} elems, expected {}",
199 kv_b_local.len(),
200 local_heads * (nope + vd) * kvl
201 );
202 }
203
204 let mut out = vec![0f32; hidden * out_w];
205 let threads = std::thread::available_parallelism()
210 .map(|n| n.get())
211 .unwrap_or(1)
212 .clamp(1, hidden);
213 let rows = hidden.div_ceil(threads);
214 std::thread::scope(|sc| {
215 for (o_chunk, i_chunk) in out
216 .chunks_mut(rows * out_w)
217 .zip(o_local.chunks(rows * in_w))
218 {
219 sc.spawn(move || {
220 for (dst_row, src_row) in o_chunk.chunks_mut(out_w).zip(i_chunk.chunks(in_w)) {
221 for h in 0..local_heads {
222 let dst = &mut dst_row[h * kvl..(h + 1) * kvl];
223 let v_base = h * (nope + vd) + nope;
224 for r in 0..vd {
225 let w = src_row[h * vd + r];
226 let src = &kv_b_local[(v_base + r) * kvl..(v_base + r) * kvl + kvl];
227 for (d, k) in dst.iter_mut().zip(src) {
228 *d += w * k;
229 }
230 }
231 }
232 }
233 });
234 }
235 });
236 Ok(out)
237}
238
239fn row_slice(v: &[f32], row_elems: usize, start: usize, end: usize) -> Vec<f32> {
241 v[start * row_elems..end * row_elems].to_vec()
242}
243
244fn col_slice(v: &[f32], row_elems: usize, start: usize, end: usize) -> Vec<f32> {
246 v.chunks(row_elems)
247 .flat_map(|r| r[start..end].iter().copied())
248 .collect()
249}
250
251pub fn shard_host(plan: &super::tp::DsaTensorPlan, full: &[f32]) -> Vec<f32> {
253 match plan.kind {
254 DsaShard::Replicated => full.to_vec(),
255 DsaShard::HeadRows => row_slice(
256 full,
257 plan.full_row_elems,
258 plan.src_row_offset,
259 plan.src_row_offset + plan.local_rows,
260 ),
261 DsaShard::HeadCols => col_slice(
262 full,
263 plan.full_row_elems,
264 plan.src_col_offset,
265 plan.src_col_offset + plan.local_row_elems,
266 ),
267 }
268}
269
270fn up_bf16(gpu: &dyn GpuBackend, v: &[f32]) -> Result<DevicePtr> {
271 let b: Vec<u8> = v
272 .iter()
273 .flat_map(|x| half::bf16::from_f32(*x).to_le_bytes())
274 .collect();
275 let p = gpu.alloc(b.len().max(1))?;
276 gpu.copy_h2d(&b, p)?;
277 Ok(p)
278}
279fn up_f32(gpu: &dyn GpuBackend, v: &[f32]) -> Result<DevicePtr> {
280 let b: Vec<u8> = v.iter().flat_map(|x| x.to_le_bytes()).collect();
281 let p = gpu.alloc(b.len().max(1))?;
282 gpu.copy_h2d(&b, p)?;
283 Ok(p)
284}
285
286pub fn build_dsa_weights(
288 gpu: &dyn GpuBackend,
289 cfg: &Glm5NextDsaConfig,
290 plan: &DsaTpPlan,
291 load: LoadFn<'_>,
292) -> Result<Glm5NextDsaWeights> {
293 let get = |n: &str| -> Result<Vec<f32>> { load(&format!("self_attn.{n}")) };
294 let shard = |n: &'static str, full: Vec<f32>| -> Result<Vec<f32>> {
295 let p = plan
296 .get(n)
297 .ok_or_else(|| anyhow::anyhow!("no shard plan for {n}"))?;
298 Ok(shard_host(p, &full))
299 };
300
301 let kv_b = get("kv_b_proj.weight")?;
302
303 let q_absorb_full = absorb_q(cfg, &get("q_b_proj.weight")?, &kv_b, plan.full_heads)?;
307 let per_head = cfg.kv_lora_rank;
308 let start = plan.tp_rank * plan.local_heads * per_head;
309 let len = plan.local_heads * per_head;
310 let q_absorb = row_slice(&q_absorb_full, cfg.q_lora_rank, start, start + len);
311
312 let kv_b_rows = cfg.qk_nope_head_dim + cfg.v_head_dim;
316 let kv_b_local = row_slice(
317 &kv_b,
318 cfg.kv_lora_rank,
319 plan.tp_rank * plan.local_heads * kv_b_rows,
320 (plan.tp_rank + 1) * plan.local_heads * kv_b_rows,
321 );
322 let o_absorb = absorb_o(
323 cfg,
324 &shard("o_proj", get("o_proj.weight")?)?,
325 &kv_b_local,
326 plan.local_heads,
327 )?;
328
329 let scale = (cfg.index_heads as f32).powf(-0.5);
331 let weights_proj: Vec<f32> = get("indexer.weights_proj.weight")?
332 .iter()
333 .map(|x| x * scale)
334 .collect();
335
336 let ape = get("indexer.index_kpool_compress_ape")?;
338
339 Ok(Glm5NextDsaWeights {
340 q_a_proj: up_bf16(gpu, &get("q_a_proj.weight")?)?,
341 q_a_layernorm: up_bf16(gpu, &get("q_a_layernorm.weight")?)?,
342 q_absorb: up_bf16(gpu, &q_absorb)?,
343 kv_a_proj: up_bf16(gpu, &get("kv_a_proj_with_mqa.weight")?)?,
344 kv_a_layernorm: up_bf16(gpu, &get("kv_a_layernorm.weight")?)?,
345 o_absorb: up_bf16(gpu, &o_absorb)?,
346 wk: up_bf16(gpu, &get("indexer.wk.weight")?)?,
347 k_norm_weight: up_bf16(gpu, &get("indexer.k_norm.weight")?)?,
348 k_norm_bias: up_bf16(gpu, &get("indexer.k_norm.bias")?)?,
350 compress_gate: up_bf16(gpu, &get("indexer.index_kpool_compress_gate")?)?,
351 wq_b: up_bf16(gpu, &get("indexer.wq_b.weight")?)?,
352 weights_proj: up_bf16(gpu, &weights_proj)?,
353 ape: up_f32(gpu, &ape)?,
354 })
355}
356
357#[cfg(test)]
358mod tests;