1use crate::{
2 backend::BackendStorage, CpuStorage, DType, Device, Result, Shape, Storage, Tensor, D,
3};
4use iq_quants::*;
5use k_quants::*;
6use std::borrow::Cow;
7use std::sync::Arc;
8
9#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
10pub mod avx;
11pub mod dsv4_qat;
12mod dummy_cuda;
13mod dummy_metal;
14pub mod expert_stream;
15pub mod ggml_file;
16pub mod gguf_file;
17pub mod imatrix_file;
18mod iq_grids;
19pub mod iq_quants;
20pub mod k_quants;
21#[cfg(feature = "metal")]
22pub mod metal;
23pub mod repack;
24#[cfg(target_arch = "x86_64")]
25pub(crate) mod repack_x86;
26#[cfg(not(target_arch = "wasm32"))]
27pub mod tokenizer;
28#[cfg(not(feature = "metal"))]
29mod metal {
30 pub use super::dummy_metal::*;
31}
32#[cfg(feature = "cuda")]
33pub mod cuda;
34#[cfg(feature = "cuda")]
35pub mod fast_mmq;
36#[cfg(feature = "cuda")]
37pub mod fast_mmvq;
38#[cfg(not(feature = "cuda"))]
39mod cuda {
40 pub use super::dummy_cuda::*;
41}
42
43#[cfg(any(
44 target_arch = "aarch64",
45 all(target_arch = "arm", target_feature = "neon")
46))]
47pub mod neon;
48#[cfg(target_feature = "simd128")]
49pub mod simd128;
50pub mod utils;
51pub mod quant_format;
54use half::{bf16, f16};
55
56pub use k_quants::GgmlType;
57
58fn as_t_slice<T>(data: &[u8]) -> &[T] {
62 let size = std::mem::size_of::<T>();
63 assert_eq!(
64 data.len() % size,
65 0,
66 "Data length must be a multiple of T's size"
67 );
68 let ptr = data.as_ptr();
69 assert_eq!(
70 (ptr as usize) % std::mem::align_of::<T>(),
71 0,
72 "Data pointer must be aligned to T's alignment"
73 );
74 unsafe { std::slice::from_raw_parts(ptr as *const T, data.len() / size) }
75}
76
77#[derive(Default)]
82struct ResidentBanks {
83 #[cfg(feature = "rocm")]
84 rocm: std::sync::OnceLock<std::sync::Arc<crate::RocmStorage>>,
85 #[cfg(feature = "vulkan")]
86 vulkan: std::sync::OnceLock<std::sync::Arc<crate::VulkanStorage>>,
87 #[cfg(feature = "vulkan")]
88 vulkan_split: std::sync::OnceLock<std::sync::Arc<crate::vulkan::MoeBankSplit>>,
89 #[cfg(feature = "wgpu")]
90 wgpu: std::sync::OnceLock<std::sync::Arc<crate::WgpuStorage>>,
91}
92
93pub struct QTensor {
94 storage: QStorage,
95 shape: Shape,
96 #[cfg_attr(
103 not(any(feature = "rocm", feature = "vulkan", feature = "wgpu")),
104 allow(dead_code)
105 )]
106 banks: ResidentBanks,
107 #[allow(dead_code)]
108 repacked_qs: repack::PackedCache,
109}
110
111impl Device {
112 fn qzeros(&self, elem_count: usize, dtype: GgmlDType) -> Result<QStorage> {
113 match self {
114 Device::Cpu => {
115 let storage = dtype.cpu_zeros(elem_count);
116 Ok(QStorage::Cpu(storage))
117 }
118 Device::Metal(metal) => {
119 let storage = metal::QMetalStorage::zeros(metal, elem_count, dtype)?;
120 Ok(QStorage::Metal(storage))
121 }
122 Device::Cuda(cuda) => {
123 let storage = cuda::QCudaStorage::zeros(cuda, elem_count, dtype)?;
124 Ok(QStorage::Cuda(storage))
125 }
126 #[cfg(feature = "rocm")]
127 Device::Rocm(d) => {
128 let storage = dtype.cpu_zeros(elem_count);
131 Ok(QStorage::Rocm(storage, d.clone()))
132 }
133 #[cfg(feature = "vulkan")]
134 Device::Vulkan(d) => {
135 let storage = dtype.cpu_zeros(elem_count);
138 Ok(QStorage::Vulkan(storage, d.clone()))
139 }
140 #[cfg(feature = "wgpu")]
141 Device::Wgpu(d) => {
142 let storage = dtype.cpu_zeros(elem_count);
145 Ok(QStorage::Wgpu(storage, d.clone()))
146 }
147 }
148 }
149}
150
151pub enum QStorage {
152 Cpu(Box<dyn QuantizedType>),
153 Metal(metal::QMetalStorage),
154 Cuda(cuda::QCudaStorage),
155 #[cfg(feature = "rocm")]
159 Rocm(Box<dyn QuantizedType>, crate::RocmDevice),
160 #[cfg(feature = "vulkan")]
164 Vulkan(Box<dyn QuantizedType>, crate::VulkanDevice),
165 #[cfg(feature = "wgpu")]
169 Wgpu(Box<dyn QuantizedType>, crate::WgpuDevice),
170 Stream(Arc<expert_stream::ExpertStreamBank>),
174}
175
176impl QStorage {
177 pub fn from_data(data: Cow<'_, [u8]>, device: &Device, dtype: GgmlDType) -> Result<Self> {
178 let data: &[u8] = &data;
179 match device {
180 Device::Cpu => Ok(Self::Cpu(dtype.from_data(Cow::Borrowed(data)))),
181 Device::Metal(d) => match dtype {
182 GgmlDType::F32 => metal::load_quantized(d, as_t_slice::<f32>(&data)),
183 GgmlDType::F16 => metal::load_quantized(d, as_t_slice::<f16>(&data)),
184 GgmlDType::Q4_0 => metal::load_quantized(d, as_t_slice::<BlockQ4_0>(&data)),
185 GgmlDType::Q4_1 => metal::load_quantized(d, as_t_slice::<BlockQ4_1>(&data)),
186 GgmlDType::Q5_0 => metal::load_quantized(d, as_t_slice::<BlockQ5_0>(&data)),
187 GgmlDType::Q5_1 => metal::load_quantized(d, as_t_slice::<BlockQ5_1>(&data)),
188 GgmlDType::Q8_0 => metal::load_quantized(d, as_t_slice::<BlockQ8_0>(&data)),
189 GgmlDType::Q8_1 => metal::load_quantized(d, as_t_slice::<BlockQ8_1>(&data)),
190 GgmlDType::Q2K => metal::load_quantized(d, as_t_slice::<BlockQ2K>(&data)),
191 GgmlDType::Q3K => metal::load_quantized(d, as_t_slice::<BlockQ3K>(&data)),
192 GgmlDType::Q4K => metal::load_quantized(d, as_t_slice::<BlockQ4K>(&data)),
193 GgmlDType::Q5K => metal::load_quantized(d, as_t_slice::<BlockQ5K>(&data)),
194 GgmlDType::Q6K => metal::load_quantized(d, as_t_slice::<BlockQ6K>(&data)),
195 GgmlDType::Q8K => metal::load_quantized(d, as_t_slice::<BlockQ8K>(&data)),
196 GgmlDType::IQ4_NL => metal::load_quantized(d, as_t_slice::<BlockIQ4nl>(&data)),
197 GgmlDType::IQ4_XS => metal::load_quantized(d, as_t_slice::<BlockIQ4xs>(&data)),
198 GgmlDType::MXFP4 => metal::load_quantized(d, as_t_slice::<BlockMXFP4>(&data)),
199 GgmlDType::BF16 => metal::load_quantized(d, as_t_slice::<bf16>(&data)),
200 GgmlDType::I32 => metal::load_quantized(d, as_t_slice::<i32>(&data)),
201 GgmlDType::IQ2_XXS => metal::load_quantized(d, as_t_slice::<BlockIQ2xxs>(&data)),
207 GgmlDType::IQ2_XS => metal::load_quantized(d, as_t_slice::<BlockIQ2xs>(&data)),
208 GgmlDType::IQ2_S => metal::load_quantized(d, as_t_slice::<BlockIQ2s>(&data)),
209 GgmlDType::IQ3_XXS => metal::load_quantized(d, as_t_slice::<BlockIQ3xxs>(&data)),
210 GgmlDType::IQ3_S => metal::load_quantized(d, as_t_slice::<BlockIQ3s>(&data)),
211 GgmlDType::IQ1_S => metal::load_quantized(d, as_t_slice::<BlockIQ1s>(&data)),
212 GgmlDType::IQ1_M => metal::load_quantized(d, as_t_slice::<BlockIQ1m>(&data)),
213 other => crate::bail!("{other:?} is not supported on the Metal backend"),
215 },
216 Device::Cuda(d) => match dtype {
217 GgmlDType::F32 => cuda::load_quantized(d, as_t_slice::<f32>(&data)),
218 GgmlDType::F16 => cuda::load_quantized(d, as_t_slice::<f16>(&data)),
219 GgmlDType::Q4_0 => cuda::load_quantized(d, as_t_slice::<BlockQ4_0>(&data)),
220 GgmlDType::Q4_1 => cuda::load_quantized(d, as_t_slice::<BlockQ4_1>(&data)),
221 GgmlDType::Q5_0 => cuda::load_quantized(d, as_t_slice::<BlockQ5_0>(&data)),
222 GgmlDType::Q5_1 => cuda::load_quantized(d, as_t_slice::<BlockQ5_1>(&data)),
223 GgmlDType::Q8_0 => cuda::load_quantized(d, as_t_slice::<BlockQ8_0>(&data)),
224 GgmlDType::Q8_1 => cuda::load_quantized(d, as_t_slice::<BlockQ8_1>(&data)),
225 GgmlDType::Q2K => cuda::load_quantized(d, as_t_slice::<BlockQ2K>(&data)),
226 GgmlDType::Q3K => cuda::load_quantized(d, as_t_slice::<BlockQ3K>(&data)),
227 GgmlDType::Q4K => cuda::load_quantized(d, as_t_slice::<BlockQ4K>(&data)),
228 GgmlDType::Q5K => cuda::load_quantized(d, as_t_slice::<BlockQ5K>(&data)),
229 GgmlDType::Q6K => cuda::load_quantized(d, as_t_slice::<BlockQ6K>(&data)),
230 GgmlDType::Q8K => cuda::load_quantized(d, as_t_slice::<BlockQ8K>(&data)),
231 GgmlDType::IQ4_NL => cuda::load_quantized(d, as_t_slice::<BlockIQ4nl>(&data)),
232 GgmlDType::IQ4_XS => cuda::load_quantized(d, as_t_slice::<BlockIQ4xs>(&data)),
233 GgmlDType::MXFP4 => cuda::load_quantized(d, as_t_slice::<BlockMXFP4>(&data)),
234 GgmlDType::BF16 => cuda::load_quantized(d, as_t_slice::<bf16>(&data)),
235 GgmlDType::I32 => cuda::load_quantized(d, as_t_slice::<i32>(&data)),
236 GgmlDType::IQ2_XXS => cuda::load_quantized(d, as_t_slice::<BlockIQ2xxs>(&data)),
244 GgmlDType::IQ2_XS => cuda::load_quantized(d, as_t_slice::<BlockIQ2xs>(&data)),
245 GgmlDType::IQ2_S => cuda::load_quantized(d, as_t_slice::<BlockIQ2s>(&data)),
246 GgmlDType::IQ3_XXS => cuda::load_quantized(d, as_t_slice::<BlockIQ3xxs>(&data)),
247 GgmlDType::IQ3_S => cuda::load_quantized(d, as_t_slice::<BlockIQ3s>(&data)),
248 GgmlDType::IQ1_S => cuda::load_quantized(d, as_t_slice::<BlockIQ1s>(&data)),
249 GgmlDType::IQ1_M => cuda::load_quantized(d, as_t_slice::<BlockIQ1m>(&data)),
250 GgmlDType::TQ1_0 => cuda::load_quantized(d, as_t_slice::<BlockTQ1_0>(&data)),
251 GgmlDType::TQ2_0 => cuda::load_quantized(d, as_t_slice::<BlockTQ2_0>(&data)),
252 GgmlDType::NVFP4 => cuda::load_quantized(d, as_t_slice::<BlockNVFP4>(&data)),
253 GgmlDType::Q1_0 => cuda::load_quantized(d, as_t_slice::<BlockQ1_0>(&data)),
254 },
255 #[cfg(feature = "rocm")]
256 Device::Rocm(d) => Ok(Self::Rocm(dtype.from_data(Cow::Borrowed(data)), d.clone())),
257 #[cfg(feature = "vulkan")]
258 Device::Vulkan(d) => Ok(Self::Vulkan(
259 dtype.from_data(Cow::Borrowed(data)),
260 d.clone(),
261 )),
262 #[cfg(feature = "wgpu")]
263 Device::Wgpu(d) => Ok(Self::Wgpu(dtype.from_data(Cow::Borrowed(data)), d.clone())),
264 }
265 }
266
267 fn block_size(&self) -> usize {
268 match self {
269 QStorage::Cpu(storage) => storage.block_size(),
270 QStorage::Metal(storage) => storage.dtype().block_size(),
271 QStorage::Cuda(storage) => storage.dtype().block_size(),
272 #[cfg(feature = "rocm")]
273 QStorage::Rocm(storage, _) => storage.block_size(),
274 #[cfg(feature = "vulkan")]
275 QStorage::Vulkan(storage, _) => storage.block_size(),
276 #[cfg(feature = "wgpu")]
277 QStorage::Wgpu(storage, _) => storage.block_size(),
278 QStorage::Stream(bank) => bank.dtype().block_size(),
279 }
280 }
281
282 fn dtype(&self) -> GgmlDType {
283 match self {
284 QStorage::Cpu(storage) => storage.dtype(),
285 QStorage::Metal(storage) => storage.dtype(),
286 QStorage::Cuda(storage) => storage.dtype(),
287 #[cfg(feature = "rocm")]
288 QStorage::Rocm(storage, _) => storage.dtype(),
289 #[cfg(feature = "vulkan")]
290 QStorage::Vulkan(storage, _) => storage.dtype(),
291 #[cfg(feature = "wgpu")]
292 QStorage::Wgpu(storage, _) => storage.dtype(),
293 QStorage::Stream(bank) => bank.dtype(),
294 }
295 }
296
297 fn device(&self) -> Device {
298 match self {
299 QStorage::Cpu(_storage) => Device::Cpu,
300 QStorage::Metal(storage) => Device::Metal(storage.device().clone()),
301 QStorage::Cuda(storage) => Device::Cuda(storage.device().clone()),
302 #[cfg(feature = "rocm")]
303 QStorage::Rocm(_storage, device) => Device::Rocm(device.clone()),
304 #[cfg(feature = "vulkan")]
305 QStorage::Vulkan(_storage, device) => Device::Vulkan(device.clone()),
306 #[cfg(feature = "wgpu")]
307 QStorage::Wgpu(_storage, device) => Device::Wgpu(device.clone()),
308 QStorage::Stream(_) => Device::Cpu,
309 }
310 }
311
312 fn size_in_bytes(&self) -> usize {
313 match self {
314 QStorage::Cpu(storage) => storage.storage_size_in_bytes(),
315 QStorage::Metal(storage) => storage.storage_size_in_bytes(),
316 QStorage::Cuda(storage) => storage.storage_size_in_bytes(),
317 #[cfg(feature = "rocm")]
318 QStorage::Rocm(storage, _) => storage.storage_size_in_bytes(),
319 #[cfg(feature = "vulkan")]
320 QStorage::Vulkan(storage, _) => storage.storage_size_in_bytes(),
321 #[cfg(feature = "wgpu")]
322 QStorage::Wgpu(storage, _) => storage.storage_size_in_bytes(),
323 QStorage::Stream(bank) => bank.logical_bytes(),
324 }
325 }
326
327 fn quantize(&mut self, src: &Storage) -> Result<()> {
328 match (self, src) {
329 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
330 storage.from_float(src.as_slice::<f32>()?);
331 }
332 (QStorage::Metal(storage), Storage::Metal(src)) => storage.quantize(src)?,
333 (QStorage::Cuda(storage), Storage::Cuda(src)) => storage.quantize(src)?,
334 _ => crate::bail!("Invalid quantize storage locations do not match"),
335 }
336 Ok(())
337 }
338
339 fn quantize_imatrix(
340 &mut self,
341 src: &Storage,
342 imatrix_weights: &[f32],
343 n_per_row: usize,
344 ) -> Result<()> {
345 match (self, src) {
346 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
347 storage.from_float_imatrix(src.as_slice::<f32>()?, imatrix_weights, n_per_row);
348 }
349 (QStorage::Metal(storage), Storage::Metal(src)) => {
350 storage.quantize_imatrix(src, imatrix_weights, n_per_row)?
351 }
352 (QStorage::Cuda(storage), Storage::Cuda(src)) => {
353 storage.quantize_imatrix(src, imatrix_weights, n_per_row)?
354 }
355 _ => crate::bail!("Invalid quantize storage locations do not match"),
356 }
357 Ok(())
358 }
359
360 fn quantize_onto(&mut self, src: &Storage) -> Result<()> {
361 match (self, src) {
362 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
363 storage.from_float(src.as_slice::<f32>()?);
364 }
365 (QStorage::Metal(storage), Storage::Cpu(src)) => storage.quantize_onto(src)?,
366 (QStorage::Cuda(storage), Storage::Cpu(src)) => storage.quantize_onto(src)?,
367 _ => crate::bail!("Invalid quantize source storage locations: not on cpu"),
368 }
369 Ok(())
370 }
371
372 fn quantize_imatrix_onto(
373 &mut self,
374 src: &Storage,
375 imatrix_weights: &[f32],
376 n_per_row: usize,
377 ) -> Result<()> {
378 match (self, src) {
379 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
380 storage.from_float_imatrix(src.as_slice::<f32>()?, imatrix_weights, n_per_row);
381 }
382 (QStorage::Metal(storage), Storage::Cpu(src)) => {
383 storage.quantize_imatrix_onto(src, imatrix_weights, n_per_row)?
384 }
385 (QStorage::Cuda(storage), Storage::Cpu(src)) => {
386 storage.quantize_imatrix_onto(src, imatrix_weights, n_per_row)?
387 }
388 _ => crate::bail!("Invalid quantize storage locations do not match"),
389 }
390 Ok(())
391 }
392
393 fn dequantize(&self, elem_count: usize) -> Result<Storage> {
394 match self {
395 QStorage::Cpu(storage) => Ok(Storage::Cpu(storage.dequantize(elem_count)?)),
396 QStorage::Metal(storage) => Ok(Storage::Metal(storage.dequantize(elem_count)?)),
397 QStorage::Cuda(storage) => Ok(Storage::Cuda(storage.dequantize(elem_count)?)),
398 #[cfg(feature = "rocm")]
399 QStorage::Rocm(storage, device) => {
400 use crate::backend::BackendDevice;
402 let cpu = storage.dequantize(elem_count)?;
403 Ok(Storage::Rocm(device.storage_from_cpu_storage(&cpu)?))
404 }
405 #[cfg(feature = "vulkan")]
406 QStorage::Vulkan(storage, device) => {
407 let cpu = storage.dequantize(elem_count)?;
409 Ok(Storage::Vulkan(device.upload_f32(cpu.as_slice::<f32>()?)?))
410 }
411 #[cfg(feature = "wgpu")]
412 QStorage::Wgpu(storage, device) => {
413 let cpu = storage.dequantize(elem_count)?;
415 Ok(Storage::Wgpu(device.upload_f32(cpu.as_slice::<f32>()?)?))
416 }
417 QStorage::Stream(_) => {
418 crate::bail!("streaming expert bank has no whole-tensor dequantize; consume it via indexed_moe_forward")
419 }
420 }
421 }
422
423 fn data(&self) -> Result<Cow<'_, [u8]>> {
424 match self {
425 QStorage::Cpu(storage) => {
426 let data_ptr = storage.as_ptr();
427 let size_in_bytes = storage.storage_size_in_bytes();
428 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
429 Ok(Cow::from(data))
430 }
431 QStorage::Cuda(storage) => Ok(Cow::from(storage.data()?)),
432 QStorage::Metal(storage) => Ok(Cow::from(storage.data()?)),
433 #[cfg(feature = "rocm")]
434 QStorage::Rocm(storage, _) => {
435 let data_ptr = storage.as_ptr();
436 let size_in_bytes = storage.storage_size_in_bytes();
437 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
438 Ok(Cow::from(data))
439 }
440 #[cfg(feature = "vulkan")]
441 QStorage::Vulkan(storage, _) => {
442 let data_ptr = storage.as_ptr();
443 let size_in_bytes = storage.storage_size_in_bytes();
444 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
445 Ok(Cow::from(data))
446 }
447 #[cfg(feature = "wgpu")]
448 QStorage::Wgpu(storage, _) => {
449 let data_ptr = storage.as_ptr();
450 let size_in_bytes = storage.storage_size_in_bytes();
451 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
452 Ok(Cow::from(data))
453 }
454 QStorage::Stream(_) => {
455 crate::bail!(
456 "streaming expert bank is not resident; consume it via indexed_moe_forward"
457 )
458 }
459 }
460 }
461
462 pub fn device_ptr(&self) -> Result<*const u8> {
463 match self {
464 QStorage::Cuda(storage) => storage.device_ptr(),
465 #[cfg(feature = "rocm")]
466 QStorage::Rocm(..) => crate::bail!("not implemented"),
467 #[cfg(feature = "vulkan")]
468 QStorage::Vulkan(..) => crate::bail!("not implemented"),
469 #[cfg(feature = "wgpu")]
470 QStorage::Wgpu(..) => crate::bail!("not implemented"),
471 QStorage::Metal(_) | QStorage::Cpu(_) | QStorage::Stream(_) => {
472 crate::bail!("not implemented");
473 }
474 }
475 }
476
477 #[cfg(feature = "cuda")]
478 pub fn device_ptr_with_guard<'a>(
479 &'a self,
480 stream: &'a crate::cuda_backend::cudarc::driver::CudaStream,
481 ) -> Result<(
482 *const u8,
483 crate::cuda_backend::cudarc::driver::SyncOnDrop<'a>,
484 )> {
485 match self {
486 QStorage::Cuda(storage) => storage.device_ptr_with_guard(stream),
487 QStorage::Metal(_) | QStorage::Cpu(_) | QStorage::Stream(_) => {
488 crate::bail!("not implemented");
489 }
490 }
491 }
492}
493
494#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
495pub enum GgmlDType {
496 F32,
497 F16,
498 BF16,
499 I32,
502 Q4_0,
503 Q4_1,
504 Q5_0,
505 Q5_1,
506 Q8_0,
507 Q8_1,
508 Q2K,
509 Q3K,
510 Q4K,
511 Q5K,
512 Q6K,
513 Q8K,
514 #[allow(non_camel_case_types)]
515 IQ4_NL,
516 #[allow(non_camel_case_types)]
517 IQ4_XS,
518 MXFP4,
520 #[allow(non_camel_case_types)]
522 IQ2_XXS,
523 #[allow(non_camel_case_types)]
524 IQ2_XS,
525 #[allow(non_camel_case_types)]
526 IQ3_XXS,
527 #[allow(non_camel_case_types)]
528 IQ1_S,
529 #[allow(non_camel_case_types)]
530 IQ3_S,
531 #[allow(non_camel_case_types)]
532 IQ2_S,
533 #[allow(non_camel_case_types)]
534 IQ1_M,
535 TQ1_0,
536 TQ2_0,
537 NVFP4,
538 Q1_0,
539}
540
541use crate::for_each_quant;
553
554macro_rules! gen_from_u32 {
555 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
556 pub fn from_u32(u: u32) -> Result<Self> {
560 let dtype = match u {
561 0 => Self::F32,
562 1 => Self::F16,
563 30 => Self::BF16,
564 26 => Self::I32,
565 $( $id => Self::$v, )+
566 _ => crate::bail!("unknown dtype for tensor {u}"),
567 };
568 Ok(dtype)
569 }
570 };
571}
572
573macro_rules! gen_to_u32 {
574 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
575 pub fn to_u32(self) -> u32 {
579 match self {
580 Self::F32 => 0,
581 Self::F16 => 1,
582 Self::BF16 => 30,
583 Self::I32 => 26,
584 $( Self::$v => $id, )+
585 }
586 }
587 };
588}
589
590macro_rules! gen_cpu_zeros {
591 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
592 pub fn cpu_zeros(&self, elem_count: usize) -> Box<dyn QuantizedType> {
594 match self {
595 Self::F32 => Box::new(vec![f32::zeros(); elem_count]),
596 Self::F16 => Box::new(vec![f16::zeros(); elem_count]),
597 Self::BF16 => Box::new(vec![bf16::zeros(); elem_count]),
598 Self::I32 => Box::new(vec![0i32; elem_count]),
599 $( Self::$v => Box::new(vec![<$b>::zeros(); elem_count / <$b>::BLCK_SIZE]), )+
600 }
601 }
602 };
603}
604
605macro_rules! gen_from_data {
606 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
607 pub fn from_data(&self, data: Cow<'_, [u8]>) -> Box<dyn QuantizedType> {
608 match self {
609 Self::F32 => Box::new(as_t_slice::<f32>(&data).to_vec()),
610 Self::F16 => Box::new(as_t_slice::<f16>(&data).to_vec()),
611 Self::BF16 => Box::new(as_t_slice::<bf16>(&data).to_vec()),
612 Self::I32 => Box::new(as_t_slice::<i32>(&data).to_vec()),
613 $( Self::$v => Box::new(as_t_slice::<$b>(&data).to_vec()), )+
614 }
615 }
616 };
617}
618
619macro_rules! gen_type_size {
620 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
621 pub fn type_size(&self) -> usize {
623 use k_quants::*;
624 match self {
625 Self::F32 => 4,
626 Self::F16 | Self::BF16 => 2,
627 Self::I32 => 4,
628 $( Self::$v => std::mem::size_of::<$b>(), )+
629 }
630 }
631 };
632}
633
634macro_rules! gen_block_align {
635 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
636 pub const fn block_align(&self) -> usize {
641 use k_quants::*;
642 match self {
643 Self::F32 => std::mem::align_of::<f32>(),
644 Self::F16 => std::mem::align_of::<f16>(),
645 Self::BF16 => std::mem::align_of::<bf16>(),
646 Self::I32 => std::mem::align_of::<i32>(),
647 $( Self::$v => std::mem::align_of::<$b>(), )+
648 }
649 }
650 };
651}
652
653macro_rules! gen_from_mmap {
654 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
655 #[allow(clippy::wrong_self_convention)] pub(crate) fn from_mmap(
662 &self,
663 mmap: Arc<memmap2::Mmap>,
664 offset: usize,
665 n_blocks: usize,
666 ) -> Box<dyn QuantizedType> {
667 match self {
668 Self::F32 => Box::new(QMmap::<f32>::new(mmap, offset, n_blocks)),
669 Self::F16 => Box::new(QMmap::<f16>::new(mmap, offset, n_blocks)),
670 Self::BF16 => Box::new(QMmap::<bf16>::new(mmap, offset, n_blocks)),
671 Self::I32 => Box::new(QMmap::<i32>::new(mmap, offset, n_blocks)),
672 $( Self::$v => Box::new(QMmap::<$b>::new(mmap, offset, n_blocks)), )+
673 }
674 }
675 };
676}
677
678impl GgmlDType {
679 for_each_quant!(gen_from_u32);
680 for_each_quant!(gen_to_u32);
681 for_each_quant!(gen_cpu_zeros);
682 for_each_quant!(gen_from_data);
683 for_each_quant!(gen_from_mmap);
684 for_each_quant!(gen_type_size);
685 for_each_quant!(gen_block_align);
686
687 pub fn block_size(&self) -> usize {
689 match self {
690 Self::F32 => 1,
691 Self::F16 | Self::BF16 => 1,
692 Self::I32 => 1,
693 Self::Q4_0 => k_quants::QK4_0,
694 Self::Q4_1 => k_quants::QK4_1,
695 Self::Q5_0 => k_quants::QK5_0,
696 Self::Q5_1 => k_quants::QK5_1,
697 Self::Q8_0 => k_quants::QK8_0,
698 Self::Q8_1 => k_quants::QK8_1,
699 Self::IQ4_NL => k_quants::QK4_NL,
700 Self::MXFP4 => k_quants::QK_MXFP4,
701 Self::Q1_0 => iq_quants::QK1_0,
702 Self::NVFP4 => iq_quants::QK_NVFP4,
703 Self::Q2K
704 | Self::Q3K
705 | Self::Q4K
706 | Self::Q5K
707 | Self::Q6K
708 | Self::Q8K
709 | Self::IQ4_XS
710 | Self::IQ2_XXS
711 | Self::IQ2_XS
712 | Self::IQ3_XXS
713 | Self::IQ1_S
714 | Self::IQ3_S
715 | Self::IQ2_S
716 | Self::IQ1_M
717 | Self::TQ1_0
718 | Self::TQ2_0 => k_quants::QK_K,
719 }
720 }
721}
722
723pub trait QuantizedType: Send + Sync {
725 fn dtype(&self) -> GgmlDType;
726 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()>;
727 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()>;
728 fn embedding(&self, ids: &[u32], rows: usize, hidden: usize) -> Result<CpuStorage>;
729 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage>;
730 fn storage_size_in_bytes(&self) -> usize;
731 fn as_ptr(&self) -> *const u8;
732 fn block_size(&self) -> usize;
733 #[allow(clippy::wrong_self_convention)]
734 fn from_float(&mut self, xs: &[f32]);
735 #[allow(clippy::wrong_self_convention)]
736 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize);
737 fn size(&self) -> usize;
738}
739
740fn embedding_rows<T: k_quants::GgmlType>(
742 blocks: &[T],
743 ids: &[u32],
744 rows: usize,
745 hidden: usize,
746) -> Result<CpuStorage> {
747 if !hidden.is_multiple_of(T::BLCK_SIZE) {
748 crate::bail!(
749 "quantized embedding hidden size {hidden} is not divisible by block size {}",
750 T::BLCK_SIZE
751 )
752 }
753 let row_blocks = hidden / T::BLCK_SIZE;
754 if blocks.len() != rows * row_blocks {
755 crate::bail!(
756 "quantized tensor has {} blocks, expected {}",
757 blocks.len(),
758 rows * row_blocks
759 )
760 }
761 let mut out = vec![0f32; ids.len() * hidden];
762 for (out_row, &row_id) in ids.iter().enumerate() {
763 let row = row_id as usize;
764 if row >= rows {
765 crate::bail!("embedding id {row} is out of range for {rows} rows")
766 }
767 let src = &blocks[row * row_blocks..(row + 1) * row_blocks];
768 let dst = &mut out[out_row * hidden..(out_row + 1) * hidden];
769 T::to_float(src, dst);
770 }
771 Ok(CpuStorage::F32(out))
772}
773
774impl<T: k_quants::GgmlType + Send + Sync> QuantizedType for Vec<T> {
775 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()> {
776 k_quants::matmul(mkn, lhs, self.as_slice(), dst)
777 }
778 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()> {
779 k_quants::matmul_f16(mkn, lhs, self.as_slice(), dst)
780 }
781
782 fn embedding(&self, ids: &[u32], rows: usize, hidden: usize) -> Result<CpuStorage> {
783 embedding_rows(self.as_slice(), ids, rows, hidden)
784 }
785
786 fn size(&self) -> usize {
787 self.len() * core::mem::size_of::<T>()
788 }
789
790 fn from_float(&mut self, xs: &[f32]) {
791 T::from_float(xs, self)
792 }
793
794 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize) {
795 T::from_float_imatrix(xs, self, imatrix_weights, n_per_row)
796 }
797
798 fn dtype(&self) -> GgmlDType {
799 T::DTYPE
800 }
801
802 fn block_size(&self) -> usize {
803 T::BLCK_SIZE
804 }
805
806 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage> {
807 let mut ys = vec![0.0f32; elem_count];
808 T::to_float(self.as_slice(), &mut ys);
809 Ok(CpuStorage::F32(ys))
810 }
811
812 fn storage_size_in_bytes(&self) -> usize {
813 self.len() * std::mem::size_of::<T>()
814 }
815
816 fn as_ptr(&self) -> *const u8 {
817 self.as_ptr() as *const u8
818 }
819}
820
821pub struct QMmap<T> {
834 mmap: Arc<memmap2::Mmap>,
835 offset: usize,
837 n_blocks: usize,
839 _t: std::marker::PhantomData<T>,
840}
841
842impl<T> QMmap<T> {
843 fn new(mmap: Arc<memmap2::Mmap>, offset: usize, n_blocks: usize) -> Self {
844 Self {
845 mmap,
846 offset,
847 n_blocks,
848 _t: std::marker::PhantomData,
849 }
850 }
851
852 #[inline]
857 fn as_slice(&self) -> &[T] {
858 let len = self.n_blocks * std::mem::size_of::<T>();
859 as_t_slice::<T>(&self.mmap[self.offset..self.offset + len])
860 }
861}
862
863impl<T: k_quants::GgmlType + Send + Sync> QuantizedType for QMmap<T> {
864 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()> {
865 k_quants::matmul(mkn, lhs, self.as_slice(), dst)
866 }
867
868 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()> {
869 k_quants::matmul_f16(mkn, lhs, self.as_slice(), dst)
870 }
871
872 fn embedding(&self, ids: &[u32], rows: usize, hidden: usize) -> Result<CpuStorage> {
873 embedding_rows(self.as_slice(), ids, rows, hidden)
874 }
875
876 fn size(&self) -> usize {
877 self.n_blocks * std::mem::size_of::<T>()
878 }
879
880 fn from_float(&mut self, _xs: &[f32]) {
881 panic!("QMmap is read-only: cannot quantize into a memory-mapped weight region")
882 }
883
884 fn from_float_imatrix(&mut self, _xs: &[f32], _imatrix_weights: &[f32], _n_per_row: usize) {
885 panic!("QMmap is read-only: cannot quantize into a memory-mapped weight region")
886 }
887
888 fn dtype(&self) -> GgmlDType {
889 T::DTYPE
890 }
891
892 fn block_size(&self) -> usize {
893 T::BLCK_SIZE
894 }
895
896 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage> {
897 let mut ys = vec![0.0f32; elem_count];
898 T::to_float(self.as_slice(), &mut ys);
899 Ok(CpuStorage::F32(ys))
900 }
901
902 fn storage_size_in_bytes(&self) -> usize {
903 self.n_blocks * std::mem::size_of::<T>()
904 }
905
906 fn as_ptr(&self) -> *const u8 {
907 self.as_slice().as_ptr() as *const u8
908 }
909}
910
911impl std::fmt::Debug for QTensor {
912 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
913 write!(f, "QTensor[{:?}; {:?}]", self.shape, self.dtype())
914 }
915}
916
917fn check_shape(shape: &Shape, block_size: usize) -> Result<()> {
918 let dims = shape.dims();
919 if dims.is_empty() {
920 crate::bail!("scalar tensor cannot be quantized {shape:?}")
921 }
922 if !dims[dims.len() - 1].is_multiple_of(block_size) {
923 crate::bail!(
924 "quantized tensor must have their last dim divisible by block size {shape:?} {}",
925 block_size
926 )
927 }
928 Ok(())
929}
930
931impl QTensor {
932 fn make(storage: QStorage, shape: Shape) -> Self {
935 Self {
936 storage,
937 shape,
938 banks: ResidentBanks::default(),
939 repacked_qs: repack::PackedCache::new(),
940 }
941 }
942
943 pub fn new<S: Into<Shape>>(storage: QStorage, shape: S) -> Result<Self> {
944 let shape = shape.into();
945 check_shape(&shape, storage.block_size())?;
946 Ok(Self::make(storage, shape))
947 }
948
949 pub fn quantize(src: &Tensor, dtype: GgmlDType) -> Result<Self> {
950 let shape = src.shape();
951 let block_size = dtype.block_size();
952 check_shape(shape, block_size)?;
953 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
954 let elem_count = shape.elem_count();
955 if !elem_count.is_multiple_of(block_size) {
956 crate::bail!(
957 "tensor size ({shape:?}) is not divisible by block size {}",
958 block_size
959 )
960 }
961 let mut storage = src.device().qzeros(elem_count, dtype)?;
962 storage.quantize(&src.storage())?;
963 Ok(Self::make(storage, shape.clone()))
964 }
965
966 pub fn quantize_imatrix(
967 src: &Tensor,
968 imatrix_weights: &[f32],
969 dtype: GgmlDType,
970 ) -> Result<Self> {
971 let n_per_row = src.dim(D::Minus1)?;
974 if imatrix_weights.len() != n_per_row {
975 crate::bail!(
976 "imatrix weights must have the same length {} as the last dim of src {}",
977 imatrix_weights.len(),
978 src.dim(D::Minus1)?
979 );
980 }
981
982 let shape = src.shape();
983 let block_size = dtype.block_size();
984 check_shape(shape, block_size)?;
985 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
986 let elem_count = shape.elem_count();
987 if !elem_count.is_multiple_of(block_size) {
988 crate::bail!(
989 "tensor size ({shape:?}) is not divisible by block size {}",
990 block_size
991 );
992 }
993 let mut storage = src.device().qzeros(elem_count, dtype)?;
994 storage.quantize_imatrix(&src.storage(), imatrix_weights, n_per_row)?;
995 Ok(Self::make(storage, shape.clone()))
996 }
997
998 pub fn quantize_imatrix_onto(
1000 src: &Tensor,
1001 imatrix_weights: &[f32],
1002 dtype: GgmlDType,
1003 dev: &Device,
1004 ) -> Result<Self> {
1005 if !src.device().is_cpu() {
1006 crate::bail!(
1007 "`quantize_onto` expects a `src` to be on the cpu, got {:?}.",
1008 src.device()
1009 )
1010 }
1011 let n_per_row = src.dim(D::Minus1)?;
1014 if imatrix_weights.len() != n_per_row {
1015 crate::bail!(
1016 "imatrix weights must have the same length {} as the last dim of src {}",
1017 imatrix_weights.len(),
1018 src.dim(D::Minus1)?
1019 );
1020 }
1021 let shape = src.shape();
1022 let block_size = dtype.block_size();
1023 check_shape(shape, block_size)?;
1024 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
1025 let elem_count = shape.elem_count();
1026 if !elem_count.is_multiple_of(block_size) {
1027 crate::bail!(
1028 "tensor size ({shape:?}) is not divisible by block size {}",
1029 block_size
1030 )
1031 }
1032 let mut storage = dev.qzeros(elem_count, dtype)?;
1034 storage.quantize_imatrix_onto(&src.storage(), imatrix_weights, n_per_row)?;
1035 Ok(Self::make(storage, shape.clone()))
1036 }
1037
1038 pub fn quantize_onto(src: &Tensor, dtype: GgmlDType, dev: &Device) -> Result<Self> {
1040 if !src.device().is_cpu() {
1041 crate::bail!(
1042 "`quantize_onto` expects a `src` to be on the cpu, got {:?}.",
1043 src.device()
1044 )
1045 }
1046 let shape = src.shape();
1047 let block_size = dtype.block_size();
1048 check_shape(shape, block_size)?;
1049 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
1050 let elem_count = shape.elem_count();
1051 if !elem_count.is_multiple_of(block_size) {
1052 crate::bail!(
1053 "tensor size ({shape:?}) is not divisible by block size {}",
1054 block_size
1055 )
1056 }
1057 let mut storage = dev.qzeros(elem_count, dtype)?;
1059 storage.quantize_onto(&src.storage())?;
1060 Ok(Self::make(storage, shape.clone()))
1061 }
1062
1063 pub fn dtype(&self) -> GgmlDType {
1064 self.storage.dtype()
1065 }
1066
1067 pub fn device(&self) -> Device {
1068 self.storage.device()
1069 }
1070
1071 pub fn rank(&self) -> usize {
1072 self.shape.rank()
1073 }
1074
1075 pub fn shape(&self) -> &Shape {
1076 &self.shape
1077 }
1078
1079 pub fn dequantize(&self, device: &Device) -> Result<Tensor> {
1080 let storage = self.storage.dequantize(self.shape.elem_count())?;
1081 let none = crate::op::BackpropOp::none();
1082 crate::tensor::from_storage(storage, self.shape.clone(), none, false).to_device(device)
1083 }
1084
1085 pub fn dequantize_f16(&self, device: &Device) -> Result<Tensor> {
1086 match &self.storage {
1089 QStorage::Cuda(s) => {
1090 let s = s.dequantize_f16(self.shape.elem_count())?;
1091 let none = crate::op::BackpropOp::none();
1092 crate::tensor::from_storage(Storage::Cuda(s), self.shape.clone(), none, false)
1093 .to_device(device)
1094 }
1095 _ => {
1096 let s = self.dequantize(device)?.to_dtype(crate::DType::F16)?;
1097 Ok(s)
1098 }
1099 }
1100 }
1101
1102 pub fn embedding(&self, ids: &Tensor) -> Result<Tensor> {
1103 let (rows, hidden) = self.shape.dims2()?;
1104 if !hidden.is_multiple_of(self.dtype().block_size()) {
1105 crate::bail!(
1106 "quantized embedding hidden size {hidden} is not divisible by block size {}",
1107 self.dtype().block_size()
1108 )
1109 }
1110 let mut out_shape = ids.dims().to_vec();
1111 out_shape.push(hidden);
1112 let device = self.device();
1113 let ids = ids
1114 .to_device(&device)?
1115 .to_dtype(DType::U32)?
1116 .flatten_all()?
1117 .contiguous()?;
1118 let storage = match &self.storage {
1119 QStorage::Cpu(storage) => {
1120 let ids = ids.to_vec1::<u32>()?;
1121 Storage::Cpu(storage.embedding(&ids, rows, hidden)?)
1122 }
1123 QStorage::Metal(storage) => match &*ids.storage() {
1124 Storage::Metal(ids_storage) => {
1125 Storage::Metal(storage.embedding(rows, hidden, ids_storage, ids.layout())?)
1126 }
1127 _ => unreachable!("ids were moved to the QTensor device"),
1128 },
1129 QStorage::Cuda(storage) => match &*ids.storage() {
1130 Storage::Cuda(ids_storage) => {
1131 Storage::Cuda(storage.embedding(rows, hidden, ids_storage, ids.layout())?)
1132 }
1133 _ => unreachable!("ids were moved to the QTensor device"),
1134 },
1135 #[cfg(feature = "rocm")]
1136 QStorage::Rocm(..) => return self.embedding_dequantized(&ids, out_shape),
1137 #[cfg(feature = "vulkan")]
1138 QStorage::Vulkan(..) => return self.embedding_dequantized(&ids, out_shape),
1139 #[cfg(feature = "wgpu")]
1140 QStorage::Wgpu(..) => return self.embedding_dequantized(&ids, out_shape),
1141 QStorage::Stream(_) => {
1142 crate::bail!("streaming expert bank is not resident; it has no embedding rows")
1143 }
1144 };
1145 let none = crate::op::BackpropOp::none();
1146 Ok(crate::tensor::from_storage(storage, out_shape, none, false))
1147 }
1148
1149 #[cfg(any(feature = "rocm", feature = "vulkan", feature = "wgpu"))]
1151 fn embedding_dequantized(&self, ids: &Tensor, out_shape: Vec<usize>) -> Result<Tensor> {
1152 self.dequantize(ids.device())?
1153 .index_select(ids, 0)?
1154 .reshape(out_shape)
1155 }
1156
1157 pub fn storage_size_in_bytes(&self) -> usize {
1158 self.storage.size_in_bytes()
1159 }
1160
1161 pub fn data(&self) -> Result<Cow<'_, [u8]>> {
1162 self.storage.data()
1163 }
1164
1165 #[cfg(feature = "rocm")]
1169 fn rocm_moe_bank(&self, dev: &crate::RocmDevice) -> Result<std::sync::Arc<crate::RocmStorage>> {
1170 use crate::backend::BackendDevice;
1171 let bank = self.data()?;
1172 cache_or_upload(&self.banks.rocm, bank.as_ref(), |b| {
1173 dev.storage_from_slice(b)
1174 })
1175 }
1176
1177 #[cfg(feature = "vulkan")]
1181 fn vulkan_moe_bank(
1182 &self,
1183 dev: &crate::VulkanDevice,
1184 e_cnt: usize,
1185 n: usize,
1186 k: usize,
1187 ) -> Result<std::sync::Arc<crate::VulkanStorage>> {
1188 let bank = self.data()?;
1189 let dt = self.storage.dtype();
1190 cache_or_upload(&self.banks.vulkan, bank.as_ref(), |b| match dt {
1191 GgmlDType::Q8_0 => dev.quantize_q8_blocks(b, e_cnt * n, k),
1192 GgmlDType::Q6K => dev.quantize_q6k(b, e_cnt * n, k),
1193 _ => dev.upload_qweight(b),
1194 })
1195 }
1196
1197 #[cfg(feature = "vulkan")]
1201 fn vulkan_moe_bank_split(
1202 &self,
1203 dev: &crate::VulkanDevice,
1204 e_cnt: usize,
1205 n: usize,
1206 k: usize,
1207 ) -> Result<std::sync::Arc<crate::vulkan::MoeBankSplit>> {
1208 let bank = self.data()?;
1209 let dt = self.storage.dtype();
1210 cache_or_upload(&self.banks.vulkan_split, bank.as_ref(), |b| match dt {
1211 GgmlDType::Q4K => dev.quantize_q4k_split(b, e_cnt * n, k),
1212 GgmlDType::Q6K => dev.quantize_q6k_split(b, e_cnt * n, k),
1213 _ => crate::bail!("vulkan_moe_bank_split: unsupported dtype {dt:?}"),
1214 })
1215 }
1216
1217 #[cfg(feature = "wgpu")]
1220 fn wgpu_moe_bank(&self, dev: &crate::WgpuDevice) -> Result<std::sync::Arc<crate::WgpuStorage>> {
1221 let bank = self.data()?;
1222 cache_or_upload(&self.banks.wgpu, bank.as_ref(), |b| dev.upload_qweight(b))
1223 }
1224
1225 pub fn indexed_moe_forward(&self, x: &Tensor, ids: &Tensor) -> Result<Tensor> {
1226 let ids = &ids.contiguous()?;
1229 match &self.storage {
1230 QStorage::Cuda(s) if cuda::QCudaStorage::supports_indexed_moe(s.dtype()) => {
1236 let out_dtype = x.dtype();
1241 let x = x.to_dtype(crate::DType::F32)?.contiguous()?;
1242 let (x_guard, x_l) = x.storage_and_layout();
1245 let (ids_guard, ids_l) = ids.storage_and_layout();
1246 match (&*x_guard, &*ids_guard) {
1247 (Storage::Cuda(x_storage), Storage::Cuda(ids_storage)) => {
1248 let (storage, out_shape) = s.indexed_moe_forward(
1249 self.shape(),
1250 x_storage,
1251 x_l,
1252 ids_storage,
1253 ids_l,
1254 )?;
1255 crate::tensor::from_storage(
1256 Storage::Cuda(storage),
1257 out_shape,
1258 crate::op::BackpropOp::none(),
1259 false,
1260 )
1261 .to_dtype(out_dtype)
1262 }
1263 _ => {
1264 panic!("Non-cuda indexed_moe_forward is not implemented!");
1265 }
1266 }
1267 }
1268 #[cfg(feature = "cuda")]
1274 QStorage::Cuda(s) if cuda::QCudaStorage::supports_iquant_moe(s.dtype()) => {
1275 let out_dtype = x.dtype();
1276 let (_e_cnt, n, k) = self.shape().dims3()?;
1277 let (t, topk) = ids.dims2()?;
1278 let nrows = t * topk;
1279
1280 if t > 1 {
1286 let x_f32 = x.to_dtype(crate::DType::F32)?.contiguous()?;
1287 let ids_u32 = ids.to_dtype(crate::DType::U32)?.contiguous()?;
1288 let (xs, _) = x_f32.storage_and_layout();
1289 let xc = match &*xs {
1290 Storage::Cuda(c) => c,
1291 _ => crate::bail!("cuda i-quant MoE: x not on cuda after contiguous()"),
1292 };
1293 let (ids_s, _) = ids_u32.storage_and_layout();
1294 let idc = match &*ids_s {
1295 Storage::Cuda(c) => c,
1296 _ => crate::bail!("cuda i-quant MoE: ids not on cuda"),
1297 };
1298 if let Some((st, sh)) = s.moe_iquant_qmmq(
1299 self.shape(),
1300 xc.as_cuda_slice::<f32>()?,
1301 x.shape(),
1302 &idc.as_cuda_slice::<u32>()?.slice(0..),
1303 ids.shape(),
1304 )? {
1305 return crate::tensor::from_storage(
1306 Storage::Cuda(st),
1307 sh,
1308 crate::op::BackpropOp::none(),
1309 false,
1310 )
1311 .to_dtype(out_dtype);
1312 }
1313 }
1314
1315 let sdim = x.dim(1)?; let x_exp = if sdim == topk {
1320 x.clone()
1321 } else {
1322 x.broadcast_as((t, topk, k))?
1323 };
1324 let x_flat = x_exp
1325 .reshape((nrows, k))?
1326 .to_dtype(crate::DType::F32)?
1327 .contiguous()?;
1328 let ids_flat = ids
1329 .reshape((nrows,))?
1330 .to_dtype(crate::DType::U32)?
1331 .contiguous()?;
1332 let (xstore, _) = x_flat.storage_and_layout();
1333 let xc = match &*xstore {
1334 Storage::Cuda(c) => c,
1335 _ => crate::bail!("cuda i-quant MoE: x not on cuda after contiguous()"),
1336 };
1337 let (idstore, _) = ids_flat.storage_and_layout();
1338 let idc = match &*idstore {
1339 Storage::Cuda(c) => c,
1340 _ => crate::bail!("cuda i-quant MoE: ids not on cuda"),
1341 };
1342 let y = s.moe_iquant_dp4a(
1343 &xc.as_cuda_slice::<f32>()?.slice(0..),
1344 &idc.as_cuda_slice::<u32>()?.slice(0..),
1345 nrows,
1346 n,
1347 k,
1348 )?;
1349 let out = crate::tensor::from_storage(
1350 Storage::Cuda(y),
1351 (nrows, n),
1352 crate::op::BackpropOp::none(),
1353 false,
1354 );
1355 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1356 }
1357 #[cfg(feature = "vulkan")]
1363 QStorage::Vulkan(_, vk_dev) if vk_moe_kernel(self.storage.dtype()).is_some() => {
1364 let out_dtype = x.dtype();
1365 let (e_cnt, n, k) = self.shape().dims3()?;
1366 let (t, topk) = ids.dims2()?;
1367 let s = x.dim(1)?; let x_exp = if s == topk {
1369 x.clone()
1370 } else {
1371 x.broadcast_as((t, topk, k))?
1372 };
1373 let nrows = t * topk;
1375 let x_flat = x_exp
1376 .reshape((nrows, k))?
1377 .to_dtype(crate::DType::F32)?
1378 .contiguous()?;
1379 let dt = self.storage.dtype();
1386 let ids_u32 = ids
1387 .reshape((nrows,))?
1388 .to_dtype(crate::DType::U32)?
1389 .contiguous()?;
1390 let y = {
1391 let (store, _) = x_flat.storage_and_layout();
1392 let xv = match &*store {
1393 Storage::Vulkan(v) => v,
1394 _ => crate::bail!("vulkan MoE: x not on vulkan after contiguous()"),
1395 };
1396 let (ids_store, _) = ids_u32.storage_and_layout();
1397 let ids_v = match &*ids_store {
1398 Storage::Vulkan(v) => v,
1399 _ => crate::bail!("vulkan MoE: ids not on vulkan after contiguous()"),
1400 };
1401 if t > 1
1410 && dt == GgmlDType::Q4K
1411 && vk_dev.has_int_dot8()
1412 && std::env::var_os("VK_MOE_PREFILL_GEMM_OFF").is_none()
1413 {
1414 let bank = self.vulkan_moe_bank_split(vk_dev, e_cnt, n, k)?;
1415 let (xq, xsq, xsum) = vk_dev.quantize_act_q8(xv, nrows, k)?;
1419 vk_dev.mmq_q4k_id_gpu(
1422 bank.as_ref(),
1423 &xq,
1424 &xsq,
1425 &xsum,
1426 ids_v,
1427 nrows,
1428 e_cnt,
1429 t,
1430 n,
1431 k,
1432 )?
1433 } else {
1434 match vk_moe_blk_dp4a_kernel(dt, n, k).filter(|_| vk_dev.has_int_dot8()) {
1440 Some((blk, with_xsum)) => {
1441 let bank = self.vulkan_moe_bank_split(vk_dev, e_cnt, n, k)?;
1442 vk_dev.moe_matvec_blk_dp4a_gpu(
1443 blk,
1444 with_xsum,
1445 bank.as_ref(),
1446 xv,
1447 ids_v,
1448 nrows,
1449 n,
1450 k,
1451 )?
1452 }
1453 None => match vk_moe_blk_kernel(dt, n, k) {
1454 Some(blk) => {
1455 let bank = self.vulkan_moe_bank_split(vk_dev, e_cnt, n, k)?;
1456 vk_dev.moe_matvec_blk_gpu(
1457 blk,
1458 bank.as_ref(),
1459 xv,
1460 ids_v,
1461 nrows,
1462 n,
1463 k,
1464 )?
1465 }
1466 None => {
1467 let kernel = vk_moe_kernel(dt).unwrap();
1469 let wbank = self.vulkan_moe_bank(vk_dev, e_cnt, n, k)?;
1470 vk_dev.moe_matvec_gpu(
1471 kernel,
1472 wbank.as_ref(),
1473 xv,
1474 ids_v,
1475 nrows,
1476 n,
1477 k,
1478 )?
1479 }
1480 },
1481 }
1482 }
1483 };
1484 let out = crate::tensor::from_storage(
1485 Storage::Vulkan(y),
1486 (nrows, n),
1487 crate::op::BackpropOp::none(),
1488 false,
1489 );
1490 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1491 }
1492 #[cfg(feature = "wgpu")]
1496 QStorage::Wgpu(_, wgpu_dev) if wgpu_moe_kernel(self.storage.dtype()).is_some() => {
1497 let out_dtype = x.dtype();
1498 let (e_cnt, n, k) = self.shape().dims3()?;
1499 let (t, topk) = ids.dims2()?;
1500 let s = x.dim(1)?; let x_exp = if s == topk {
1502 x.clone()
1503 } else {
1504 x.broadcast_as((t, topk, k))?
1505 };
1506 let nrows = t * topk;
1507 let x_flat = x_exp
1508 .reshape((nrows, k))?
1509 .to_dtype(crate::DType::F32)?
1510 .contiguous()?;
1511 let ids_vec = ids
1512 .reshape((nrows,))?
1513 .to_dtype(crate::DType::U32)?
1514 .to_vec1::<u32>()?;
1515 if let Some(&bad) = ids_vec.iter().find(|&&e| e as usize >= e_cnt) {
1516 crate::bail!("indexed_moe_forward: expert id {bad} >= num_experts {e_cnt}");
1517 }
1518 let kernel = wgpu_moe_kernel(self.storage.dtype()).unwrap();
1520 let wbank = self.wgpu_moe_bank(wgpu_dev)?;
1522 let ids_buf = wgpu_dev.upload_ids(&ids_vec)?;
1523 let y = {
1524 let (store, _) = x_flat.storage_and_layout();
1525 let xv = match &*store {
1526 Storage::Wgpu(v) => v,
1527 _ => crate::bail!("wgpu MoE: x not on wgpu after contiguous()"),
1528 };
1529 wgpu_dev.moe_matvec_gpu(kernel, wbank.as_ref(), xv, &ids_buf, nrows, n, k)?
1530 };
1531 let out = crate::tensor::from_storage(
1532 Storage::Wgpu(y),
1533 (nrows, n),
1534 crate::op::BackpropOp::none(),
1535 false,
1536 );
1537 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1538 }
1539 #[cfg(feature = "rocm")]
1544 QStorage::Rocm(_, rocm_dev)
1545 if crate::RocmQuantType::from_ggml(self.storage.dtype()).is_some() =>
1546 {
1547 let qt = crate::RocmQuantType::from_ggml(self.storage.dtype()).unwrap();
1548 let out_dtype = x.dtype();
1549 let (_e_cnt, n, k) = self.shape().dims3()?;
1552 let (t, topk) = ids.dims2()?;
1553 let s = x.dim(1)?; let x_exp = if s == topk {
1555 x.clone()
1556 } else {
1557 x.broadcast_as((t, topk, k))?
1558 };
1559 let nrows = t * topk;
1560 let use_qmmq = t > 1 && qt.qmmq_capable();
1566 let x_flat = match x_exp.dtype() {
1567 DType::F16 | DType::F32 if use_qmmq => {
1571 x_exp.reshape((nrows, k))?.contiguous()?
1572 }
1573 _ if use_qmmq => x_exp
1574 .reshape((nrows, k))?
1575 .to_dtype(DType::F16)?
1576 .contiguous()?,
1577 DType::BF16 | DType::F16 => x_exp.reshape((nrows, k))?.contiguous()?,
1578 DType::F32 if qt.dp4a_active() => x_exp.reshape((nrows, k))?.contiguous()?,
1582 _ => x_exp
1583 .reshape((nrows, k))?
1584 .to_dtype(DType::F16)?
1585 .contiguous()?,
1586 };
1587 let wbank = self.rocm_moe_bank(rocm_dev)?;
1588 let ids_u32 = ids
1595 .reshape((nrows,))?
1596 .to_dtype(crate::DType::U32)?
1597 .contiguous()?;
1598 let (store, _) = x_flat.storage_and_layout();
1599 let xr = match &*store {
1600 crate::Storage::Rocm(r) => r,
1601 _ => crate::bail!("rocm MoE: x not on rocm after contiguous()"),
1602 };
1603 let (idstore, _) = ids_u32.storage_and_layout();
1604 let idr = match &*idstore {
1605 crate::Storage::Rocm(r) => r,
1606 _ => crate::bail!("rocm MoE: ids not on rocm"),
1607 };
1608 let y = if use_qmmq {
1609 rocm_dev.moe_qmmq_quant(qt, wbank.as_ref(), xr, idr, nrows, n, k)?
1610 } else {
1611 rocm_dev.moe_matvec_quant(qt, wbank.as_ref(), xr, idr, nrows, n, k)?
1612 };
1613 let out = crate::tensor::from_storage(
1614 crate::Storage::Rocm(y),
1615 (nrows, n),
1616 crate::op::BackpropOp::none(),
1617 false,
1618 );
1619 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1620 }
1621 #[cfg(feature = "metal")]
1629 QStorage::Metal(s)
1630 if matches!(&*x.storage(), Storage::Metal(_))
1631 && matches!(&*ids.storage(), Storage::Metal(_)) =>
1632 {
1633 let out_dtype = x.dtype();
1634 let x = x.contiguous()?;
1635 let (xs_guard, x_l) = x.storage_and_layout();
1636 let (ids_guard, ids_l) = ids.storage_and_layout();
1637 let (Storage::Metal(x_storage), Storage::Metal(ids_storage)) =
1638 (&*xs_guard, &*ids_guard)
1639 else {
1640 unreachable!("metal MoE arm is guarded on Metal x/ids storage");
1641 };
1642 let (storage, out_shape) =
1643 s.indexed_moe_forward(self.shape(), x_storage, x_l, ids_storage, ids_l)?;
1644 let out = crate::tensor::from_storage(
1645 Storage::Metal(storage),
1646 out_shape,
1647 crate::op::BackpropOp::none(),
1648 false,
1649 );
1650 out.to_dtype(out_dtype)
1651 }
1652 QStorage::Stream(bank) => {
1656 let (e_cnt, n, k) = self.shape().dims3()?;
1657 let dtype = bank.dtype();
1658 moe_grouped_per_expert(x, ids, n, k, |eid, device| {
1659 if eid as usize >= e_cnt {
1660 crate::bail!("indexed_moe_forward: expert id {eid} >= num_experts {e_cnt}");
1661 }
1662 let bytes = bank.fetch(eid)?;
1663 QStorage::from_data(std::borrow::Cow::Borrowed(&bytes), device, dtype)
1664 })
1665 }
1666 _ => {
1667 let (e_cnt, n, k) = self.shape().dims3()?;
1673 let dtype = self.storage.dtype();
1674 let all_bytes = self.data()?;
1675 let expert_bytes = all_bytes.len() / e_cnt;
1676 moe_grouped_per_expert(x, ids, n, k, |eid, device| {
1677 let off = eid as usize * expert_bytes;
1678 QStorage::from_data(
1679 std::borrow::Cow::Borrowed(&all_bytes[off..off + expert_bytes]),
1680 device,
1681 dtype,
1682 )
1683 })
1684 }
1685 }
1686 }
1687
1688 pub fn device_ptr(&self) -> Result<*const u8> {
1689 match &self.storage {
1690 QStorage::Cuda(storage) => storage.device_ptr(),
1691 #[cfg(feature = "rocm")]
1692 QStorage::Rocm(..) => crate::bail!("not implemented"),
1693 #[cfg(feature = "vulkan")]
1694 QStorage::Vulkan(..) => crate::bail!("not implemented"),
1695 #[cfg(feature = "wgpu")]
1696 QStorage::Wgpu(..) => crate::bail!("not implemented"),
1697 QStorage::Metal(_) | QStorage::Cpu(_) | QStorage::Stream(_) => {
1698 crate::bail!("not implemented");
1699 }
1700 }
1701 }
1702
1703 #[cfg(feature = "cuda")]
1704 pub fn device_ptr_with_guard<'a>(
1705 &'a self,
1706 stream: &'a crate::cuda_backend::cudarc::driver::CudaStream,
1707 ) -> Result<(
1708 *const u8,
1709 crate::cuda_backend::cudarc::driver::SyncOnDrop<'a>,
1710 )> {
1711 self.storage.device_ptr_with_guard(stream)
1712 }
1713}
1714
1715#[derive(Clone, Debug)]
1716pub enum QMatMul {
1717 QTensor(std::sync::Arc<QTensor>),
1718 Tensor(Tensor),
1719 TensorF16(Tensor),
1720 #[cfg(feature = "vulkan")]
1726 VulkanQuant {
1727 qtensor: std::sync::Arc<QTensor>,
1728 wq: std::sync::Arc<crate::VulkanStorage>,
1729 dtype: GgmlDType,
1730 n: usize,
1731 k: usize,
1732 },
1733 #[cfg(feature = "wgpu")]
1737 WgpuQuant {
1738 qtensor: std::sync::Arc<QTensor>,
1739 wq: std::sync::Arc<crate::WgpuStorage>,
1740 dtype: GgmlDType,
1741 n: usize,
1742 k: usize,
1743 },
1744 #[cfg(feature = "rocm")]
1750 RocmQuant {
1751 qtensor: std::sync::Arc<QTensor>,
1752 wq: std::sync::Arc<crate::RocmStorage>,
1753 dtype: GgmlDType,
1754 n: usize,
1755 k: usize,
1756 },
1757}
1758
1759#[cfg(any(feature = "rocm", feature = "vulkan", feature = "wgpu"))]
1769fn cache_or_upload<S>(
1770 slot: &std::sync::OnceLock<std::sync::Arc<S>>,
1771 bank: &[u8],
1772 upload: impl FnOnce(&[u8]) -> Result<S>,
1773) -> Result<std::sync::Arc<S>> {
1774 if let Some(w) = slot.get() {
1775 return Ok(w.clone());
1776 }
1777 let w = std::sync::Arc::new(upload(bank)?);
1778 Ok(slot.get_or_init(|| w).clone())
1781}
1782
1783#[cfg(feature = "vulkan")]
1789fn vk_moe_kernel(dt: GgmlDType) -> Option<&'static str> {
1790 match dt {
1791 GgmlDType::Q4_0 => Some("moe_matvec_q4_0"),
1792 GgmlDType::Q8_0 => Some("moe_matvec_q8_0"),
1793 GgmlDType::Q4K => Some("moe_matvec_q4k"),
1794 GgmlDType::Q6K => Some("moe_matvec_q6k"),
1795 _ => None,
1796 }
1797}
1798
1799#[cfg(feature = "vulkan")]
1805fn vk_moe_blk_kernel(dt: GgmlDType, n: usize, k: usize) -> Option<&'static str> {
1806 if std::env::var_os("VK_MOE_PACKED").is_some() {
1809 return None;
1810 }
1811 match (dt, n, k) {
1812 (GgmlDType::Q4K, 768, 2048) => Some("moe_matvec_q4k_blk_gu"),
1813 (GgmlDType::Q4K, 2048, 768) => Some("moe_matvec_q4k_blk_dn"),
1814 (GgmlDType::Q6K, 2048, 768) => Some("moe_matvec_q6k_blk_dn"),
1815 _ => None,
1816 }
1817}
1818
1819#[cfg(feature = "vulkan")]
1828fn vk_moe_blk_dp4a_kernel(dt: GgmlDType, n: usize, k: usize) -> Option<(&'static str, bool)> {
1829 if std::env::var_os("VK_MOE_PACKED").is_some() || std::env::var_os("VK_MOE_DP4A_OFF").is_some()
1830 {
1831 return None;
1832 }
1833 match (dt, n, k) {
1834 (GgmlDType::Q4K, 768, 2048) => Some(("moe_matvec_q4k_dp4a_blk_gu", true)),
1835 (GgmlDType::Q4K, 2048, 768) => Some(("moe_matvec_q4k_dp4a_blk_dn", true)),
1836 (GgmlDType::Q6K, 2048, 768) => Some(("moe_matvec_q6k_dp4a_blk_dn", false)),
1837 _ => None,
1838 }
1839}
1840
1841#[cfg(feature = "wgpu")]
1843fn wgpu_moe_kernel(dt: GgmlDType) -> Option<&'static str> {
1844 match dt {
1845 GgmlDType::Q4_0 => Some("moe_matvec_q4_0"),
1846 GgmlDType::Q8_0 => Some("moe_matvec_q8_0"),
1847 GgmlDType::Q4K => Some("moe_matvec_q4k"),
1848 _ => None,
1849 }
1850}
1851
1852fn moe_grouped_per_expert(
1857 x: &Tensor,
1858 ids: &Tensor,
1859 n: usize,
1860 k: usize,
1861 mut make_storage: impl FnMut(u32, &Device) -> Result<QStorage>,
1862) -> Result<Tensor> {
1863 use crate::Module; use std::collections::HashMap;
1865 use std::sync::Arc;
1866 let device = x.device();
1867 let out_dtype = x.dtype();
1868 let (t, topk) = ids.dims2()?;
1869 let s = x.dim(1)?; let x_exp = if s == topk {
1871 x.clone()
1872 } else {
1873 x.broadcast_as((t, topk, k))?
1874 };
1875 let x_flat = x_exp
1876 .reshape((t * topk, k))?
1877 .to_dtype(DType::F32)?
1878 .contiguous()?;
1879 let ids_flat = ids.reshape((t * topk,))?.to_dtype(DType::U32)?;
1880 let ids_vec = ids_flat.to_vec1::<u32>()?;
1881 let mut groups: HashMap<u32, Vec<u32>> = HashMap::new();
1882 for (slot, eid) in ids_vec.iter().enumerate() {
1883 groups.entry(*eid).or_default().push(slot as u32);
1884 }
1885 let mut out_flat = Tensor::zeros((t * topk, n), DType::F32, device)?;
1886 for (eid, slots) in groups.into_iter() {
1887 let qs = make_storage(eid, device)?;
1888 let shape: crate::Shape = (n, k).into();
1889 let w_e = QTensor::make(qs, shape);
1890 let qm = QMatMul::from_arc(Arc::new(w_e))?;
1891 let m = slots.len();
1892 let idx = Tensor::from_vec(slots, (m,), device)?;
1893 let x_e = x_flat.index_select(&idx, 0)?; let y_e = qm.forward(&x_e)?.to_dtype(DType::F32)?; out_flat = out_flat.index_add(&idx, &y_e, 0)?;
1896 }
1897 out_flat.reshape((t, topk, n))?.to_dtype(out_dtype)
1898}
1899
1900#[cfg_attr(not(feature = "rocm"), allow(unused_variables))]
1906pub fn moe_combine(ys: &Tensor, scores: &Tensor) -> Result<Tensor> {
1907 let (t, topk, n) = ys.dims3()?;
1908 #[cfg(feature = "rocm")]
1909 if let Device::Rocm(dev) = ys.device() {
1910 let ys_c = ys.contiguous()?;
1911 let scores_c = scores.to_dtype(DType::F32)?.contiguous()?;
1912 let (ys_store, _) = ys_c.storage_and_layout();
1913 let yr = match &*ys_store {
1914 Storage::Rocm(r) => r,
1915 _ => crate::bail!("moe_combine: ys not on rocm after contiguous()"),
1916 };
1917 let (sc_store, _) = scores_c.storage_and_layout();
1918 let sr = match &*sc_store {
1919 Storage::Rocm(r) => r,
1920 _ => crate::bail!("moe_combine: scores not on rocm after contiguous()"),
1921 };
1922 let out = dev.moe_combine(yr, sr, t, topk, n)?;
1923 return Ok(crate::tensor::from_storage(
1924 Storage::Rocm(out),
1925 (t, n),
1926 crate::op::BackpropOp::none(),
1927 false,
1928 ));
1929 }
1930 let out_dtype = ys.dtype();
1935 ys.to_dtype(DType::F32)?
1936 .broadcast_mul(&scores.to_dtype(DType::F32)?.unsqueeze(D::Minus1)?)?
1937 .sum(D::Minus2)?
1938 .to_dtype(out_dtype)
1939}
1940
1941#[cfg_attr(not(feature = "rocm"), allow(unused_variables))]
1946pub fn moe_route(logits: &Tensor, topk: usize, norm: bool) -> Result<(Tensor, Tensor)> {
1947 let (ntok, n_experts) = logits.dims2()?;
1948 #[cfg(feature = "rocm")]
1949 if let Device::Rocm(dev) = logits.device() {
1950 let logits_c = logits.to_dtype(DType::F32)?.contiguous()?;
1951 let (lg_store, _) = logits_c.storage_and_layout();
1952 let lr = match &*lg_store {
1953 Storage::Rocm(r) => r,
1954 _ => crate::bail!("moe_route: logits not on rocm after contiguous()"),
1955 };
1956 let (ids, w) = dev.moe_route(lr, ntok, n_experts, topk, norm)?;
1957 let ids_t = crate::tensor::from_storage(
1958 Storage::Rocm(ids),
1959 (ntok, topk),
1960 crate::op::BackpropOp::none(),
1961 false,
1962 );
1963 let w_t = crate::tensor::from_storage(
1964 Storage::Rocm(w),
1965 (ntok, topk),
1966 crate::op::BackpropOp::none(),
1967 false,
1968 );
1969 return Ok((ids_t, w_t));
1970 }
1971 #[cfg(feature = "cuda")]
1972 if let Device::Cuda(cdev) = logits.device() {
1973 if n_experts <= 256 && topk <= 32 {
1974 let logits_c = logits.to_dtype(DType::F32)?.contiguous()?;
1975 let (lg_store, _) = logits_c.storage_and_layout();
1976 let lr = match &*lg_store {
1977 Storage::Cuda(c) => c,
1978 _ => crate::bail!("moe_route: logits not on cuda after contiguous()"),
1979 };
1980 let lview = lr.as_cuda_slice::<f32>()?.slice(0..);
1981 let (ids, w) = cuda::moe_route(&lview, ntok, n_experts, topk, norm, cdev)?;
1982 let ids_t = crate::tensor::from_storage(
1983 Storage::Cuda(ids),
1984 (ntok, topk),
1985 crate::op::BackpropOp::none(),
1986 false,
1987 );
1988 let w_t = crate::tensor::from_storage(
1989 Storage::Cuda(w),
1990 (ntok, topk),
1991 crate::op::BackpropOp::none(),
1992 false,
1993 );
1994 return Ok((ids_t, w_t));
1995 }
1996 }
1997 #[cfg(feature = "vulkan")]
2001 if let Device::Vulkan(vdev) = logits.device() {
2002 if norm && n_experts == 128 && topk == 8 {
2003 let logits_c = logits.to_dtype(DType::F32)?.contiguous()?;
2004 let (lg_store, _) = logits_c.storage_and_layout();
2005 let lv = match &*lg_store {
2006 Storage::Vulkan(v) => v,
2007 _ => crate::bail!("moe_route: logits not on vulkan after contiguous()"),
2008 };
2009 let (ids, w) = vdev.moe_route_vk(lv, ntok, n_experts, topk)?;
2010 let ids_t = crate::tensor::from_storage(
2011 Storage::Vulkan(ids),
2012 (ntok, topk),
2013 crate::op::BackpropOp::none(),
2014 false,
2015 );
2016 let w_t = crate::tensor::from_storage(
2017 Storage::Vulkan(w),
2018 (ntok, topk),
2019 crate::op::BackpropOp::none(),
2020 false,
2021 );
2022 return Ok((ids_t, w_t));
2023 }
2024 }
2025 let lf = logits.to_dtype(DType::F32)?;
2026 let mx = lf.max_keepdim(D::Minus1)?;
2027 let e = lf.broadcast_sub(&mx)?.exp()?;
2028 let z = e.sum_keepdim(D::Minus1)?;
2029 let p = e.broadcast_div(&z)?;
2030 let (sv, si) = p.sort_last_dim(false)?;
2031 let ids = si.narrow(D::Minus1, 0, topk)?.contiguous()?;
2032 let mut w = sv.narrow(D::Minus1, 0, topk)?.contiguous()?;
2033 if norm {
2034 w = w.broadcast_div(&w.sum_keepdim(D::Minus1)?)?;
2035 }
2036 Ok((ids, w))
2037}
2038
2039pub fn moe_gate_up(
2047 x: &Tensor,
2048 ids: &Tensor,
2049 gate: &QMatMul,
2050 up: &QMatMul,
2051) -> Result<(Tensor, Tensor)> {
2052 #[cfg(feature = "rocm")]
2053 {
2054 if let (QMatMul::QTensor(gq), QMatMul::QTensor(uq)) = (gate, up) {
2055 if let (QStorage::Rocm(_, dev), QStorage::Rocm(..)) = (&gq.storage, &uq.storage) {
2056 let dt = gq.storage.dtype();
2057 if dt == uq.storage.dtype() {
2058 if let Some(qt) = crate::RocmQuantType::from_ggml(dt) {
2059 let (_e, n, k) = gq.shape().dims3()?;
2060 let (t, topk) = ids.dims2()?;
2061 let use_qmmq = t > 1 && qt.qmmq_capable();
2062 if x.dim(1)? == 1 && !use_qmmq {
2063 let nrows = t * topk;
2064 let x_exp = x.broadcast_as((t, topk, k))?;
2065 let x_flat = match x_exp.dtype() {
2066 DType::BF16 | DType::F16 => {
2067 x_exp.reshape((nrows, k))?.contiguous()?
2068 }
2069 DType::F32 if qt.dp4a_active() => {
2070 x_exp.reshape((nrows, k))?.contiguous()?
2071 }
2072 _ => x_exp
2073 .reshape((nrows, k))?
2074 .to_dtype(DType::F16)?
2075 .contiguous()?,
2076 };
2077 let out_dtype = x.dtype();
2078 let ids_u32 =
2079 ids.reshape((nrows,))?.to_dtype(DType::U32)?.contiguous()?;
2080 let gwb = gq.rocm_moe_bank(dev)?;
2081 let uwb = uq.rocm_moe_bank(dev)?;
2082 let (xstore, _) = x_flat.storage_and_layout();
2083 let xr = match &*xstore {
2084 Storage::Rocm(r) => r,
2085 _ => crate::bail!("moe_gate_up: x not on rocm after contiguous()"),
2086 };
2087 let (idstore, _) = ids_u32.storage_and_layout();
2088 let idr = match &*idstore {
2089 Storage::Rocm(r) => r,
2090 _ => crate::bail!("moe_gate_up: ids not on rocm"),
2091 };
2092 let (gy, uy) = dev.moe_matvec_pair(
2093 qt,
2094 gwb.as_ref(),
2095 uwb.as_ref(),
2096 xr,
2097 idr,
2098 nrows,
2099 n,
2100 k,
2101 )?;
2102 let g = crate::tensor::from_storage(
2103 Storage::Rocm(gy),
2104 (nrows, n),
2105 crate::op::BackpropOp::none(),
2106 false,
2107 )
2108 .reshape((t, topk, n))?
2109 .to_dtype(out_dtype)?;
2110 let u = crate::tensor::from_storage(
2111 Storage::Rocm(uy),
2112 (nrows, n),
2113 crate::op::BackpropOp::none(),
2114 false,
2115 )
2116 .reshape((t, topk, n))?
2117 .to_dtype(out_dtype)?;
2118 return Ok((g, u));
2119 }
2120 }
2121 }
2122 }
2123 }
2124 }
2125 #[cfg(feature = "vulkan")]
2132 if std::env::var_os("VK_MOE_GU_FUSE_OFF").is_none() {
2133 if let (QMatMul::QTensor(gq), QMatMul::QTensor(uq)) = (gate, up) {
2134 if let (QStorage::Vulkan(_, dev), QStorage::Vulkan(..)) = (&gq.storage, &uq.storage) {
2135 let dt = gq.storage.dtype();
2136 if dt == uq.storage.dtype() && dev.has_int_dot8() {
2137 let (e_cnt, n, k) = gq.shape().dims3()?;
2138 if uq.shape().dims3()? == (e_cnt, n, k) && x.dim(1)? == 1 {
2140 if let Some((blk, with_xsum)) = vk_moe_blk_dp4a_kernel(dt, n, k) {
2141 let (t, topk) = ids.dims2()?;
2142 let nrows = t * topk;
2143 let x_flat = x
2144 .broadcast_as((t, topk, k))?
2145 .reshape((nrows, k))?
2146 .to_dtype(DType::F32)?
2147 .contiguous()?;
2148 let ids_u32 =
2149 ids.reshape((nrows,))?.to_dtype(DType::U32)?.contiguous()?;
2150 let (xstore, _) = x_flat.storage_and_layout();
2151 let xv = match &*xstore {
2152 Storage::Vulkan(v) => v,
2153 _ => {
2154 crate::bail!("moe_gate_up: x not on vulkan after contiguous()")
2155 }
2156 };
2157 let (idstore, _) = ids_u32.storage_and_layout();
2158 let idv = match &*idstore {
2159 Storage::Vulkan(v) => v,
2160 _ => crate::bail!("moe_gate_up: ids not on vulkan"),
2161 };
2162 let (xq, xs, xsum) = dev.quantize_act_q8(xv, nrows, k)?;
2164 let gbank = gq.vulkan_moe_bank_split(dev, e_cnt, n, k)?;
2165 let ubank = uq.vulkan_moe_bank_split(dev, e_cnt, n, k)?;
2166 let out_dtype = x.dtype();
2167 let (gy, uy) = if t > 1
2171 && dt == GgmlDType::Q4K
2172 && std::env::var_os("VK_MOE_PREFILL_GEMM_OFF").is_none()
2173 {
2174 let (counts, rows) =
2175 dev.moe_expert_rows_vk(idv, nrows, e_cnt, t)?;
2176 let gy = dev.mmq_q4k_id_pre_gpu(
2177 gbank.as_ref(),
2178 &xq,
2179 &xs,
2180 &xsum,
2181 &rows,
2182 &counts,
2183 nrows,
2184 e_cnt,
2185 t,
2186 n,
2187 k,
2188 )?;
2189 let uy = dev.mmq_q4k_id_pre_gpu(
2190 ubank.as_ref(),
2191 &xq,
2192 &xs,
2193 &xsum,
2194 &rows,
2195 &counts,
2196 nrows,
2197 e_cnt,
2198 t,
2199 n,
2200 k,
2201 )?;
2202 (gy, uy)
2203 } else {
2204 let gy = dev.moe_matvec_blk_dp4a_pre_gpu(
2205 blk,
2206 with_xsum,
2207 gbank.as_ref(),
2208 &xq,
2209 &xs,
2210 &xsum,
2211 idv,
2212 nrows,
2213 n,
2214 )?;
2215 let uy = dev.moe_matvec_blk_dp4a_pre_gpu(
2216 blk,
2217 with_xsum,
2218 ubank.as_ref(),
2219 &xq,
2220 &xs,
2221 &xsum,
2222 idv,
2223 nrows,
2224 n,
2225 )?;
2226 (gy, uy)
2227 };
2228 let shape = |o| -> Result<Tensor> {
2229 crate::tensor::from_storage(
2230 Storage::Vulkan(o),
2231 (nrows, n),
2232 crate::op::BackpropOp::none(),
2233 false,
2234 )
2235 .reshape((t, topk, n))?
2236 .to_dtype(out_dtype)
2237 };
2238 return Ok((shape(gy)?, shape(uy)?));
2239 }
2240 }
2241 }
2242 }
2243 }
2244 }
2245 Ok((
2246 gate.indexed_moe_forward(x, ids)?,
2247 up.indexed_moe_forward(x, ids)?,
2248 ))
2249}
2250
2251impl QMatMul {
2252 pub fn from_arc(qtensor: std::sync::Arc<QTensor>) -> Result<Self> {
2253 #[cfg(feature = "vulkan")]
2259 {
2260 let dt = qtensor.dtype();
2261 let native_vk = matches!(
2262 dt,
2263 GgmlDType::Q4_0
2264 | GgmlDType::Q8_0
2265 | GgmlDType::Q4K
2266 | GgmlDType::Q5K
2267 | GgmlDType::Q6K
2268 | GgmlDType::Q2K
2269 | GgmlDType::Q3K
2270 | GgmlDType::IQ4_XS
2271 | GgmlDType::IQ4_NL
2272 | GgmlDType::TQ2_0
2273 | GgmlDType::IQ2_XXS
2274 | GgmlDType::IQ2_S
2275 | GgmlDType::IQ3_XXS
2276 | GgmlDType::IQ3_S
2277 | GgmlDType::IQ1_S
2278 | GgmlDType::IQ1_M
2279 | GgmlDType::IQ2_XS
2280 );
2281 if native_vk {
2282 if let Device::Vulkan(d) = qtensor.device() {
2283 if let Ok((n, k)) = qtensor.shape().dims2() {
2284 let blk = dt.block_size();
2285 if k % blk == 0 {
2286 let bytes = qtensor.data()?;
2287 let wq = match dt {
2294 GgmlDType::Q6K => d.quantize_q6k(&bytes, n, k)?,
2295 GgmlDType::Q3K => d.quantize_q3k(&bytes, n, k)?,
2296 GgmlDType::Q8_0 => d.quantize_q8_blocks(&bytes, n, k)?,
2297 GgmlDType::IQ2_XXS => d.quantize_iq2xxs(&bytes, n, k)?,
2298 GgmlDType::IQ2_XS => d.quantize_iq2xs(&bytes, n, k)?,
2299 GgmlDType::IQ1_M => d.quantize_iq1m(&bytes, n, k)?,
2300 GgmlDType::IQ1_S => d.quantize_iq1s(&bytes, n, k)?,
2301 GgmlDType::IQ3_S => d.quantize_iq3s(&bytes, n, k)?,
2302 GgmlDType::IQ3_XXS => d.quantize_iq3xxs(&bytes, n, k)?,
2303 GgmlDType::IQ2_S => d.quantize_iq2s(&bytes, n, k)?,
2304 _ => d.upload_qweight(&bytes)?,
2305 };
2306 return Ok(Self::VulkanQuant {
2307 qtensor,
2308 wq: std::sync::Arc::new(wq),
2309 dtype: dt,
2310 n,
2311 k,
2312 });
2313 }
2314 }
2315 }
2316 }
2317 }
2318 #[cfg(feature = "wgpu")]
2321 {
2322 let dt = qtensor.dtype();
2323 let native_wgpu = matches!(dt, GgmlDType::Q4_0 | GgmlDType::Q8_0 | GgmlDType::Q4K);
2324 if native_wgpu {
2325 if let Device::Wgpu(d) = qtensor.device() {
2326 if let Ok((n, k)) = qtensor.shape().dims2() {
2327 let blk = dt.block_size();
2328 if k % blk == 0 {
2329 let bytes = qtensor.data()?;
2330 let wq = d.upload_qweight(&bytes)?;
2331 return Ok(Self::WgpuQuant {
2332 qtensor,
2333 wq: std::sync::Arc::new(wq),
2334 dtype: dt,
2335 n,
2336 k,
2337 });
2338 }
2339 }
2340 }
2341 }
2342 }
2343 #[cfg(feature = "rocm")]
2349 {
2350 let dt = qtensor.dtype();
2351 if let Some(qt) = crate::RocmQuantType::from_ggml(dt) {
2356 if let Device::Rocm(d) = qtensor.device() {
2357 if let Ok((n, k)) = qtensor.shape().dims2() {
2358 let blk_ok = k % qt.block_elems() == 0;
2359 if blk_ok {
2360 use crate::backend::BackendDevice;
2361 let bytes = qtensor.data()?;
2362 let wq = d.storage_from_slice(bytes.as_ref())?;
2363 return Ok(Self::RocmQuant {
2364 qtensor,
2365 wq: std::sync::Arc::new(wq),
2366 dtype: dt,
2367 n,
2368 k,
2369 });
2370 }
2371 }
2372 }
2373 }
2374 }
2375 #[cfg(feature = "rocm")]
2381 {
2382 if qtensor.device().is_rocm()
2383 && qtensor.shape().dims().len() == 3
2384 && crate::RocmQuantType::from_ggml(qtensor.dtype()).is_some()
2385 {
2386 return Ok(Self::QTensor(qtensor));
2387 }
2388 }
2389 #[cfg(feature = "vulkan")]
2395 {
2396 if qtensor.device().is_vulkan()
2397 && qtensor.shape().dims().len() == 3
2398 && vk_moe_kernel(qtensor.dtype()).is_some()
2399 {
2400 return Ok(Self::QTensor(qtensor));
2401 }
2402 }
2403 #[cfg(feature = "wgpu")]
2404 {
2405 if qtensor.device().is_wgpu()
2406 && qtensor.shape().dims().len() == 3
2407 && wgpu_moe_kernel(qtensor.dtype()).is_some()
2408 {
2409 return Ok(Self::QTensor(qtensor));
2410 }
2411 }
2412 let dequantize = match qtensor.dtype() {
2413 GgmlDType::F32 | GgmlDType::F16 | GgmlDType::BF16 | GgmlDType::I32 => true,
2414 _ => {
2417 qtensor.device().is_vulkan()
2418 || qtensor.device().is_wgpu()
2419 || qtensor.device().is_rocm()
2420 }
2421 };
2422 let t = if dequantize {
2423 if qtensor.device().is_rocm() {
2427 Self::TensorF16(qtensor.dequantize_f16(&qtensor.device())?)
2428 } else {
2429 Self::Tensor(qtensor.dequantize(&qtensor.device())?)
2430 }
2431 } else {
2432 Self::QTensor(qtensor)
2433 };
2434 Ok(t)
2435 }
2436
2437 pub fn from_qtensor(qtensor: QTensor) -> Result<Self> {
2438 Self::from_arc(std::sync::Arc::new(qtensor))
2439 }
2440
2441 pub fn dequantize_f16(&self) -> Result<Tensor> {
2442 match self {
2443 Self::QTensor(t) => t.dequantize_f16(&t.device()),
2444 Self::Tensor(t) => t.to_dtype(DType::F16),
2445 Self::TensorF16(t) => Ok(t.clone()),
2446 #[cfg(feature = "rocm")]
2447 Self::RocmQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
2448 #[cfg(feature = "vulkan")]
2449 Self::VulkanQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
2450 #[cfg(feature = "wgpu")]
2451 Self::WgpuQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
2452 }
2453 }
2454
2455 pub fn forward_via_f16(&self, xs: &Tensor) -> Result<Tensor> {
2456 let w = self.dequantize_f16()?;
2457 let in_dtype = xs.dtype();
2458 let w = match *xs.dims() {
2459 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
2460 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
2461 _ => w.t()?,
2462 };
2463 xs.to_dtype(DType::F16)?.matmul(&w)?.to_dtype(in_dtype)
2464 }
2465
2466 pub fn indexed_moe_forward(&self, x: &Tensor, ids: &Tensor) -> Result<Tensor> {
2467 match self {
2468 Self::QTensor(t) => t.indexed_moe_forward(x, ids),
2469 #[cfg(feature = "rocm")]
2474 Self::RocmQuant {
2475 qtensor, wq, dtype, ..
2476 } if crate::RocmQuantType::from_ggml(*dtype).is_some() => {
2477 let qt = crate::RocmQuantType::from_ggml(*dtype).unwrap();
2478 let wbank = wq.as_ref();
2479 let (_e_cnt, n, k) = qtensor.shape().dims3()?;
2481 let (t, topk) = ids.dims2()?;
2482 let s = x.dim(1)?; let x_exp = if s == topk {
2484 x.clone()
2485 } else {
2486 x.broadcast_as((t, topk, k))?
2487 };
2488 let nrows = t * topk;
2489 let use_qmmq = t > 1 && qt.qmmq_capable();
2495 let x_flat = match x_exp.dtype() {
2496 DType::F16 | DType::F32 if use_qmmq => {
2500 x_exp.reshape((nrows, k))?.contiguous()?
2501 }
2502 _ if use_qmmq => x_exp
2503 .reshape((nrows, k))?
2504 .to_dtype(DType::F16)?
2505 .contiguous()?,
2506 DType::BF16 | DType::F16 => x_exp.reshape((nrows, k))?.contiguous()?,
2507 DType::F32 if qt.dp4a_active() => x_exp.reshape((nrows, k))?.contiguous()?,
2510 _ => x_exp
2511 .reshape((nrows, k))?
2512 .to_dtype(DType::F16)?
2513 .contiguous()?,
2514 };
2515 let out_dtype = x.dtype();
2516 let ids_u32 = ids
2521 .reshape((nrows,))?
2522 .to_dtype(crate::DType::U32)?
2523 .contiguous()?;
2524 let (xstore, _) = x_flat.storage_and_layout();
2525 let xr = match &*xstore {
2526 crate::Storage::Rocm(r) => r,
2527 _ => crate::bail!("rocm MoE: x not on rocm after contiguous()"),
2528 };
2529 let (idstore, _) = ids_u32.storage_and_layout();
2530 let idr = match &*idstore {
2531 crate::Storage::Rocm(r) => r,
2532 _ => crate::bail!("rocm MoE: ids not on rocm"),
2533 };
2534 let y = if use_qmmq {
2535 wbank
2536 .device
2537 .moe_qmmq_quant(qt, wbank, xr, idr, nrows, n, k)?
2538 } else {
2539 wbank
2540 .device
2541 .moe_matvec_quant(qt, wbank, xr, idr, nrows, n, k)?
2542 };
2543 let out = crate::tensor::from_storage(
2544 crate::Storage::Rocm(y),
2545 (nrows, n),
2546 crate::op::BackpropOp::none(),
2547 false,
2548 );
2549 out.reshape((t, topk, n))?.to_dtype(out_dtype)
2550 }
2551 #[cfg(feature = "rocm")]
2553 Self::RocmQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
2554 #[cfg(feature = "vulkan")]
2555 Self::VulkanQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
2556 #[cfg(feature = "wgpu")]
2557 Self::WgpuQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
2558 _ => {
2559 panic!("Not implemented!")
2560 }
2561 }
2562 }
2563
2564 pub fn embedding(&self, ids: &Tensor) -> Result<Tensor> {
2565 match self {
2566 Self::QTensor(t) => t.embedding(ids),
2567 #[cfg(feature = "vulkan")]
2568 Self::VulkanQuant { qtensor, .. } => qtensor.embedding(ids),
2569 #[cfg(feature = "wgpu")]
2570 Self::WgpuQuant { qtensor, .. } => qtensor.embedding(ids),
2571 #[cfg(feature = "rocm")]
2572 Self::RocmQuant { qtensor, .. } => qtensor.embedding(ids),
2573 Self::Tensor(w) | Self::TensorF16(w) => {
2574 let mut final_dims = ids.dims().to_vec();
2575 final_dims.push(w.dim(D::Minus1)?);
2576 let ids = ids.to_device(w.device())?.flatten_all()?;
2577 w.index_select(&ids, 0)?.reshape(final_dims)
2578 }
2579 }
2580 }
2581}
2582
2583impl QTensor {
2584 pub fn gemv_fused_shared_lhs(ts: &[&Self], lhs: &Tensor) -> Result<Option<Vec<Tensor>>> {
2587 #[cfg(target_arch = "aarch64")]
2588 {
2589 if ts.is_empty() || !lhs.device().is_cpu() || lhs.dtype() != crate::DType::F32 {
2590 return Ok(None);
2591 }
2592 if !lhs.is_contiguous() {
2593 return Ok(None);
2594 }
2595 let dims = lhs.dims();
2596 let Some((&k, batch)) = dims.split_last() else {
2597 return Ok(None);
2598 };
2599 if batch.iter().product::<usize>() != 1 {
2600 return Ok(None);
2601 }
2602 let mut ns = Vec::with_capacity(ts.len());
2603 let mut parts: Vec<(&dyn QuantizedType, &repack::PackedCache)> =
2604 Vec::with_capacity(ts.len());
2605 for t in ts {
2606 let (n, tk) = t.shape.dims2()?;
2607 if tk != k {
2608 return Ok(None);
2609 }
2610 let QStorage::Cpu(s) = &t.storage else {
2611 return Ok(None);
2612 };
2613 ns.push(n);
2614 parts.push((s.as_ref(), &t.repacked_qs));
2615 }
2616 let storage = lhs.storage();
2617 let crate::Storage::Cpu(cpu) = &*storage else {
2618 return Ok(None);
2619 };
2620 let slice = cpu.as_slice::<f32>()?;
2621 let offset = lhs.layout().start_offset();
2622 let slice = &slice[offset..offset + k];
2623 let mut dsts: Vec<Vec<f32>> = ns.iter().map(|&n| vec![0f32; n]).collect();
2624 if !repack::try_gemv_fused(k, slice, &parts, &mut dsts)? {
2625 return Ok(None);
2626 }
2627 drop(storage);
2628 let outs = dsts
2629 .into_iter()
2630 .zip(&ns)
2631 .map(|(d, &n)| {
2632 let mut shape = dims.to_vec();
2633 *shape.last_mut().unwrap() = n;
2634 Tensor::from_vec(d, shape, &crate::Device::Cpu)
2635 })
2636 .collect::<Result<Vec<_>>>()?;
2637 Ok(Some(outs))
2638 }
2639 #[cfg(target_arch = "x86_64")]
2640 {
2641 if repack_x86::level() != Some(repack_x86::X86Level::Avx512Vnni) {
2644 return Ok(None);
2645 }
2646 if ts.is_empty() || !lhs.device().is_cpu() || lhs.dtype() != crate::DType::F32 {
2647 return Ok(None);
2648 }
2649 if !lhs.is_contiguous() {
2650 return Ok(None);
2651 }
2652 let dims = lhs.dims();
2653 let Some((&k, batch)) = dims.split_last() else {
2654 return Ok(None);
2655 };
2656 let m: usize = batch.iter().product::<usize>().max(1);
2657 let dtype = ts[0].dtype();
2658 let mut shapes = Vec::with_capacity(ts.len());
2659 for t in ts {
2660 let Ok((n, tk)) = t.shape.dims2() else {
2661 return Ok(None);
2662 };
2663 if tk != k || t.dtype() != dtype || !repack_x86::select(dtype, n, k) {
2664 return Ok(None);
2665 }
2666 shapes.push(n);
2667 }
2668 let guard = lhs.storage();
2669 let crate::Storage::Cpu(cpu) = &*guard else {
2670 return Ok(None);
2671 };
2672 let slice = cpu.as_slice::<f32>()?;
2673 let offset = lhs.layout().start_offset();
2674 let lhs_data = &slice[offset..offset + m * k];
2675 let lhs_q = repack_x86::quantize_lhs(lhs_data, m, k);
2676 let packs: Vec<&repack_x86::PackedX86> = ts
2677 .iter()
2678 .enumerate()
2679 .map(|(i, t)| {
2680 let n = shapes[i];
2681 t.repacked_qs
2682 .x86_get_or_init(|| repack_x86::pack(dtype, t.storage_ref(), n, k))
2683 })
2684 .collect();
2685 let parts: Vec<(&repack_x86::PackedX86, usize)> = packs
2686 .iter()
2687 .zip(shapes.iter())
2688 .map(|(p, &n)| (*p, n))
2689 .collect();
2690 let mut dsts: Vec<Vec<f32>> = shapes.iter().map(|&n| vec![0f32; m * n]).collect();
2691 repack_x86::gemv_fused(&parts, &lhs_q, m, k, &mut dsts);
2692 drop(guard);
2693 let mut out = Vec::with_capacity(ts.len());
2694 let mut out_dims = dims.to_vec();
2695 for (dst, &n) in dsts.into_iter().zip(shapes.iter()) {
2696 *out_dims.last_mut().unwrap() = n;
2697 out.push(Tensor::from_vec(
2698 dst,
2699 out_dims.clone(),
2700 &crate::Device::Cpu,
2701 )?);
2702 }
2703 Ok(Some(out))
2704 }
2705
2706 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
2707 {
2708 let _ = (ts, lhs);
2709 Ok(None)
2710 }
2711 }
2712}
2713
2714impl QTensor {
2715 #[cfg(target_arch = "x86_64")]
2716 pub(crate) fn storage_ref(&self) -> &dyn QuantizedType {
2717 match &self.storage {
2718 QStorage::Cpu(s) => s.as_ref(),
2719 _ => unreachable!("cpu-only path"),
2720 }
2721 }
2722
2723 pub fn indexed_gemv(&self, x: &Tensor, ids: &Tensor) -> Result<Option<Tensor>> {
2727 #[cfg(target_arch = "aarch64")]
2728 {
2729 if !x.device().is_cpu()
2730 || !matches!(x.dtype(), crate::DType::F32 | crate::DType::BF16)
2731 || !x.is_contiguous()
2732 {
2733 return Ok(None);
2734 }
2735 let Ok((n_experts, n_out, k)) = self.shape.dims3() else {
2736 return Ok(None);
2737 };
2738 let Ok((batch, x_t, xk)) = x.dims3() else {
2739 return Ok(None);
2740 };
2741 let Ok((ids_b, topk)) = ids.dims2() else {
2742 return Ok(None);
2743 };
2744 if xk != k || ids_b != batch || (x_t != 1 && x_t != topk) {
2745 return Ok(None);
2746 }
2747 let QStorage::Cpu(storage) = &self.storage else {
2748 return Ok(None);
2749 };
2750 let ids_v: Vec<u32> = ids
2751 .to_dtype(crate::DType::U32)?
2752 .flatten_all()?
2753 .to_vec1::<u32>()?;
2754 if ids_v.iter().any(|&e| e as usize >= n_experts) {
2755 crate::bail!("expert index out of range");
2756 }
2757 let guard = x.storage();
2758 let crate::Storage::Cpu(cpu) = &*guard else {
2759 return Ok(None);
2760 };
2761 let offset = x.layout().start_offset();
2762 let n_rows = batch * x_t;
2763 let widened;
2764 let lhs: &[f32] = if x.dtype() == crate::DType::BF16 {
2765 let bslice = cpu.as_slice::<half::bf16>()?;
2766 widened = widen_bf16(&bslice[offset..offset + n_rows * k]);
2767 &widened
2768 } else {
2769 let slice = cpu.as_slice::<f32>()?;
2770 &slice[offset..offset + n_rows * k]
2771 };
2772 let mut dst = vec![0f32; batch * topk * n_out];
2773 let ok = repack::try_indexed_gemv(
2774 storage.as_ref(),
2775 &self.repacked_qs,
2776 n_experts,
2777 n_out,
2778 k,
2779 lhs,
2780 n_rows,
2781 &ids_v,
2782 topk,
2783 &mut dst,
2784 )?;
2785 drop(guard);
2786 if !ok {
2787 return Ok(None);
2788 }
2789 let out = Tensor::from_vec(dst, (batch, topk, n_out), &crate::Device::Cpu)?;
2790 if x.dtype() == crate::DType::BF16 {
2791 Ok(Some(out.to_dtype(crate::DType::BF16)?))
2792 } else {
2793 Ok(Some(out))
2794 }
2795 }
2796 #[cfg(not(target_arch = "aarch64"))]
2797 {
2798 let _ = (x, ids);
2799 Ok(None)
2800 }
2801 }
2802}
2803
2804fn widen_bf16(src: &[half::bf16]) -> Vec<f32> {
2805 let mut out: Vec<f32> = Vec::with_capacity(src.len());
2806 let out_ptr = out.as_mut_ptr() as usize;
2807 let n_units = src.len().div_ceil(WIDEN_CHUNK);
2808 crate::utils::barrier_pool().execute_chunked(n_units, |range| {
2809 let out_ptr = out_ptr as *mut f32;
2810 for unit in range {
2811 let lo = unit * WIDEN_CHUNK;
2812 let hi = src.len().min(lo + WIDEN_CHUNK);
2813 for (i, v) in src[lo..hi].iter().enumerate() {
2814 unsafe { *out_ptr.add(lo + i) = v.to_f32() };
2815 }
2816 }
2817 });
2818 unsafe { out.set_len(src.len()) };
2820 out
2821}
2822
2823fn narrow_bf16(src: &[f32]) -> Vec<half::bf16> {
2824 let mut out: Vec<half::bf16> = Vec::with_capacity(src.len());
2825 let out_ptr = out.as_mut_ptr() as usize;
2826 let n_units = src.len().div_ceil(WIDEN_CHUNK);
2827 crate::utils::barrier_pool().execute_chunked(n_units, |range| {
2828 let out_ptr = out_ptr as *mut half::bf16;
2829 for unit in range {
2830 let lo = unit * WIDEN_CHUNK;
2831 let hi = src.len().min(lo + WIDEN_CHUNK);
2832 for (i, v) in src[lo..hi].iter().enumerate() {
2833 unsafe { *out_ptr.add(lo + i) = half::bf16::from_f32(*v) };
2834 }
2835 }
2836 });
2837 unsafe { out.set_len(src.len()) };
2839 out
2840}
2841
2842const WIDEN_CHUNK: usize = 32 * 1024;
2843
2844impl crate::CustomOp1 for QTensor {
2845 fn name(&self) -> &'static str {
2846 "qmatmul"
2847 }
2848
2849 fn cpu_fwd(
2850 &self,
2851 storage: &crate::CpuStorage,
2852 layout: &crate::Layout,
2853 ) -> Result<(crate::CpuStorage, Shape)> {
2854 if !layout.is_contiguous() {
2855 crate::bail!("input tensor is not contiguous {layout:?}")
2856 }
2857 let src_shape = layout.shape();
2858 let (n, k) = self.shape.dims2()?;
2860 if src_shape.rank() < 2 {
2861 crate::bail!("input tensor has only one dimension {layout:?}")
2862 }
2863 let mut dst_shape = src_shape.dims().to_vec();
2864 let last_k = dst_shape.pop().unwrap();
2865 if last_k != k {
2866 crate::bail!("input tensor {layout:?} incompatible with {:?}", self.shape)
2867 }
2868 dst_shape.push(n);
2869 let dst_shape = Shape::from(dst_shape);
2870 #[allow(clippy::infallible_destructuring_match)]
2871 let self_storage = match &self.storage {
2872 QStorage::Cpu(storage) => storage,
2873 #[cfg(feature = "rocm")]
2874 QStorage::Rocm(..) => crate::bail!("Invalid storage"),
2875 #[cfg(feature = "vulkan")]
2876 QStorage::Vulkan(..) => crate::bail!("Invalid storage"),
2877 #[cfg(feature = "wgpu")]
2878 QStorage::Wgpu(..) => crate::bail!("Invalid storage"),
2879 QStorage::Metal(_) | QStorage::Cuda(_) | QStorage::Stream(_) => {
2880 crate::bail!("Invalid storage")
2881 }
2882 };
2883 match storage.dtype() {
2884 DType::F32 => {
2885 let slice = storage.as_slice::<f32>()?;
2886 let slice =
2887 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
2888 let mut dst_storage = vec![0f32; dst_shape.elem_count()];
2889
2890 let mkn = (dst_shape.elem_count() / n, k, n);
2891 let used_packed = repack::try_matmul_f32(
2892 self_storage.as_ref(),
2893 &self.repacked_qs,
2894 mkn,
2895 slice,
2896 &mut dst_storage,
2897 )?;
2898 if used_packed {
2899 return Ok((crate::CpuStorage::F32(dst_storage), dst_shape));
2900 }
2901
2902 self_storage.matmul_t(mkn, slice, &mut dst_storage)?;
2903 Ok((crate::CpuStorage::F32(dst_storage), dst_shape))
2904 }
2905 DType::F16 => {
2906 let slice = storage.as_slice::<f16>()?;
2907 let slice =
2908 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
2909 let mut dst_storage = vec![f16::ZERO; dst_shape.elem_count()];
2910 self_storage.matmul_t_f16(
2911 (dst_shape.elem_count() / n, k, n),
2912 slice,
2913 &mut dst_storage,
2914 )?;
2915 Ok((crate::CpuStorage::F16(dst_storage), dst_shape))
2916 }
2917 DType::BF16 => {
2918 let slice = storage.as_slice::<half::bf16>()?;
2920 let slice =
2921 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
2922 let lhs = widen_bf16(slice);
2923 let mut dst_storage = vec![0f32; dst_shape.elem_count()];
2924
2925 let mkn = (dst_shape.elem_count() / n, k, n);
2926 let used_packed = repack::try_matmul_f32(
2927 self_storage.as_ref(),
2928 &self.repacked_qs,
2929 mkn,
2930 &lhs,
2931 &mut dst_storage,
2932 )?;
2933 if !used_packed {
2934 self_storage.matmul_t(mkn, &lhs, &mut dst_storage)?;
2935 }
2936 let dst: Vec<half::bf16> = narrow_bf16(&dst_storage);
2937 Ok((crate::CpuStorage::BF16(dst), dst_shape))
2938 }
2939 _ => crate::bail!("Expected f32/f16/bf16"),
2940 }
2941 }
2942
2943 fn metal_fwd(
2944 &self,
2945 storage: &crate::MetalStorage,
2946 layout: &crate::Layout,
2947 ) -> Result<(crate::MetalStorage, Shape)> {
2948 let self_storage = match &self.storage {
2949 QStorage::Metal(metal) => metal,
2950 _ => unreachable!("Cannot call metal matmul on non metal QTensor"),
2951 };
2952 self_storage.fwd(&self.shape, storage, layout)
2953 }
2954
2955 fn cuda_fwd(
2956 &self,
2957 storage: &crate::CudaStorage,
2958 layout: &crate::Layout,
2959 ) -> Result<(crate::CudaStorage, Shape)> {
2960 let self_storage = match &self.storage {
2961 QStorage::Cuda(cuda) => cuda,
2962 _ => unreachable!("Cannot call cuda matmul on non cuda QTensor"),
2963 };
2964 self_storage.fwd(&self.shape, storage, layout)
2965 }
2966}
2967
2968fn dense_matmul(xs: &Tensor, w: &Tensor) -> Result<Tensor> {
2978 let k = *w.dims().last().unwrap();
2979 let rows = xs.elem_count() / k;
2980 if rows == 1 && xs.device().is_rocm() {
2981 let n = w.dim(0)?;
2982 #[cfg(feature = "rocm")]
2983 {
2984 let d = match xs.device() {
2988 Device::Rocm(d) => d.clone(),
2989 _ => unreachable!(),
2990 };
2991 let xs1 = xs.reshape((k,))?.to_dtype(w.dtype())?.contiguous()?;
2992 let w = w.contiguous()?;
2993 let (wstore, _) = w.storage_and_layout();
2994 let wr = match &*wstore {
2995 crate::Storage::Rocm(r) => r,
2996 _ => crate::bail!("dense_matmul: weight not on rocm"),
2997 };
2998 let (xstore, _) = xs1.storage_and_layout();
2999 let xr = match &*xstore {
3000 crate::Storage::Rocm(r) => r,
3001 _ => crate::bail!("dense_matmul: x not on rocm"),
3002 };
3003 let y = d.dense_gemv(wr, xr, n, k)?;
3004 let mut dims = xs.dims().to_vec();
3005 *dims.last_mut().unwrap() = n;
3006 return crate::tensor::from_storage(
3007 crate::Storage::Rocm(y),
3008 dims,
3009 crate::op::BackpropOp::none(),
3010 false,
3011 )
3012 .to_dtype(xs.dtype());
3013 }
3014 #[cfg(not(feature = "rocm"))]
3015 {
3016 let out = xs.reshape((1, k))?.broadcast_mul(w)?.sum(D::Minus1)?;
3017 let mut dims = xs.dims().to_vec();
3018 *dims.last_mut().unwrap() = n;
3019 return out.reshape(dims);
3020 }
3021 }
3022 let w = match *xs.dims() {
3023 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3024 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3025 _ => w.t()?,
3026 };
3027 xs.matmul(&w)
3028}
3029
3030#[cfg(feature = "vulkan")]
3045fn vulkan_prefill_gemm_max_rows(dtype: GgmlDType) -> usize {
3046 match dtype {
3047 GgmlDType::Q4_0 | GgmlDType::Q8_0 | GgmlDType::Q4K | GgmlDType::Q5K | GgmlDType::Q6K => {
3048 usize::MAX
3049 }
3050 _ => 0,
3051 }
3052}
3053
3054#[cfg(feature = "vulkan")]
3061fn vulkan_act_offset0(xs: &Tensor) -> Result<Tensor> {
3062 let xs = xs.contiguous()?;
3063 if xs.layout().start_offset() == 0 {
3064 Ok(xs)
3065 } else {
3066 xs.force_contiguous()
3067 }
3068}
3069
3070impl crate::Module for QMatMul {
3071 fn forward(&self, xs: &Tensor) -> Result<Tensor> {
3072 match self {
3073 #[cfg(feature = "rocm")]
3074 Self::RocmQuant {
3075 qtensor,
3076 wq,
3077 dtype,
3078 n,
3079 k,
3080 } => {
3081 let xs_recovered = if xs.device().is_rocm() {
3085 None
3086 } else {
3087 Some(xs.to_device(&qtensor.device())?)
3088 };
3089 let xs = xs_recovered.as_ref().unwrap_or(xs);
3090 let rows: usize = xs.elem_count() / *k;
3091 #[cfg(feature = "rocm")]
3097 let unified_qt = crate::RocmQuantType::from_ggml(*dtype);
3098 #[cfg(not(feature = "rocm"))]
3099 let unified_qt: Option<()> = None;
3100 #[cfg(feature = "rocm")]
3105 let qmmq_ok = unified_qt.map(|qt| qt.qmmq_capable()).unwrap_or(false);
3106 #[cfg(not(feature = "rocm"))]
3107 let qmmq_ok = false;
3108 if rows == 1 && unified_qt.is_some() {
3109 #[cfg(feature = "rocm")]
3120 let keep_f32 = unified_qt.map(|qt| qt.dp4a_active()).unwrap_or(false);
3121 #[cfg(not(feature = "rocm"))]
3122 let keep_f32 = false;
3123 let xs = match xs.dtype() {
3124 DType::BF16 | DType::F16 => xs.contiguous()?,
3125 DType::F32 if keep_f32 => xs.contiguous()?,
3126 _ => xs.to_dtype(DType::F16)?.contiguous()?,
3127 };
3128 let d = match xs.device() {
3129 Device::Rocm(d) => d,
3130 _ => crate::bail!("RocmQuant input not on rocm"),
3131 };
3132 let y = {
3133 let (store, _) = xs.storage_and_layout();
3134 let xr = match &*store {
3135 crate::Storage::Rocm(r) => r,
3136 _ => crate::bail!("RocmQuant expected rocm storage"),
3137 };
3138 #[cfg(feature = "rocm")]
3139 {
3140 d.matvec_quant(unified_qt.unwrap(), wq, xr, *n, *k)?
3141 }
3142 #[cfg(not(feature = "rocm"))]
3143 {
3144 crate::bail!("rocm feature disabled")
3145 }
3146 };
3147 let mut dims = xs.dims().to_vec();
3148 let last = dims.len() - 1;
3149 dims[last] = *n;
3150 Ok(crate::tensor::from_storage(
3151 crate::Storage::Rocm(y),
3152 dims,
3153 crate::op::BackpropOp::none(),
3154 false,
3155 ))
3156 } else if let Some(qt) = unified_qt.filter(|_| qmmq_ok) {
3157 let xs = xs.to_dtype(DType::F16)?.contiguous()?;
3166 let d = match xs.device() {
3167 Device::Rocm(d) => d,
3168 _ => crate::bail!("RocmQuant input not on rocm"),
3169 };
3170 let m = xs.elem_count() / *k;
3171 let y = {
3172 let (store, _) = xs.storage_and_layout();
3173 let xr = match &*store {
3174 crate::Storage::Rocm(r) => r,
3175 _ => crate::bail!("RocmQuant expected rocm storage"),
3176 };
3177 #[cfg(feature = "rocm")]
3178 {
3179 d.qmmq_quant(qt, xr, wq, m, *n, *k)?
3180 }
3181 #[cfg(not(feature = "rocm"))]
3182 {
3183 let _ = qt;
3184 crate::bail!("rocm feature disabled")
3185 }
3186 };
3187 let mut dims = xs.dims().to_vec();
3188 let last = dims.len() - 1;
3189 dims[last] = *n;
3190 Ok(crate::tensor::from_storage(
3191 crate::Storage::Rocm(y),
3192 dims,
3193 crate::op::BackpropOp::none(),
3194 false,
3195 ))
3196 } else {
3197 let w = qtensor.dequantize_f16(&xs.device())?;
3201 let w = match *xs.dims() {
3202 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3203 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3204 _ => w.t()?,
3205 };
3206 xs.to_dtype(DType::F16)?.matmul(&w)
3207 }
3208 }
3209 #[cfg(feature = "vulkan")]
3210 Self::VulkanQuant {
3211 qtensor,
3212 wq,
3213 dtype,
3214 n,
3215 k,
3216 } => {
3217 let vdev = qtensor.device();
3222 let xs_on_vdev = if xs.device().same_device(&vdev) {
3223 None
3224 } else {
3225 Some(xs.to_device(&vdev)?)
3226 };
3227 let xs = xs_on_vdev.as_ref().unwrap_or(xs);
3228 let rows: usize = xs.elem_count() / *k;
3229 if rows == 1 {
3230 let xs = vulkan_act_offset0(xs)?;
3233 let d = match xs.device() {
3234 Device::Vulkan(d) => d,
3235 _ => crate::bail!("VulkanQuant input not on vulkan"),
3236 };
3237 let y = {
3238 let (store, _) = xs.storage_and_layout();
3239 let xv = match &*store {
3240 crate::Storage::Vulkan(v) => v,
3241 _ => crate::bail!("VulkanQuant expected vulkan storage"),
3242 };
3243 match dtype {
3244 GgmlDType::Q4_0 => d.matvec_q4_0_gpu(wq, xv, *n, *k)?,
3245 GgmlDType::Q8_0 => d.matvec_q8_gpu(wq, xv, *n, *k)?,
3248 GgmlDType::Q4K => d.matvec_q4k_gpu(wq, xv, *n, *k)?,
3249 GgmlDType::Q5K => d.matvec_q5k_gpu(wq, xv, *n, *k)?,
3250 GgmlDType::Q6K => d.matvec_q6k_gpu(wq, xv, *n, *k)?,
3251 GgmlDType::Q2K => d.matvec_q2k_gpu(wq, xv, *n, *k)?,
3252 GgmlDType::Q3K => d.matvec_q3k_gpu(wq, xv, *n, *k)?,
3253 GgmlDType::IQ4_XS => d.matvec_iq4xs_gpu(wq, xv, *n, *k)?,
3254 GgmlDType::IQ4_NL => d.matvec_iq4nl_gpu(wq, xv, *n, *k)?,
3255 GgmlDType::IQ2_XXS => d.matvec_iq2xxs_gpu(wq, xv, *n, *k)?,
3256 GgmlDType::IQ2_XS => d.matvec_iq2xs_gpu(wq, xv, *n, *k)?,
3257 GgmlDType::IQ1_M => d.matvec_iq1m_gpu(wq, xv, *n, *k)?,
3258 GgmlDType::IQ1_S => d.matvec_iq1s_gpu(wq, xv, *n, *k)?,
3259 GgmlDType::IQ3_S => d.matvec_iq3s_gpu(wq, xv, *n, *k)?,
3260 GgmlDType::IQ3_XXS => d.matvec_iq3xxs_gpu(wq, xv, *n, *k)?,
3261 GgmlDType::IQ2_S => d.matvec_iq2s_gpu(wq, xv, *n, *k)?,
3262 GgmlDType::TQ2_0 => d.matvec_tq2_0_gpu(wq, xv, *n, *k)?,
3263 other => crate::bail!("VulkanQuant: no native matvec for {other:?}"),
3264 }
3265 };
3266 let mut dims = xs.dims().to_vec();
3267 let last = dims.len() - 1;
3268 dims[last] = *n;
3269 Ok(crate::tensor::from_storage(
3270 crate::Storage::Vulkan(y),
3271 dims,
3272 crate::op::BackpropOp::none(),
3273 false,
3274 ))
3275 } else if rows <= vulkan_prefill_gemm_max_rows(*dtype) {
3276 let m = rows;
3285 let xs = vulkan_act_offset0(xs)?;
3286 let d = match xs.device() {
3287 Device::Vulkan(d) => d,
3288 _ => crate::bail!("VulkanQuant input not on vulkan"),
3289 };
3290 let y = {
3291 let (store, _) = xs.storage_and_layout();
3292 let xv = match &*store {
3293 crate::Storage::Vulkan(v) => v,
3294 _ => crate::bail!("VulkanQuant expected vulkan storage"),
3295 };
3296 match dtype {
3297 GgmlDType::Q4_0 => d.matmul_q4_0_gpu(wq, xv, m, *n, *k)?,
3298 GgmlDType::Q8_0 => d.matmul_q8_gpu(wq, xv, m, *n, *k)?,
3299 GgmlDType::Q4K
3304 if *n == 2048
3305 && *k == 2048
3306 && std::env::var_os("VK_MMQ_Q4K").is_some() =>
3307 {
3308 let bank = qtensor.vulkan_moe_bank_split(d, 1, *n, *k)?;
3309 let (xq, xsq, xsum) = d.quantize_act_q8(xv, m, *k)?;
3310 d.mmq_q4k_gpu(&xq, &xsq, &xsum, bank.as_ref(), m, *n)?
3311 }
3312 GgmlDType::Q4K => d.matmul_q4k_gpu(wq, xv, m, *n, *k)?,
3313 GgmlDType::Q5K => d.matmul_q5k_gpu(wq, xv, m, *n, *k)?,
3314 GgmlDType::Q6K => d.matmul_q6k_gpu(wq, xv, m, *n, *k)?,
3315 other => crate::bail!("VulkanQuant: no native matmul for {other:?}"),
3316 }
3317 };
3318 let mut dims = xs.dims().to_vec();
3319 let last = dims.len() - 1;
3320 dims[last] = *n;
3321 Ok(crate::tensor::from_storage(
3322 crate::Storage::Vulkan(y),
3323 dims,
3324 crate::op::BackpropOp::none(),
3325 false,
3326 ))
3327 } else {
3328 let w = qtensor.dequantize(&xs.device())?;
3333 let w = match *xs.dims() {
3334 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3335 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3336 _ => w.t()?,
3337 };
3338 xs.matmul(&w)
3339 }
3340 }
3341 #[cfg(feature = "wgpu")]
3342 Self::WgpuQuant {
3343 qtensor,
3344 wq,
3345 dtype,
3346 n,
3347 k,
3348 } => {
3349 let vdev = qtensor.device();
3353 let xs_on_vdev = if xs.device().same_device(&vdev) {
3354 None
3355 } else {
3356 Some(xs.to_device(&vdev)?)
3357 };
3358 let xs = xs_on_vdev.as_ref().unwrap_or(xs);
3359 let rows: usize = xs.elem_count() / *k;
3360 if rows == 1 {
3361 let xs = xs.contiguous()?;
3364 let d = match xs.device() {
3365 Device::Wgpu(d) => d,
3366 _ => crate::bail!("WgpuQuant input not on wgpu"),
3367 };
3368 let y = {
3369 let (store, _) = xs.storage_and_layout();
3370 let xv = match &*store {
3371 crate::Storage::Wgpu(v) => v,
3372 _ => crate::bail!("WgpuQuant expected wgpu storage"),
3373 };
3374 match dtype {
3375 GgmlDType::Q4_0 => d.matvec_q4_0_gpu(wq, xv, *n, *k)?,
3376 GgmlDType::Q8_0 => d.matvec_q8_0_gpu(wq, xv, *n, *k)?,
3377 GgmlDType::Q4K => d.matvec_q4k_gpu(wq, xv, *n, *k)?,
3378 other => crate::bail!("WgpuQuant: no native matvec for {other:?}"),
3379 }
3380 };
3381 let mut dims = xs.dims().to_vec();
3382 let last = dims.len() - 1;
3383 dims[last] = *n;
3384 Ok(crate::tensor::from_storage(
3385 crate::Storage::Wgpu(y),
3386 dims,
3387 crate::op::BackpropOp::none(),
3388 false,
3389 ))
3390 } else {
3391 let w = qtensor.dequantize(&xs.device())?;
3393 let w = match *xs.dims() {
3394 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3395 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3396 _ => w.t()?,
3397 };
3398 xs.matmul(&w)
3399 }
3400 }
3401 Self::QTensor(t) => xs.apply_op1_no_bwd(t.as_ref()),
3402 Self::Tensor(w) => dense_matmul(xs, w),
3403 Self::TensorF16(w) => {
3404 let in_dtype = xs.dtype();
3405 dense_matmul(&xs.to_dtype(DType::F16)?, w)?.to_dtype(in_dtype)
3406 }
3407 }
3408 }
3409}
3410
3411#[cfg(test)]
3412mod tests {
3413 use super::GgmlDType;
3414
3415 #[test]
3416 fn block_align_divides_type_size() {
3417 let dtypes: Vec<GgmlDType> = (0..1024u32)
3420 .filter_map(|id| GgmlDType::from_u32(id).ok())
3421 .collect();
3422 for dtype in [
3423 GgmlDType::F32,
3424 GgmlDType::BF16,
3425 GgmlDType::I32,
3426 GgmlDType::NVFP4,
3427 ] {
3428 assert!(dtypes.contains(&dtype), "{dtype:?} has no GGML id");
3429 }
3430 for dtype in dtypes {
3431 assert_eq!(
3432 GgmlDType::from_u32(dtype.to_u32()).unwrap(),
3433 dtype,
3434 "{dtype:?} does not survive a to_u32/from_u32 round trip"
3435 );
3436 let (size, align) = (dtype.type_size(), dtype.block_align());
3437 assert!(align.is_power_of_two(), "{dtype:?}: align {align}");
3438 assert_eq!(
3439 size % align,
3440 0,
3441 "{dtype:?}: type_size {size} block_align {align}"
3442 );
3443 }
3444 }
3445}