1use anyhow::{Result, bail};
5
6#[cfg(atlas_cutlass)]
7use std::ffi::c_void;
8
9#[cfg(atlas_cutlass)]
10use super::*;
11
12fn ensure_group_arrays(
33 who: &str,
34 num_experts: usize,
35 per_expert: &[(&str, usize)],
36 offsets: &[i32],
37) -> Result<()> {
38 if offsets.len() != num_experts + 1 {
39 bail!(
40 "{who}: expert_offsets len {} != num_experts+1 {}",
41 offsets.len(),
42 num_experts + 1
43 );
44 }
45 for (name, len) in per_expert {
46 if *len != num_experts {
47 bail!("{who}: {name} len {len} != num_experts {num_experts}");
48 }
49 }
50 if offsets[0] < 0 {
51 bail!("{who}: expert_offsets[0] = {} is negative", offsets[0]);
52 }
53 for w in offsets.windows(2) {
54 if w[1] < w[0] {
55 bail!(
56 "{who}: expert_offsets is not non-decreasing ({} then {}) — a group would \
57 have a negative row count",
58 w[0],
59 w[1]
60 );
61 }
62 }
63 Ok(())
64}
65
66#[allow(clippy::too_many_arguments)]
79pub fn nvfp4_grouped_gate_up(
80 a: u64,
81 gate_packed_ptrs: &[u64],
82 gate_scale_ptrs: &[u64],
83 gate_scale2_vals: &[f32],
84 up_packed_ptrs: &[u64],
85 up_scale_ptrs: &[u64],
86 up_scale2_vals: &[f32],
87 c_gate: u64,
88 c_up: u64,
89 expert_offsets: &[i32],
90 n: u32,
91 k: u32,
92 stream: u64,
93) -> Result<()> {
94 let num_experts = gate_packed_ptrs.len();
95 ensure_group_arrays(
96 "nvfp4_grouped_gate_up",
97 num_experts,
98 &[
99 ("gate_scale_ptrs", gate_scale_ptrs.len()),
100 ("gate_scale2_vals", gate_scale2_vals.len()),
101 ("up_packed_ptrs", up_packed_ptrs.len()),
102 ("up_scale_ptrs", up_scale_ptrs.len()),
103 ("up_scale2_vals", up_scale2_vals.len()),
104 ],
105 expert_offsets,
106 )?;
107 #[cfg(atlas_cutlass)]
108 {
109 let ctx = ctx()?;
110 let status = unsafe {
111 atlas_cutlass_nvfp4_grouped_gate_up(
112 a as *const c_void,
113 gate_packed_ptrs.as_ptr(),
114 gate_scale_ptrs.as_ptr(),
115 gate_scale2_vals.as_ptr(),
116 up_packed_ptrs.as_ptr(),
117 up_scale_ptrs.as_ptr(),
118 up_scale2_vals.as_ptr(),
119 c_gate as *mut c_void,
120 c_up as *mut c_void,
121 expert_offsets.as_ptr(),
122 num_experts as i32,
123 n as i32,
124 k as i32,
125 ctx.workspace as *mut c_void,
126 ctx.ws_size,
127 stream as *mut c_void,
128 )
129 };
130 if status != 0 {
131 bail!("CUTLASS nvfp4 grouped gate_up failed: status {status}");
132 }
133 Ok(())
134 }
135 #[cfg(not(atlas_cutlass))]
136 {
137 let _ = (
138 a,
139 gate_packed_ptrs,
140 gate_scale_ptrs,
141 gate_scale2_vals,
142 up_packed_ptrs,
143 up_scale_ptrs,
144 up_scale2_vals,
145 c_gate,
146 c_up,
147 expert_offsets,
148 n,
149 k,
150 stream,
151 );
152 bail!("CUTLASS support was not built; set CUTLASS_HOME when building")
153 }
154}
155
156#[allow(clippy::too_many_arguments)]
168pub fn nvfp4_grouped_gate_up_fused(
169 a: u64,
170 sorted_token_ids: u64,
171 gate_packed_ptrs: &[u64],
172 gate_sfb_ptrs: &[u64],
173 gate_scale2_vals: &[f32],
174 up_packed_ptrs: &[u64],
175 up_sfb_ptrs: &[u64],
176 up_scale2_vals: &[f32],
177 c_gate: u64,
178 c_up: u64,
179 expert_offsets_host: &[i32],
180 n: u32,
181 k: u32,
182 stream: u64,
183) -> Result<()> {
184 let num_experts = gate_packed_ptrs.len();
185 ensure_group_arrays(
186 "nvfp4_grouped_gate_up_fused",
187 num_experts,
188 &[
189 ("gate_sfb_ptrs", gate_sfb_ptrs.len()),
190 ("gate_scale2_vals", gate_scale2_vals.len()),
191 ("up_packed_ptrs", up_packed_ptrs.len()),
192 ("up_sfb_ptrs", up_sfb_ptrs.len()),
193 ("up_scale2_vals", up_scale2_vals.len()),
194 ],
195 expert_offsets_host,
196 )?;
197 #[cfg(atlas_cutlass)]
198 {
199 let ctx = ctx()?;
200 let status = unsafe {
201 atlas_cutlass_nvfp4_grouped_gate_up_fused(
202 a as *const c_void,
203 sorted_token_ids as *const i32,
204 gate_packed_ptrs.as_ptr(),
205 gate_sfb_ptrs.as_ptr(),
206 gate_scale2_vals.as_ptr(),
207 up_packed_ptrs.as_ptr(),
208 up_sfb_ptrs.as_ptr(),
209 up_scale2_vals.as_ptr(),
210 c_gate as *mut c_void,
211 c_up as *mut c_void,
212 expert_offsets_host.as_ptr(),
213 num_experts as i32,
214 n as i32,
215 k as i32,
216 ctx.workspace as *mut c_void,
217 ctx.ws_size,
218 stream as *mut c_void,
219 )
220 };
221 if status != 0 {
222 bail!("CUTLASS nvfp4 grouped(fused) gate_up failed: status {status}");
223 }
224 Ok(())
225 }
226 #[cfg(not(atlas_cutlass))]
227 {
228 let _ = (
229 a,
230 sorted_token_ids,
231 gate_packed_ptrs,
232 gate_sfb_ptrs,
233 gate_scale2_vals,
234 up_packed_ptrs,
235 up_sfb_ptrs,
236 up_scale2_vals,
237 c_gate,
238 c_up,
239 expert_offsets_host,
240 n,
241 k,
242 stream,
243 );
244 bail!("CUTLASS support was not built; set CUTLASS_HOME when building")
245 }
246}
247
248#[allow(clippy::too_many_arguments)]
254pub fn nvfp4_grouped_down(
255 a: u64,
256 packed_ptrs: &[u64],
257 sfb_ptrs: &[u64],
258 scale2_vals: &[f32],
259 c: u64,
260 expert_offsets_host: &[i32],
261 n: u32,
262 k: u32,
263 stream: u64,
264) -> Result<()> {
265 let num_experts = packed_ptrs.len();
266 ensure_group_arrays(
267 "nvfp4_grouped_down",
268 num_experts,
269 &[
270 ("sfb_ptrs", sfb_ptrs.len()),
271 ("scale2_vals", scale2_vals.len()),
272 ],
273 expert_offsets_host,
274 )?;
275 #[cfg(atlas_cutlass)]
276 {
277 let ctx = ctx()?;
278 let status = unsafe {
279 atlas_cutlass_nvfp4_grouped_down(
280 a as *const c_void,
281 packed_ptrs.as_ptr(),
282 sfb_ptrs.as_ptr(),
283 scale2_vals.as_ptr(),
284 c as *mut c_void,
285 expert_offsets_host.as_ptr(),
286 num_experts as i32,
287 n as i32,
288 k as i32,
289 ctx.workspace as *mut c_void,
290 ctx.ws_size,
291 stream as *mut c_void,
292 )
293 };
294 if status != 0 {
295 bail!("CUTLASS nvfp4 grouped down failed: status {status}");
296 }
297 Ok(())
298 }
299 #[cfg(not(atlas_cutlass))]
300 {
301 let _ = (
302 a,
303 packed_ptrs,
304 sfb_ptrs,
305 scale2_vals,
306 c,
307 expert_offsets_host,
308 n,
309 k,
310 stream,
311 );
312 bail!("CUTLASS support was not built; set CUTLASS_HOME when building")
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::ensure_group_arrays;
319
320 #[test]
323 fn rejects_any_short_per_expert_array() {
324 let offsets = [0i32, 4, 9];
325 ensure_group_arrays("t", 2, &[("a", 2), ("b", 2)], &offsets).unwrap();
327 let e = ensure_group_arrays("t", 2, &[("a", 1), ("b", 2)], &offsets).unwrap_err();
330 assert!(e.to_string().contains("a len 1"), "{e}");
331 let e = ensure_group_arrays("t", 2, &[("a", 2), ("b", 1)], &offsets).unwrap_err();
332 assert!(e.to_string().contains("b len 1"), "{e}");
333 assert!(ensure_group_arrays("t", 2, &[("a", 3), ("b", 2)], &offsets).is_err());
336 }
337
338 #[test]
339 fn rejects_bad_expert_offsets() {
340 assert!(
341 ensure_group_arrays("t", 2, &[], &[0i32, 4]).is_err(),
342 "short"
343 );
344 assert!(
345 ensure_group_arrays("t", 2, &[], &[0i32, 4, 9, 12]).is_err(),
346 "long"
347 );
348 assert!(
349 ensure_group_arrays("t", 2, &[], &[-1i32, 4, 9]).is_err(),
350 "negative base"
351 );
352 assert!(
353 ensure_group_arrays("t", 2, &[], &[0i32, 9, 4]).is_err(),
354 "decreasing => negative group row count"
355 );
356 ensure_group_arrays("t", 2, &[], &[0i32, 4, 4]).unwrap();
359 }
360
361 #[test]
364 fn zero_experts_is_not_a_panic() {
365 ensure_group_arrays("t", 0, &[], &[0i32]).unwrap();
366 }
367}