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 GgmlDType::ROCMFP4 => cuda::load_quantized(d, as_t_slice::<BlockROCMFP4>(&data)),
255 GgmlDType::ROCMFP4_FAST => {
256 cuda::load_quantized(d, as_t_slice::<BlockROCMFP4Fast>(&data))
257 }
258 },
259 #[cfg(feature = "rocm")]
260 Device::Rocm(d) => Ok(Self::Rocm(dtype.from_data(Cow::Borrowed(data)), d.clone())),
261 #[cfg(feature = "vulkan")]
262 Device::Vulkan(d) => Ok(Self::Vulkan(
263 dtype.from_data(Cow::Borrowed(data)),
264 d.clone(),
265 )),
266 #[cfg(feature = "wgpu")]
267 Device::Wgpu(d) => Ok(Self::Wgpu(dtype.from_data(Cow::Borrowed(data)), d.clone())),
268 }
269 }
270
271 fn block_size(&self) -> usize {
272 match self {
273 QStorage::Cpu(storage) => storage.block_size(),
274 QStorage::Metal(storage) => storage.dtype().block_size(),
275 QStorage::Cuda(storage) => storage.dtype().block_size(),
276 #[cfg(feature = "rocm")]
277 QStorage::Rocm(storage, _) => storage.block_size(),
278 #[cfg(feature = "vulkan")]
279 QStorage::Vulkan(storage, _) => storage.block_size(),
280 #[cfg(feature = "wgpu")]
281 QStorage::Wgpu(storage, _) => storage.block_size(),
282 QStorage::Stream(bank) => bank.dtype().block_size(),
283 }
284 }
285
286 fn dtype(&self) -> GgmlDType {
287 match self {
288 QStorage::Cpu(storage) => storage.dtype(),
289 QStorage::Metal(storage) => storage.dtype(),
290 QStorage::Cuda(storage) => storage.dtype(),
291 #[cfg(feature = "rocm")]
292 QStorage::Rocm(storage, _) => storage.dtype(),
293 #[cfg(feature = "vulkan")]
294 QStorage::Vulkan(storage, _) => storage.dtype(),
295 #[cfg(feature = "wgpu")]
296 QStorage::Wgpu(storage, _) => storage.dtype(),
297 QStorage::Stream(bank) => bank.dtype(),
298 }
299 }
300
301 fn device(&self) -> Device {
302 match self {
303 QStorage::Cpu(_storage) => Device::Cpu,
304 QStorage::Metal(storage) => Device::Metal(storage.device().clone()),
305 QStorage::Cuda(storage) => Device::Cuda(storage.device().clone()),
306 #[cfg(feature = "rocm")]
307 QStorage::Rocm(_storage, device) => Device::Rocm(device.clone()),
308 #[cfg(feature = "vulkan")]
309 QStorage::Vulkan(_storage, device) => Device::Vulkan(device.clone()),
310 #[cfg(feature = "wgpu")]
311 QStorage::Wgpu(_storage, device) => Device::Wgpu(device.clone()),
312 QStorage::Stream(_) => Device::Cpu,
313 }
314 }
315
316 fn size_in_bytes(&self) -> usize {
317 match self {
318 QStorage::Cpu(storage) => storage.storage_size_in_bytes(),
319 QStorage::Metal(storage) => storage.storage_size_in_bytes(),
320 QStorage::Cuda(storage) => storage.storage_size_in_bytes(),
321 #[cfg(feature = "rocm")]
322 QStorage::Rocm(storage, _) => storage.storage_size_in_bytes(),
323 #[cfg(feature = "vulkan")]
324 QStorage::Vulkan(storage, _) => storage.storage_size_in_bytes(),
325 #[cfg(feature = "wgpu")]
326 QStorage::Wgpu(storage, _) => storage.storage_size_in_bytes(),
327 QStorage::Stream(bank) => bank.logical_bytes(),
328 }
329 }
330
331 fn quantize(&mut self, src: &Storage) -> Result<()> {
332 match (self, src) {
333 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
334 storage.from_float(src.as_slice::<f32>()?);
335 }
336 (QStorage::Metal(storage), Storage::Metal(src)) => storage.quantize(src)?,
337 (QStorage::Cuda(storage), Storage::Cuda(src)) => storage.quantize(src)?,
338 _ => crate::bail!("Invalid quantize storage locations do not match"),
339 }
340 Ok(())
341 }
342
343 fn quantize_imatrix(
344 &mut self,
345 src: &Storage,
346 imatrix_weights: &[f32],
347 n_per_row: usize,
348 ) -> Result<()> {
349 match (self, src) {
350 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
351 storage.from_float_imatrix(src.as_slice::<f32>()?, imatrix_weights, n_per_row);
352 }
353 (QStorage::Metal(storage), Storage::Metal(src)) => {
354 storage.quantize_imatrix(src, imatrix_weights, n_per_row)?
355 }
356 (QStorage::Cuda(storage), Storage::Cuda(src)) => {
357 storage.quantize_imatrix(src, imatrix_weights, n_per_row)?
358 }
359 _ => crate::bail!("Invalid quantize storage locations do not match"),
360 }
361 Ok(())
362 }
363
364 fn quantize_onto(&mut self, src: &Storage) -> Result<()> {
365 match (self, src) {
366 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
367 storage.from_float(src.as_slice::<f32>()?);
368 }
369 (QStorage::Metal(storage), Storage::Cpu(src)) => storage.quantize_onto(src)?,
370 (QStorage::Cuda(storage), Storage::Cpu(src)) => storage.quantize_onto(src)?,
371 _ => crate::bail!("Invalid quantize source storage locations: not on cpu"),
372 }
373 Ok(())
374 }
375
376 fn quantize_imatrix_onto(
377 &mut self,
378 src: &Storage,
379 imatrix_weights: &[f32],
380 n_per_row: usize,
381 ) -> Result<()> {
382 match (self, src) {
383 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
384 storage.from_float_imatrix(src.as_slice::<f32>()?, imatrix_weights, n_per_row);
385 }
386 (QStorage::Metal(storage), Storage::Cpu(src)) => {
387 storage.quantize_imatrix_onto(src, imatrix_weights, n_per_row)?
388 }
389 (QStorage::Cuda(storage), Storage::Cpu(src)) => {
390 storage.quantize_imatrix_onto(src, imatrix_weights, n_per_row)?
391 }
392 _ => crate::bail!("Invalid quantize storage locations do not match"),
393 }
394 Ok(())
395 }
396
397 fn dequantize(&self, elem_count: usize) -> Result<Storage> {
398 match self {
399 QStorage::Cpu(storage) => Ok(Storage::Cpu(storage.dequantize(elem_count)?)),
400 QStorage::Metal(storage) => Ok(Storage::Metal(storage.dequantize(elem_count)?)),
401 QStorage::Cuda(storage) => Ok(Storage::Cuda(storage.dequantize(elem_count)?)),
402 #[cfg(feature = "rocm")]
403 QStorage::Rocm(storage, device) => {
404 use crate::backend::BackendDevice;
406 let cpu = storage.dequantize(elem_count)?;
407 Ok(Storage::Rocm(device.storage_from_cpu_storage(&cpu)?))
408 }
409 #[cfg(feature = "vulkan")]
410 QStorage::Vulkan(storage, device) => {
411 let cpu = storage.dequantize(elem_count)?;
413 Ok(Storage::Vulkan(device.upload_f32(cpu.as_slice::<f32>()?)?))
414 }
415 #[cfg(feature = "wgpu")]
416 QStorage::Wgpu(storage, device) => {
417 let cpu = storage.dequantize(elem_count)?;
419 Ok(Storage::Wgpu(device.upload_f32(cpu.as_slice::<f32>()?)?))
420 }
421 QStorage::Stream(_) => {
422 crate::bail!("streaming expert bank has no whole-tensor dequantize; consume it via indexed_moe_forward")
423 }
424 }
425 }
426
427 fn data(&self) -> Result<Cow<'_, [u8]>> {
428 match self {
429 QStorage::Cpu(storage) => {
430 let data_ptr = storage.as_ptr();
431 let size_in_bytes = storage.storage_size_in_bytes();
432 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
433 Ok(Cow::from(data))
434 }
435 QStorage::Cuda(storage) => Ok(Cow::from(storage.data()?)),
436 QStorage::Metal(storage) => Ok(Cow::from(storage.data()?)),
437 #[cfg(feature = "rocm")]
438 QStorage::Rocm(storage, _) => {
439 let data_ptr = storage.as_ptr();
440 let size_in_bytes = storage.storage_size_in_bytes();
441 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
442 Ok(Cow::from(data))
443 }
444 #[cfg(feature = "vulkan")]
445 QStorage::Vulkan(storage, _) => {
446 let data_ptr = storage.as_ptr();
447 let size_in_bytes = storage.storage_size_in_bytes();
448 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
449 Ok(Cow::from(data))
450 }
451 #[cfg(feature = "wgpu")]
452 QStorage::Wgpu(storage, _) => {
453 let data_ptr = storage.as_ptr();
454 let size_in_bytes = storage.storage_size_in_bytes();
455 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
456 Ok(Cow::from(data))
457 }
458 QStorage::Stream(_) => {
459 crate::bail!(
460 "streaming expert bank is not resident; consume it via indexed_moe_forward"
461 )
462 }
463 }
464 }
465
466 pub fn device_ptr(&self) -> Result<*const u8> {
467 match self {
468 QStorage::Cuda(storage) => storage.device_ptr(),
469 #[cfg(feature = "rocm")]
470 QStorage::Rocm(..) => crate::bail!("not implemented"),
471 #[cfg(feature = "vulkan")]
472 QStorage::Vulkan(..) => crate::bail!("not implemented"),
473 #[cfg(feature = "wgpu")]
474 QStorage::Wgpu(..) => crate::bail!("not implemented"),
475 QStorage::Metal(_) | QStorage::Cpu(_) | QStorage::Stream(_) => {
476 crate::bail!("not implemented");
477 }
478 }
479 }
480
481 #[cfg(feature = "cuda")]
482 pub fn device_ptr_with_guard<'a>(
483 &'a self,
484 stream: &'a crate::cuda_backend::cudarc::driver::CudaStream,
485 ) -> Result<(
486 *const u8,
487 crate::cuda_backend::cudarc::driver::SyncOnDrop<'a>,
488 )> {
489 match self {
490 QStorage::Cuda(storage) => storage.device_ptr_with_guard(stream),
491 QStorage::Metal(_) | QStorage::Cpu(_) | QStorage::Stream(_) => {
492 crate::bail!("not implemented");
493 }
494 }
495 }
496}
497
498#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
499pub enum GgmlDType {
500 F32,
501 F16,
502 BF16,
503 I32,
506 Q4_0,
507 Q4_1,
508 Q5_0,
509 Q5_1,
510 Q8_0,
511 Q8_1,
512 Q2K,
513 Q3K,
514 Q4K,
515 Q5K,
516 Q6K,
517 Q8K,
518 #[allow(non_camel_case_types)]
519 IQ4_NL,
520 #[allow(non_camel_case_types)]
521 IQ4_XS,
522 MXFP4,
524 #[allow(non_camel_case_types)]
526 IQ2_XXS,
527 #[allow(non_camel_case_types)]
528 IQ2_XS,
529 #[allow(non_camel_case_types)]
530 IQ3_XXS,
531 #[allow(non_camel_case_types)]
532 IQ1_S,
533 #[allow(non_camel_case_types)]
534 IQ3_S,
535 #[allow(non_camel_case_types)]
536 IQ2_S,
537 #[allow(non_camel_case_types)]
538 IQ1_M,
539 TQ1_0,
540 TQ2_0,
541 NVFP4,
542 Q1_0,
543 #[allow(non_camel_case_types)]
545 ROCMFP4,
546 #[allow(non_camel_case_types)]
547 ROCMFP4_FAST,
548}
549
550use crate::for_each_quant;
562
563macro_rules! gen_from_u32 {
564 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
565 pub fn from_u32(u: u32) -> Result<Self> {
569 let dtype = match u {
570 0 => Self::F32,
571 1 => Self::F16,
572 30 => Self::BF16,
573 26 => Self::I32,
574 $( $id => Self::$v, )+
575 _ => crate::bail!("unknown dtype for tensor {u}"),
576 };
577 Ok(dtype)
578 }
579 };
580}
581
582macro_rules! gen_to_u32 {
583 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
584 pub fn to_u32(self) -> u32 {
588 match self {
589 Self::F32 => 0,
590 Self::F16 => 1,
591 Self::BF16 => 30,
592 Self::I32 => 26,
593 $( Self::$v => $id, )+
594 }
595 }
596 };
597}
598
599macro_rules! gen_cpu_zeros {
600 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
601 pub fn cpu_zeros(&self, elem_count: usize) -> Box<dyn QuantizedType> {
603 match self {
604 Self::F32 => Box::new(vec![f32::zeros(); elem_count]),
605 Self::F16 => Box::new(vec![f16::zeros(); elem_count]),
606 Self::BF16 => Box::new(vec![bf16::zeros(); elem_count]),
607 Self::I32 => Box::new(vec![0i32; elem_count]),
608 $( Self::$v => Box::new(vec![<$b>::zeros(); elem_count / <$b>::BLCK_SIZE]), )+
609 }
610 }
611 };
612}
613
614macro_rules! gen_from_data {
615 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
616 pub fn from_data(&self, data: Cow<'_, [u8]>) -> Box<dyn QuantizedType> {
617 match self {
618 Self::F32 => Box::new(as_t_slice::<f32>(&data).to_vec()),
619 Self::F16 => Box::new(as_t_slice::<f16>(&data).to_vec()),
620 Self::BF16 => Box::new(as_t_slice::<bf16>(&data).to_vec()),
621 Self::I32 => Box::new(as_t_slice::<i32>(&data).to_vec()),
622 $( Self::$v => Box::new(as_t_slice::<$b>(&data).to_vec()), )+
623 }
624 }
625 };
626}
627
628macro_rules! gen_type_size {
629 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
630 pub fn type_size(&self) -> usize {
632 use k_quants::*;
633 match self {
634 Self::F32 => 4,
635 Self::F16 | Self::BF16 => 2,
636 Self::I32 => 4,
637 $( Self::$v => std::mem::size_of::<$b>(), )+
638 }
639 }
640 };
641}
642
643macro_rules! gen_block_align {
644 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
645 pub const fn block_align(&self) -> usize {
650 use k_quants::*;
651 match self {
652 Self::F32 => std::mem::align_of::<f32>(),
653 Self::F16 => std::mem::align_of::<f16>(),
654 Self::BF16 => std::mem::align_of::<bf16>(),
655 Self::I32 => std::mem::align_of::<i32>(),
656 $( Self::$v => std::mem::align_of::<$b>(), )+
657 }
658 }
659 };
660}
661
662macro_rules! gen_from_mmap {
663 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
664 #[allow(clippy::wrong_self_convention)] pub(crate) fn from_mmap(
671 &self,
672 mmap: Arc<memmap2::Mmap>,
673 offset: usize,
674 n_blocks: usize,
675 ) -> Box<dyn QuantizedType> {
676 match self {
677 Self::F32 => Box::new(QMmap::<f32>::new(mmap, offset, n_blocks)),
678 Self::F16 => Box::new(QMmap::<f16>::new(mmap, offset, n_blocks)),
679 Self::BF16 => Box::new(QMmap::<bf16>::new(mmap, offset, n_blocks)),
680 Self::I32 => Box::new(QMmap::<i32>::new(mmap, offset, n_blocks)),
681 $( Self::$v => Box::new(QMmap::<$b>::new(mmap, offset, n_blocks)), )+
682 }
683 }
684 };
685}
686
687impl GgmlDType {
688 for_each_quant!(gen_from_u32);
689 for_each_quant!(gen_to_u32);
690 for_each_quant!(gen_cpu_zeros);
691 for_each_quant!(gen_from_data);
692 for_each_quant!(gen_from_mmap);
693 for_each_quant!(gen_type_size);
694 for_each_quant!(gen_block_align);
695
696 pub fn block_size(&self) -> usize {
698 match self {
699 Self::F32 => 1,
700 Self::F16 | Self::BF16 => 1,
701 Self::I32 => 1,
702 Self::Q4_0 => k_quants::QK4_0,
703 Self::Q4_1 => k_quants::QK4_1,
704 Self::Q5_0 => k_quants::QK5_0,
705 Self::Q5_1 => k_quants::QK5_1,
706 Self::Q8_0 => k_quants::QK8_0,
707 Self::Q8_1 => k_quants::QK8_1,
708 Self::IQ4_NL => k_quants::QK4_NL,
709 Self::MXFP4 => k_quants::QK_MXFP4,
710 Self::Q1_0 => iq_quants::QK1_0,
711 Self::NVFP4 => iq_quants::QK_NVFP4,
712 Self::ROCMFP4 | Self::ROCMFP4_FAST => iq_quants::QK_ROCMFP4,
713 Self::Q2K
714 | Self::Q3K
715 | Self::Q4K
716 | Self::Q5K
717 | Self::Q6K
718 | Self::Q8K
719 | Self::IQ4_XS
720 | Self::IQ2_XXS
721 | Self::IQ2_XS
722 | Self::IQ3_XXS
723 | Self::IQ1_S
724 | Self::IQ3_S
725 | Self::IQ2_S
726 | Self::IQ1_M
727 | Self::TQ1_0
728 | Self::TQ2_0 => k_quants::QK_K,
729 }
730 }
731}
732
733pub trait QuantizedType: Send + Sync {
735 fn dtype(&self) -> GgmlDType;
736 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()>;
737 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()>;
738 fn embedding(&self, ids: &[u32], rows: usize, hidden: usize) -> Result<CpuStorage>;
739 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage>;
740 fn storage_size_in_bytes(&self) -> usize;
741 fn as_ptr(&self) -> *const u8;
742 fn block_size(&self) -> usize;
743 #[allow(clippy::wrong_self_convention)]
744 fn from_float(&mut self, xs: &[f32]);
745 #[allow(clippy::wrong_self_convention)]
746 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize);
747 fn size(&self) -> usize;
748}
749
750fn embedding_rows<T: k_quants::GgmlType>(
752 blocks: &[T],
753 ids: &[u32],
754 rows: usize,
755 hidden: usize,
756) -> Result<CpuStorage> {
757 if !hidden.is_multiple_of(T::BLCK_SIZE) {
758 crate::bail!(
759 "quantized embedding hidden size {hidden} is not divisible by block size {}",
760 T::BLCK_SIZE
761 )
762 }
763 let row_blocks = hidden / T::BLCK_SIZE;
764 if blocks.len() != rows * row_blocks {
765 crate::bail!(
766 "quantized tensor has {} blocks, expected {}",
767 blocks.len(),
768 rows * row_blocks
769 )
770 }
771 let mut out = vec![0f32; ids.len() * hidden];
772 for (out_row, &row_id) in ids.iter().enumerate() {
773 let row = row_id as usize;
774 if row >= rows {
775 crate::bail!("embedding id {row} is out of range for {rows} rows")
776 }
777 let src = &blocks[row * row_blocks..(row + 1) * row_blocks];
778 let dst = &mut out[out_row * hidden..(out_row + 1) * hidden];
779 T::to_float(src, dst);
780 }
781 Ok(CpuStorage::F32(out))
782}
783
784impl<T: k_quants::GgmlType + Send + Sync> QuantizedType for Vec<T> {
785 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()> {
786 k_quants::matmul(mkn, lhs, self.as_slice(), dst)
787 }
788 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()> {
789 k_quants::matmul_f16(mkn, lhs, self.as_slice(), dst)
790 }
791
792 fn embedding(&self, ids: &[u32], rows: usize, hidden: usize) -> Result<CpuStorage> {
793 embedding_rows(self.as_slice(), ids, rows, hidden)
794 }
795
796 fn size(&self) -> usize {
797 self.len() * core::mem::size_of::<T>()
798 }
799
800 fn from_float(&mut self, xs: &[f32]) {
801 T::from_float(xs, self)
802 }
803
804 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize) {
805 T::from_float_imatrix(xs, self, imatrix_weights, n_per_row)
806 }
807
808 fn dtype(&self) -> GgmlDType {
809 T::DTYPE
810 }
811
812 fn block_size(&self) -> usize {
813 T::BLCK_SIZE
814 }
815
816 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage> {
817 let mut ys = vec![0.0f32; elem_count];
818 T::to_float(self.as_slice(), &mut ys);
819 Ok(CpuStorage::F32(ys))
820 }
821
822 fn storage_size_in_bytes(&self) -> usize {
823 self.len() * std::mem::size_of::<T>()
824 }
825
826 fn as_ptr(&self) -> *const u8 {
827 self.as_ptr() as *const u8
828 }
829}
830
831pub struct QMmap<T> {
844 mmap: Arc<memmap2::Mmap>,
845 offset: usize,
847 n_blocks: usize,
849 _t: std::marker::PhantomData<T>,
850}
851
852impl<T> QMmap<T> {
853 fn new(mmap: Arc<memmap2::Mmap>, offset: usize, n_blocks: usize) -> Self {
854 Self {
855 mmap,
856 offset,
857 n_blocks,
858 _t: std::marker::PhantomData,
859 }
860 }
861
862 #[inline]
867 fn as_slice(&self) -> &[T] {
868 let len = self.n_blocks * std::mem::size_of::<T>();
869 as_t_slice::<T>(&self.mmap[self.offset..self.offset + len])
870 }
871}
872
873impl<T: k_quants::GgmlType + Send + Sync> QuantizedType for QMmap<T> {
874 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()> {
875 k_quants::matmul(mkn, lhs, self.as_slice(), dst)
876 }
877
878 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()> {
879 k_quants::matmul_f16(mkn, lhs, self.as_slice(), dst)
880 }
881
882 fn embedding(&self, ids: &[u32], rows: usize, hidden: usize) -> Result<CpuStorage> {
883 embedding_rows(self.as_slice(), ids, rows, hidden)
884 }
885
886 fn size(&self) -> usize {
887 self.n_blocks * std::mem::size_of::<T>()
888 }
889
890 fn from_float(&mut self, _xs: &[f32]) {
891 panic!("QMmap is read-only: cannot quantize into a memory-mapped weight region")
892 }
893
894 fn from_float_imatrix(&mut self, _xs: &[f32], _imatrix_weights: &[f32], _n_per_row: usize) {
895 panic!("QMmap is read-only: cannot quantize into a memory-mapped weight region")
896 }
897
898 fn dtype(&self) -> GgmlDType {
899 T::DTYPE
900 }
901
902 fn block_size(&self) -> usize {
903 T::BLCK_SIZE
904 }
905
906 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage> {
907 let mut ys = vec![0.0f32; elem_count];
908 T::to_float(self.as_slice(), &mut ys);
909 Ok(CpuStorage::F32(ys))
910 }
911
912 fn storage_size_in_bytes(&self) -> usize {
913 self.n_blocks * std::mem::size_of::<T>()
914 }
915
916 fn as_ptr(&self) -> *const u8 {
917 self.as_slice().as_ptr() as *const u8
918 }
919}
920
921impl std::fmt::Debug for QTensor {
922 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
923 write!(f, "QTensor[{:?}; {:?}]", self.shape, self.dtype())
924 }
925}
926
927fn check_shape(shape: &Shape, block_size: usize) -> Result<()> {
928 let dims = shape.dims();
929 if dims.is_empty() {
930 crate::bail!("scalar tensor cannot be quantized {shape:?}")
931 }
932 if !dims[dims.len() - 1].is_multiple_of(block_size) {
933 crate::bail!(
934 "quantized tensor must have their last dim divisible by block size {shape:?} {}",
935 block_size
936 )
937 }
938 Ok(())
939}
940
941impl QTensor {
942 fn make(storage: QStorage, shape: Shape) -> Self {
945 Self {
946 storage,
947 shape,
948 banks: ResidentBanks::default(),
949 repacked_qs: repack::PackedCache::new(),
950 }
951 }
952
953 pub fn new<S: Into<Shape>>(storage: QStorage, shape: S) -> Result<Self> {
954 let shape = shape.into();
955 check_shape(&shape, storage.block_size())?;
956 Ok(Self::make(storage, shape))
957 }
958
959 pub fn quantize(src: &Tensor, dtype: GgmlDType) -> Result<Self> {
960 let shape = src.shape();
961 let block_size = dtype.block_size();
962 check_shape(shape, block_size)?;
963 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
964 let elem_count = shape.elem_count();
965 if !elem_count.is_multiple_of(block_size) {
966 crate::bail!(
967 "tensor size ({shape:?}) is not divisible by block size {}",
968 block_size
969 )
970 }
971 let mut storage = src.device().qzeros(elem_count, dtype)?;
972 storage.quantize(&src.storage())?;
973 Ok(Self::make(storage, shape.clone()))
974 }
975
976 pub fn quantize_imatrix(
977 src: &Tensor,
978 imatrix_weights: &[f32],
979 dtype: GgmlDType,
980 ) -> Result<Self> {
981 let n_per_row = src.dim(D::Minus1)?;
984 if imatrix_weights.len() != n_per_row {
985 crate::bail!(
986 "imatrix weights must have the same length {} as the last dim of src {}",
987 imatrix_weights.len(),
988 src.dim(D::Minus1)?
989 );
990 }
991
992 let shape = src.shape();
993 let block_size = dtype.block_size();
994 check_shape(shape, block_size)?;
995 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
996 let elem_count = shape.elem_count();
997 if !elem_count.is_multiple_of(block_size) {
998 crate::bail!(
999 "tensor size ({shape:?}) is not divisible by block size {}",
1000 block_size
1001 );
1002 }
1003 let mut storage = src.device().qzeros(elem_count, dtype)?;
1004 storage.quantize_imatrix(&src.storage(), imatrix_weights, n_per_row)?;
1005 Ok(Self::make(storage, shape.clone()))
1006 }
1007
1008 pub fn quantize_imatrix_onto(
1010 src: &Tensor,
1011 imatrix_weights: &[f32],
1012 dtype: GgmlDType,
1013 dev: &Device,
1014 ) -> Result<Self> {
1015 if !src.device().is_cpu() {
1016 crate::bail!(
1017 "`quantize_onto` expects a `src` to be on the cpu, got {:?}.",
1018 src.device()
1019 )
1020 }
1021 let n_per_row = src.dim(D::Minus1)?;
1024 if imatrix_weights.len() != n_per_row {
1025 crate::bail!(
1026 "imatrix weights must have the same length {} as the last dim of src {}",
1027 imatrix_weights.len(),
1028 src.dim(D::Minus1)?
1029 );
1030 }
1031 let shape = src.shape();
1032 let block_size = dtype.block_size();
1033 check_shape(shape, block_size)?;
1034 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
1035 let elem_count = shape.elem_count();
1036 if !elem_count.is_multiple_of(block_size) {
1037 crate::bail!(
1038 "tensor size ({shape:?}) is not divisible by block size {}",
1039 block_size
1040 )
1041 }
1042 let mut storage = dev.qzeros(elem_count, dtype)?;
1044 storage.quantize_imatrix_onto(&src.storage(), imatrix_weights, n_per_row)?;
1045 Ok(Self::make(storage, shape.clone()))
1046 }
1047
1048 pub fn quantize_onto(src: &Tensor, dtype: GgmlDType, dev: &Device) -> Result<Self> {
1050 if !src.device().is_cpu() {
1051 crate::bail!(
1052 "`quantize_onto` expects a `src` to be on the cpu, got {:?}.",
1053 src.device()
1054 )
1055 }
1056 let shape = src.shape();
1057 let block_size = dtype.block_size();
1058 check_shape(shape, block_size)?;
1059 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
1060 let elem_count = shape.elem_count();
1061 if !elem_count.is_multiple_of(block_size) {
1062 crate::bail!(
1063 "tensor size ({shape:?}) is not divisible by block size {}",
1064 block_size
1065 )
1066 }
1067 let mut storage = dev.qzeros(elem_count, dtype)?;
1069 storage.quantize_onto(&src.storage())?;
1070 Ok(Self::make(storage, shape.clone()))
1071 }
1072
1073 pub fn dtype(&self) -> GgmlDType {
1074 self.storage.dtype()
1075 }
1076
1077 pub fn device(&self) -> Device {
1078 self.storage.device()
1079 }
1080
1081 pub fn rank(&self) -> usize {
1082 self.shape.rank()
1083 }
1084
1085 pub fn shape(&self) -> &Shape {
1086 &self.shape
1087 }
1088
1089 pub fn dequantize(&self, device: &Device) -> Result<Tensor> {
1090 let storage = self.storage.dequantize(self.shape.elem_count())?;
1091 let none = crate::op::BackpropOp::none();
1092 crate::tensor::from_storage(storage, self.shape.clone(), none, false).to_device(device)
1093 }
1094
1095 pub fn dequantize_f16(&self, device: &Device) -> Result<Tensor> {
1096 match &self.storage {
1099 QStorage::Cuda(s) => {
1100 let s = s.dequantize_f16(self.shape.elem_count())?;
1101 let none = crate::op::BackpropOp::none();
1102 crate::tensor::from_storage(Storage::Cuda(s), self.shape.clone(), none, false)
1103 .to_device(device)
1104 }
1105 _ => {
1106 let s = self.dequantize(device)?.to_dtype(crate::DType::F16)?;
1107 Ok(s)
1108 }
1109 }
1110 }
1111
1112 pub fn embedding(&self, ids: &Tensor) -> Result<Tensor> {
1113 let (rows, hidden) = self.shape.dims2()?;
1114 if !hidden.is_multiple_of(self.dtype().block_size()) {
1115 crate::bail!(
1116 "quantized embedding hidden size {hidden} is not divisible by block size {}",
1117 self.dtype().block_size()
1118 )
1119 }
1120 let mut out_shape = ids.dims().to_vec();
1121 out_shape.push(hidden);
1122 let device = self.device();
1123 let ids = ids
1124 .to_device(&device)?
1125 .to_dtype(DType::U32)?
1126 .flatten_all()?
1127 .contiguous()?;
1128 let storage = match &self.storage {
1129 QStorage::Cpu(storage) => {
1130 let ids = ids.to_vec1::<u32>()?;
1131 Storage::Cpu(storage.embedding(&ids, rows, hidden)?)
1132 }
1133 QStorage::Metal(storage) => match &*ids.storage() {
1134 Storage::Metal(ids_storage) => {
1135 Storage::Metal(storage.embedding(rows, hidden, ids_storage, ids.layout())?)
1136 }
1137 _ => unreachable!("ids were moved to the QTensor device"),
1138 },
1139 QStorage::Cuda(storage) => match &*ids.storage() {
1140 Storage::Cuda(ids_storage) => {
1141 Storage::Cuda(storage.embedding(rows, hidden, ids_storage, ids.layout())?)
1142 }
1143 _ => unreachable!("ids were moved to the QTensor device"),
1144 },
1145 #[cfg(feature = "rocm")]
1146 QStorage::Rocm(..) => return self.embedding_dequantized(&ids, out_shape),
1147 #[cfg(feature = "vulkan")]
1148 QStorage::Vulkan(..) => return self.embedding_dequantized(&ids, out_shape),
1149 #[cfg(feature = "wgpu")]
1150 QStorage::Wgpu(..) => return self.embedding_dequantized(&ids, out_shape),
1151 QStorage::Stream(_) => {
1152 crate::bail!("streaming expert bank is not resident; it has no embedding rows")
1153 }
1154 };
1155 let none = crate::op::BackpropOp::none();
1156 Ok(crate::tensor::from_storage(storage, out_shape, none, false))
1157 }
1158
1159 #[cfg(any(feature = "rocm", feature = "vulkan", feature = "wgpu"))]
1161 fn embedding_dequantized(&self, ids: &Tensor, out_shape: Vec<usize>) -> Result<Tensor> {
1162 self.dequantize(ids.device())?
1163 .index_select(ids, 0)?
1164 .reshape(out_shape)
1165 }
1166
1167 pub fn storage_size_in_bytes(&self) -> usize {
1168 self.storage.size_in_bytes()
1169 }
1170
1171 pub fn data(&self) -> Result<Cow<'_, [u8]>> {
1172 self.storage.data()
1173 }
1174
1175 #[cfg(feature = "rocm")]
1179 fn rocm_moe_bank(&self, dev: &crate::RocmDevice) -> Result<std::sync::Arc<crate::RocmStorage>> {
1180 use crate::backend::BackendDevice;
1181 let bank = self.data()?;
1182 cache_or_upload(&self.banks.rocm, bank.as_ref(), |b| {
1183 dev.storage_from_slice(b)
1184 })
1185 }
1186
1187 #[cfg(feature = "vulkan")]
1191 fn vulkan_moe_bank(
1192 &self,
1193 dev: &crate::VulkanDevice,
1194 e_cnt: usize,
1195 n: usize,
1196 k: usize,
1197 ) -> Result<std::sync::Arc<crate::VulkanStorage>> {
1198 let bank = self.data()?;
1199 let dt = self.storage.dtype();
1200 cache_or_upload(&self.banks.vulkan, bank.as_ref(), |b| match dt {
1201 GgmlDType::Q8_0 => dev.quantize_q8_blocks(b, e_cnt * n, k),
1202 GgmlDType::Q6K => dev.quantize_q6k(b, e_cnt * n, k),
1203 _ => dev.upload_qweight(b),
1204 })
1205 }
1206
1207 #[cfg(feature = "vulkan")]
1211 fn vulkan_moe_bank_split(
1212 &self,
1213 dev: &crate::VulkanDevice,
1214 e_cnt: usize,
1215 n: usize,
1216 k: usize,
1217 ) -> Result<std::sync::Arc<crate::vulkan::MoeBankSplit>> {
1218 let bank = self.data()?;
1219 let dt = self.storage.dtype();
1220 cache_or_upload(&self.banks.vulkan_split, bank.as_ref(), |b| match dt {
1221 GgmlDType::Q4K => dev.quantize_q4k_split(b, e_cnt * n, k),
1222 GgmlDType::Q6K => dev.quantize_q6k_split(b, e_cnt * n, k),
1223 _ => crate::bail!("vulkan_moe_bank_split: unsupported dtype {dt:?}"),
1224 })
1225 }
1226
1227 #[cfg(feature = "wgpu")]
1230 fn wgpu_moe_bank(&self, dev: &crate::WgpuDevice) -> Result<std::sync::Arc<crate::WgpuStorage>> {
1231 let bank = self.data()?;
1232 cache_or_upload(&self.banks.wgpu, bank.as_ref(), |b| dev.upload_qweight(b))
1233 }
1234
1235 pub fn indexed_moe_forward(&self, x: &Tensor, ids: &Tensor) -> Result<Tensor> {
1236 let ids = &ids.contiguous()?;
1239 match &self.storage {
1240 QStorage::Cuda(s) if cuda::QCudaStorage::supports_indexed_moe(s.dtype()) => {
1246 let out_dtype = x.dtype();
1251 let x = x.to_dtype(crate::DType::F32)?.contiguous()?;
1252 let (x_guard, x_l) = x.storage_and_layout();
1255 let (ids_guard, ids_l) = ids.storage_and_layout();
1256 match (&*x_guard, &*ids_guard) {
1257 (Storage::Cuda(x_storage), Storage::Cuda(ids_storage)) => {
1258 let (storage, out_shape) = s.indexed_moe_forward(
1259 self.shape(),
1260 x_storage,
1261 x_l,
1262 ids_storage,
1263 ids_l,
1264 )?;
1265 crate::tensor::from_storage(
1266 Storage::Cuda(storage),
1267 out_shape,
1268 crate::op::BackpropOp::none(),
1269 false,
1270 )
1271 .to_dtype(out_dtype)
1272 }
1273 _ => {
1274 panic!("Non-cuda indexed_moe_forward is not implemented!");
1275 }
1276 }
1277 }
1278 #[cfg(feature = "cuda")]
1284 QStorage::Cuda(s) if cuda::QCudaStorage::supports_iquant_moe(s.dtype()) => {
1285 let out_dtype = x.dtype();
1286 let (_e_cnt, n, k) = self.shape().dims3()?;
1287 let (t, topk) = ids.dims2()?;
1288 let nrows = t * topk;
1289
1290 if t > 1 {
1296 let x_f32 = x.to_dtype(crate::DType::F32)?.contiguous()?;
1297 let ids_u32 = ids.to_dtype(crate::DType::U32)?.contiguous()?;
1298 let (xs, _) = x_f32.storage_and_layout();
1299 let xc = match &*xs {
1300 Storage::Cuda(c) => c,
1301 _ => crate::bail!("cuda i-quant MoE: x not on cuda after contiguous()"),
1302 };
1303 let (ids_s, _) = ids_u32.storage_and_layout();
1304 let idc = match &*ids_s {
1305 Storage::Cuda(c) => c,
1306 _ => crate::bail!("cuda i-quant MoE: ids not on cuda"),
1307 };
1308 if let Some((st, sh)) = s.moe_iquant_qmmq(
1309 self.shape(),
1310 xc.as_cuda_slice::<f32>()?,
1311 x.shape(),
1312 &idc.as_cuda_slice::<u32>()?.slice(0..),
1313 ids.shape(),
1314 )? {
1315 return crate::tensor::from_storage(
1316 Storage::Cuda(st),
1317 sh,
1318 crate::op::BackpropOp::none(),
1319 false,
1320 )
1321 .to_dtype(out_dtype);
1322 }
1323 }
1324
1325 let sdim = x.dim(1)?; let x_exp = if sdim == topk {
1330 x.clone()
1331 } else {
1332 x.broadcast_as((t, topk, k))?
1333 };
1334 let x_flat = x_exp
1335 .reshape((nrows, k))?
1336 .to_dtype(crate::DType::F32)?
1337 .contiguous()?;
1338 let ids_flat = ids
1339 .reshape((nrows,))?
1340 .to_dtype(crate::DType::U32)?
1341 .contiguous()?;
1342 let (xstore, _) = x_flat.storage_and_layout();
1343 let xc = match &*xstore {
1344 Storage::Cuda(c) => c,
1345 _ => crate::bail!("cuda i-quant MoE: x not on cuda after contiguous()"),
1346 };
1347 let (idstore, _) = ids_flat.storage_and_layout();
1348 let idc = match &*idstore {
1349 Storage::Cuda(c) => c,
1350 _ => crate::bail!("cuda i-quant MoE: ids not on cuda"),
1351 };
1352 let y = s.moe_iquant_dp4a(
1353 &xc.as_cuda_slice::<f32>()?.slice(0..),
1354 &idc.as_cuda_slice::<u32>()?.slice(0..),
1355 nrows,
1356 n,
1357 k,
1358 )?;
1359 let out = crate::tensor::from_storage(
1360 Storage::Cuda(y),
1361 (nrows, n),
1362 crate::op::BackpropOp::none(),
1363 false,
1364 );
1365 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1366 }
1367 #[cfg(feature = "vulkan")]
1373 QStorage::Vulkan(_, vk_dev) if vk_moe_kernel(self.storage.dtype()).is_some() => {
1374 let out_dtype = x.dtype();
1375 let (e_cnt, n, k) = self.shape().dims3()?;
1376 let (t, topk) = ids.dims2()?;
1377 let s = x.dim(1)?; let x_exp = if s == topk {
1379 x.clone()
1380 } else {
1381 x.broadcast_as((t, topk, k))?
1382 };
1383 let nrows = t * topk;
1385 let x_flat = x_exp
1386 .reshape((nrows, k))?
1387 .to_dtype(crate::DType::F32)?
1388 .contiguous()?;
1389 let dt = self.storage.dtype();
1396 let ids_u32 = ids
1397 .reshape((nrows,))?
1398 .to_dtype(crate::DType::U32)?
1399 .contiguous()?;
1400 let y = {
1401 let (store, _) = x_flat.storage_and_layout();
1402 let xv = match &*store {
1403 Storage::Vulkan(v) => v,
1404 _ => crate::bail!("vulkan MoE: x not on vulkan after contiguous()"),
1405 };
1406 let (ids_store, _) = ids_u32.storage_and_layout();
1407 let ids_v = match &*ids_store {
1408 Storage::Vulkan(v) => v,
1409 _ => crate::bail!("vulkan MoE: ids not on vulkan after contiguous()"),
1410 };
1411 if t > 1
1420 && dt == GgmlDType::Q4K
1421 && vk_dev.has_int_dot8()
1422 && std::env::var_os("VK_MOE_PREFILL_GEMM_OFF").is_none()
1423 {
1424 let bank = self.vulkan_moe_bank_split(vk_dev, e_cnt, n, k)?;
1425 let (xq, xsq, xsum) = vk_dev.quantize_act_q8(xv, nrows, k)?;
1429 vk_dev.mmq_q4k_id_gpu(
1432 bank.as_ref(),
1433 &xq,
1434 &xsq,
1435 &xsum,
1436 ids_v,
1437 nrows,
1438 e_cnt,
1439 t,
1440 n,
1441 k,
1442 )?
1443 } else {
1444 match vk_moe_blk_dp4a_kernel(dt, n, k).filter(|_| vk_dev.has_int_dot8()) {
1450 Some((blk, with_xsum)) => {
1451 let bank = self.vulkan_moe_bank_split(vk_dev, e_cnt, n, k)?;
1452 vk_dev.moe_matvec_blk_dp4a_gpu(
1453 blk,
1454 with_xsum,
1455 bank.as_ref(),
1456 xv,
1457 ids_v,
1458 nrows,
1459 n,
1460 k,
1461 )?
1462 }
1463 None => match vk_moe_blk_kernel(dt, n, k) {
1464 Some(blk) => {
1465 let bank = self.vulkan_moe_bank_split(vk_dev, e_cnt, n, k)?;
1466 vk_dev.moe_matvec_blk_gpu(
1467 blk,
1468 bank.as_ref(),
1469 xv,
1470 ids_v,
1471 nrows,
1472 n,
1473 k,
1474 )?
1475 }
1476 None => {
1477 let kernel = vk_moe_kernel(dt).unwrap();
1479 let wbank = self.vulkan_moe_bank(vk_dev, e_cnt, n, k)?;
1480 vk_dev.moe_matvec_gpu(
1481 kernel,
1482 wbank.as_ref(),
1483 xv,
1484 ids_v,
1485 nrows,
1486 n,
1487 k,
1488 )?
1489 }
1490 },
1491 }
1492 }
1493 };
1494 let out = crate::tensor::from_storage(
1495 Storage::Vulkan(y),
1496 (nrows, n),
1497 crate::op::BackpropOp::none(),
1498 false,
1499 );
1500 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1501 }
1502 #[cfg(feature = "wgpu")]
1506 QStorage::Wgpu(_, wgpu_dev) if wgpu_moe_kernel(self.storage.dtype()).is_some() => {
1507 let out_dtype = x.dtype();
1508 let (e_cnt, n, k) = self.shape().dims3()?;
1509 let (t, topk) = ids.dims2()?;
1510 let s = x.dim(1)?; let x_exp = if s == topk {
1512 x.clone()
1513 } else {
1514 x.broadcast_as((t, topk, k))?
1515 };
1516 let nrows = t * topk;
1517 let x_flat = x_exp
1518 .reshape((nrows, k))?
1519 .to_dtype(crate::DType::F32)?
1520 .contiguous()?;
1521 let ids_vec = ids
1522 .reshape((nrows,))?
1523 .to_dtype(crate::DType::U32)?
1524 .to_vec1::<u32>()?;
1525 if let Some(&bad) = ids_vec.iter().find(|&&e| e as usize >= e_cnt) {
1526 crate::bail!("indexed_moe_forward: expert id {bad} >= num_experts {e_cnt}");
1527 }
1528 let kernel = wgpu_moe_kernel(self.storage.dtype()).unwrap();
1530 let wbank = self.wgpu_moe_bank(wgpu_dev)?;
1532 let ids_buf = wgpu_dev.upload_ids(&ids_vec)?;
1533 let y = {
1534 let (store, _) = x_flat.storage_and_layout();
1535 let xv = match &*store {
1536 Storage::Wgpu(v) => v,
1537 _ => crate::bail!("wgpu MoE: x not on wgpu after contiguous()"),
1538 };
1539 wgpu_dev.moe_matvec_gpu(kernel, wbank.as_ref(), xv, &ids_buf, nrows, n, k)?
1540 };
1541 let out = crate::tensor::from_storage(
1542 Storage::Wgpu(y),
1543 (nrows, n),
1544 crate::op::BackpropOp::none(),
1545 false,
1546 );
1547 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1548 }
1549 #[cfg(feature = "rocm")]
1554 QStorage::Rocm(_, rocm_dev)
1555 if crate::RocmQuantType::from_ggml(self.storage.dtype()).is_some() =>
1556 {
1557 let qt = crate::RocmQuantType::from_ggml(self.storage.dtype()).unwrap();
1558 let out_dtype = x.dtype();
1559 let (_e_cnt, n, k) = self.shape().dims3()?;
1562 let (t, topk) = ids.dims2()?;
1563 let s = x.dim(1)?; let x_exp = if s == topk {
1565 x.clone()
1566 } else {
1567 x.broadcast_as((t, topk, k))?
1568 };
1569 let nrows = t * topk;
1570 let use_qmmq = t > 1 && qt.qmmq_capable();
1576 let x_flat = match x_exp.dtype() {
1577 DType::F16 | DType::F32 if use_qmmq => {
1581 x_exp.reshape((nrows, k))?.contiguous()?
1582 }
1583 _ if use_qmmq => x_exp
1584 .reshape((nrows, k))?
1585 .to_dtype(DType::F16)?
1586 .contiguous()?,
1587 DType::BF16 | DType::F16 => x_exp.reshape((nrows, k))?.contiguous()?,
1588 DType::F32 if qt.dp4a_active() => x_exp.reshape((nrows, k))?.contiguous()?,
1592 _ => x_exp
1593 .reshape((nrows, k))?
1594 .to_dtype(DType::F16)?
1595 .contiguous()?,
1596 };
1597 let wbank = self.rocm_moe_bank(rocm_dev)?;
1598 let ids_u32 = ids
1605 .reshape((nrows,))?
1606 .to_dtype(crate::DType::U32)?
1607 .contiguous()?;
1608 let (store, _) = x_flat.storage_and_layout();
1609 let xr = match &*store {
1610 crate::Storage::Rocm(r) => r,
1611 _ => crate::bail!("rocm MoE: x not on rocm after contiguous()"),
1612 };
1613 let (idstore, _) = ids_u32.storage_and_layout();
1614 let idr = match &*idstore {
1615 crate::Storage::Rocm(r) => r,
1616 _ => crate::bail!("rocm MoE: ids not on rocm"),
1617 };
1618 let y = if use_qmmq {
1619 rocm_dev.moe_qmmq_quant(qt, wbank.as_ref(), xr, idr, nrows, n, k)?
1620 } else {
1621 rocm_dev.moe_matvec_quant(qt, wbank.as_ref(), xr, idr, nrows, n, k)?
1622 };
1623 let out = crate::tensor::from_storage(
1624 crate::Storage::Rocm(y),
1625 (nrows, n),
1626 crate::op::BackpropOp::none(),
1627 false,
1628 );
1629 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1630 }
1631 #[cfg(feature = "metal")]
1639 QStorage::Metal(s)
1640 if matches!(&*x.storage(), Storage::Metal(_))
1641 && matches!(&*ids.storage(), Storage::Metal(_)) =>
1642 {
1643 let out_dtype = x.dtype();
1644 let x = x.contiguous()?;
1645 let (xs_guard, x_l) = x.storage_and_layout();
1646 let (ids_guard, ids_l) = ids.storage_and_layout();
1647 let (Storage::Metal(x_storage), Storage::Metal(ids_storage)) =
1648 (&*xs_guard, &*ids_guard)
1649 else {
1650 unreachable!("metal MoE arm is guarded on Metal x/ids storage");
1651 };
1652 let (storage, out_shape) =
1653 s.indexed_moe_forward(self.shape(), x_storage, x_l, ids_storage, ids_l)?;
1654 let out = crate::tensor::from_storage(
1655 Storage::Metal(storage),
1656 out_shape,
1657 crate::op::BackpropOp::none(),
1658 false,
1659 );
1660 out.to_dtype(out_dtype)
1661 }
1662 QStorage::Stream(bank) => {
1666 let (e_cnt, n, k) = self.shape().dims3()?;
1667 let dtype = bank.dtype();
1668 moe_grouped_per_expert(x, ids, n, k, |eid, device| {
1669 if eid as usize >= e_cnt {
1670 crate::bail!("indexed_moe_forward: expert id {eid} >= num_experts {e_cnt}");
1671 }
1672 let bytes = bank.fetch(eid)?;
1673 QStorage::from_data(std::borrow::Cow::Borrowed(&bytes), device, dtype)
1674 })
1675 }
1676 _ => {
1677 let (e_cnt, n, k) = self.shape().dims3()?;
1683 let dtype = self.storage.dtype();
1684 let all_bytes = self.data()?;
1685 let expert_bytes = all_bytes.len() / e_cnt;
1686 moe_grouped_per_expert(x, ids, n, k, |eid, device| {
1687 let off = eid as usize * expert_bytes;
1688 QStorage::from_data(
1689 std::borrow::Cow::Borrowed(&all_bytes[off..off + expert_bytes]),
1690 device,
1691 dtype,
1692 )
1693 })
1694 }
1695 }
1696 }
1697
1698 pub fn device_ptr(&self) -> Result<*const u8> {
1699 match &self.storage {
1700 QStorage::Cuda(storage) => storage.device_ptr(),
1701 #[cfg(feature = "rocm")]
1702 QStorage::Rocm(..) => crate::bail!("not implemented"),
1703 #[cfg(feature = "vulkan")]
1704 QStorage::Vulkan(..) => crate::bail!("not implemented"),
1705 #[cfg(feature = "wgpu")]
1706 QStorage::Wgpu(..) => crate::bail!("not implemented"),
1707 QStorage::Metal(_) | QStorage::Cpu(_) | QStorage::Stream(_) => {
1708 crate::bail!("not implemented");
1709 }
1710 }
1711 }
1712
1713 #[cfg(feature = "cuda")]
1714 pub fn device_ptr_with_guard<'a>(
1715 &'a self,
1716 stream: &'a crate::cuda_backend::cudarc::driver::CudaStream,
1717 ) -> Result<(
1718 *const u8,
1719 crate::cuda_backend::cudarc::driver::SyncOnDrop<'a>,
1720 )> {
1721 self.storage.device_ptr_with_guard(stream)
1722 }
1723}
1724
1725#[derive(Clone, Debug)]
1726pub enum QMatMul {
1727 QTensor(std::sync::Arc<QTensor>),
1728 Tensor(Tensor),
1729 TensorF16(Tensor),
1730 #[cfg(feature = "vulkan")]
1736 VulkanQuant {
1737 qtensor: std::sync::Arc<QTensor>,
1738 wq: std::sync::Arc<crate::VulkanStorage>,
1739 dtype: GgmlDType,
1740 n: usize,
1741 k: usize,
1742 },
1743 #[cfg(feature = "wgpu")]
1747 WgpuQuant {
1748 qtensor: std::sync::Arc<QTensor>,
1749 wq: std::sync::Arc<crate::WgpuStorage>,
1750 dtype: GgmlDType,
1751 n: usize,
1752 k: usize,
1753 },
1754 #[cfg(feature = "rocm")]
1760 RocmQuant {
1761 qtensor: std::sync::Arc<QTensor>,
1762 wq: std::sync::Arc<crate::RocmStorage>,
1763 dtype: GgmlDType,
1764 n: usize,
1765 k: usize,
1766 },
1767}
1768
1769#[cfg(any(feature = "rocm", feature = "vulkan", feature = "wgpu"))]
1779fn cache_or_upload<S>(
1780 slot: &std::sync::OnceLock<std::sync::Arc<S>>,
1781 bank: &[u8],
1782 upload: impl FnOnce(&[u8]) -> Result<S>,
1783) -> Result<std::sync::Arc<S>> {
1784 if let Some(w) = slot.get() {
1785 return Ok(w.clone());
1786 }
1787 let w = std::sync::Arc::new(upload(bank)?);
1788 Ok(slot.get_or_init(|| w).clone())
1791}
1792
1793#[cfg(feature = "vulkan")]
1799fn vk_moe_kernel(dt: GgmlDType) -> Option<&'static str> {
1800 match dt {
1801 GgmlDType::Q4_0 => Some("moe_matvec_q4_0"),
1802 GgmlDType::Q8_0 => Some("moe_matvec_q8_0"),
1803 GgmlDType::Q4K => Some("moe_matvec_q4k"),
1804 GgmlDType::Q6K => Some("moe_matvec_q6k"),
1805 _ => None,
1806 }
1807}
1808
1809#[cfg(feature = "vulkan")]
1815fn vk_moe_blk_kernel(dt: GgmlDType, n: usize, k: usize) -> Option<&'static str> {
1816 if std::env::var_os("VK_MOE_PACKED").is_some() {
1819 return None;
1820 }
1821 match (dt, n, k) {
1822 (GgmlDType::Q4K, 768, 2048) => Some("moe_matvec_q4k_blk_gu"),
1823 (GgmlDType::Q4K, 2048, 768) => Some("moe_matvec_q4k_blk_dn"),
1824 (GgmlDType::Q6K, 2048, 768) => Some("moe_matvec_q6k_blk_dn"),
1825 _ => None,
1826 }
1827}
1828
1829#[cfg(feature = "vulkan")]
1838fn vk_moe_blk_dp4a_kernel(dt: GgmlDType, n: usize, k: usize) -> Option<(&'static str, bool)> {
1839 if std::env::var_os("VK_MOE_PACKED").is_some() || std::env::var_os("VK_MOE_DP4A_OFF").is_some()
1840 {
1841 return None;
1842 }
1843 match (dt, n, k) {
1844 (GgmlDType::Q4K, 768, 2048) => Some(("moe_matvec_q4k_dp4a_blk_gu", true)),
1845 (GgmlDType::Q4K, 2048, 768) => Some(("moe_matvec_q4k_dp4a_blk_dn", true)),
1846 (GgmlDType::Q6K, 2048, 768) => Some(("moe_matvec_q6k_dp4a_blk_dn", false)),
1847 _ => None,
1848 }
1849}
1850
1851#[cfg(feature = "wgpu")]
1853fn wgpu_moe_kernel(dt: GgmlDType) -> Option<&'static str> {
1854 match dt {
1855 GgmlDType::Q4_0 => Some("moe_matvec_q4_0"),
1856 GgmlDType::Q8_0 => Some("moe_matvec_q8_0"),
1857 GgmlDType::Q4K => Some("moe_matvec_q4k"),
1858 _ => None,
1859 }
1860}
1861
1862fn moe_grouped_per_expert(
1867 x: &Tensor,
1868 ids: &Tensor,
1869 n: usize,
1870 k: usize,
1871 mut make_storage: impl FnMut(u32, &Device) -> Result<QStorage>,
1872) -> Result<Tensor> {
1873 use crate::Module; use std::collections::HashMap;
1875 use std::sync::Arc;
1876 let device = x.device();
1877 let out_dtype = x.dtype();
1878 let (t, topk) = ids.dims2()?;
1879 let s = x.dim(1)?; let x_exp = if s == topk {
1881 x.clone()
1882 } else {
1883 x.broadcast_as((t, topk, k))?
1884 };
1885 let x_flat = x_exp
1886 .reshape((t * topk, k))?
1887 .to_dtype(DType::F32)?
1888 .contiguous()?;
1889 let ids_flat = ids.reshape((t * topk,))?.to_dtype(DType::U32)?;
1890 let ids_vec = ids_flat.to_vec1::<u32>()?;
1891 let mut groups: HashMap<u32, Vec<u32>> = HashMap::new();
1892 for (slot, eid) in ids_vec.iter().enumerate() {
1893 groups.entry(*eid).or_default().push(slot as u32);
1894 }
1895 let mut out_flat = Tensor::zeros((t * topk, n), DType::F32, device)?;
1896 for (eid, slots) in groups.into_iter() {
1897 let qs = make_storage(eid, device)?;
1898 let shape: crate::Shape = (n, k).into();
1899 let w_e = QTensor::make(qs, shape);
1900 let qm = QMatMul::from_arc(Arc::new(w_e))?;
1901 let m = slots.len();
1902 let idx = Tensor::from_vec(slots, (m,), device)?;
1903 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)?;
1906 }
1907 out_flat.reshape((t, topk, n))?.to_dtype(out_dtype)
1908}
1909
1910#[cfg_attr(not(feature = "rocm"), allow(unused_variables))]
1916pub fn moe_combine(ys: &Tensor, scores: &Tensor) -> Result<Tensor> {
1917 let (t, topk, n) = ys.dims3()?;
1918 #[cfg(feature = "rocm")]
1919 if let Device::Rocm(dev) = ys.device() {
1920 let ys_c = ys.contiguous()?;
1921 let scores_c = scores.to_dtype(DType::F32)?.contiguous()?;
1922 let (ys_store, _) = ys_c.storage_and_layout();
1923 let yr = match &*ys_store {
1924 Storage::Rocm(r) => r,
1925 _ => crate::bail!("moe_combine: ys not on rocm after contiguous()"),
1926 };
1927 let (sc_store, _) = scores_c.storage_and_layout();
1928 let sr = match &*sc_store {
1929 Storage::Rocm(r) => r,
1930 _ => crate::bail!("moe_combine: scores not on rocm after contiguous()"),
1931 };
1932 let out = dev.moe_combine(yr, sr, t, topk, n)?;
1933 return Ok(crate::tensor::from_storage(
1934 Storage::Rocm(out),
1935 (t, n),
1936 crate::op::BackpropOp::none(),
1937 false,
1938 ));
1939 }
1940 let out_dtype = ys.dtype();
1945 ys.to_dtype(DType::F32)?
1946 .broadcast_mul(&scores.to_dtype(DType::F32)?.unsqueeze(D::Minus1)?)?
1947 .sum(D::Minus2)?
1948 .to_dtype(out_dtype)
1949}
1950
1951#[cfg_attr(not(feature = "rocm"), allow(unused_variables))]
1956pub fn moe_route(logits: &Tensor, topk: usize, norm: bool) -> Result<(Tensor, Tensor)> {
1957 let (ntok, n_experts) = logits.dims2()?;
1958 #[cfg(feature = "rocm")]
1959 if let Device::Rocm(dev) = logits.device() {
1960 let logits_c = logits.to_dtype(DType::F32)?.contiguous()?;
1961 let (lg_store, _) = logits_c.storage_and_layout();
1962 let lr = match &*lg_store {
1963 Storage::Rocm(r) => r,
1964 _ => crate::bail!("moe_route: logits not on rocm after contiguous()"),
1965 };
1966 let (ids, w) = dev.moe_route(lr, ntok, n_experts, topk, norm)?;
1967 let ids_t = crate::tensor::from_storage(
1968 Storage::Rocm(ids),
1969 (ntok, topk),
1970 crate::op::BackpropOp::none(),
1971 false,
1972 );
1973 let w_t = crate::tensor::from_storage(
1974 Storage::Rocm(w),
1975 (ntok, topk),
1976 crate::op::BackpropOp::none(),
1977 false,
1978 );
1979 return Ok((ids_t, w_t));
1980 }
1981 #[cfg(feature = "cuda")]
1982 if let Device::Cuda(cdev) = logits.device() {
1983 if n_experts <= 256 && topk <= 32 {
1984 let logits_c = logits.to_dtype(DType::F32)?.contiguous()?;
1985 let (lg_store, _) = logits_c.storage_and_layout();
1986 let lr = match &*lg_store {
1987 Storage::Cuda(c) => c,
1988 _ => crate::bail!("moe_route: logits not on cuda after contiguous()"),
1989 };
1990 let lview = lr.as_cuda_slice::<f32>()?.slice(0..);
1991 let (ids, w) = cuda::moe_route(&lview, ntok, n_experts, topk, norm, cdev)?;
1992 let ids_t = crate::tensor::from_storage(
1993 Storage::Cuda(ids),
1994 (ntok, topk),
1995 crate::op::BackpropOp::none(),
1996 false,
1997 );
1998 let w_t = crate::tensor::from_storage(
1999 Storage::Cuda(w),
2000 (ntok, topk),
2001 crate::op::BackpropOp::none(),
2002 false,
2003 );
2004 return Ok((ids_t, w_t));
2005 }
2006 }
2007 #[cfg(feature = "vulkan")]
2011 if let Device::Vulkan(vdev) = logits.device() {
2012 if norm && n_experts == 128 && topk == 8 {
2013 let logits_c = logits.to_dtype(DType::F32)?.contiguous()?;
2014 let (lg_store, _) = logits_c.storage_and_layout();
2015 let lv = match &*lg_store {
2016 Storage::Vulkan(v) => v,
2017 _ => crate::bail!("moe_route: logits not on vulkan after contiguous()"),
2018 };
2019 let (ids, w) = vdev.moe_route_vk(lv, ntok, n_experts, topk)?;
2020 let ids_t = crate::tensor::from_storage(
2021 Storage::Vulkan(ids),
2022 (ntok, topk),
2023 crate::op::BackpropOp::none(),
2024 false,
2025 );
2026 let w_t = crate::tensor::from_storage(
2027 Storage::Vulkan(w),
2028 (ntok, topk),
2029 crate::op::BackpropOp::none(),
2030 false,
2031 );
2032 return Ok((ids_t, w_t));
2033 }
2034 }
2035 let lf = logits.to_dtype(DType::F32)?;
2036 let mx = lf.max_keepdim(D::Minus1)?;
2037 let e = lf.broadcast_sub(&mx)?.exp()?;
2038 let z = e.sum_keepdim(D::Minus1)?;
2039 let p = e.broadcast_div(&z)?;
2040 let (sv, si) = p.sort_last_dim(false)?;
2041 let ids = si.narrow(D::Minus1, 0, topk)?.contiguous()?;
2042 let mut w = sv.narrow(D::Minus1, 0, topk)?.contiguous()?;
2043 if norm {
2044 w = w.broadcast_div(&w.sum_keepdim(D::Minus1)?)?;
2045 }
2046 Ok((ids, w))
2047}
2048
2049pub fn moe_gate_up(
2057 x: &Tensor,
2058 ids: &Tensor,
2059 gate: &QMatMul,
2060 up: &QMatMul,
2061) -> Result<(Tensor, Tensor)> {
2062 #[cfg(feature = "rocm")]
2063 {
2064 if let (QMatMul::QTensor(gq), QMatMul::QTensor(uq)) = (gate, up) {
2065 if let (QStorage::Rocm(_, dev), QStorage::Rocm(..)) = (&gq.storage, &uq.storage) {
2066 let dt = gq.storage.dtype();
2067 if dt == uq.storage.dtype() {
2068 if let Some(qt) = crate::RocmQuantType::from_ggml(dt) {
2069 let (_e, n, k) = gq.shape().dims3()?;
2070 let (t, topk) = ids.dims2()?;
2071 let use_qmmq = t > 1 && qt.qmmq_capable();
2072 if x.dim(1)? == 1 && !use_qmmq {
2073 let nrows = t * topk;
2074 let x_exp = x.broadcast_as((t, topk, k))?;
2075 let x_flat = match x_exp.dtype() {
2076 DType::BF16 | DType::F16 => {
2077 x_exp.reshape((nrows, k))?.contiguous()?
2078 }
2079 DType::F32 if qt.dp4a_active() => {
2080 x_exp.reshape((nrows, k))?.contiguous()?
2081 }
2082 _ => x_exp
2083 .reshape((nrows, k))?
2084 .to_dtype(DType::F16)?
2085 .contiguous()?,
2086 };
2087 let out_dtype = x.dtype();
2088 let ids_u32 =
2089 ids.reshape((nrows,))?.to_dtype(DType::U32)?.contiguous()?;
2090 let gwb = gq.rocm_moe_bank(dev)?;
2091 let uwb = uq.rocm_moe_bank(dev)?;
2092 let (xstore, _) = x_flat.storage_and_layout();
2093 let xr = match &*xstore {
2094 Storage::Rocm(r) => r,
2095 _ => crate::bail!("moe_gate_up: x not on rocm after contiguous()"),
2096 };
2097 let (idstore, _) = ids_u32.storage_and_layout();
2098 let idr = match &*idstore {
2099 Storage::Rocm(r) => r,
2100 _ => crate::bail!("moe_gate_up: ids not on rocm"),
2101 };
2102 let (gy, uy) = dev.moe_matvec_pair(
2103 qt,
2104 gwb.as_ref(),
2105 uwb.as_ref(),
2106 xr,
2107 idr,
2108 nrows,
2109 n,
2110 k,
2111 )?;
2112 let g = crate::tensor::from_storage(
2113 Storage::Rocm(gy),
2114 (nrows, n),
2115 crate::op::BackpropOp::none(),
2116 false,
2117 )
2118 .reshape((t, topk, n))?
2119 .to_dtype(out_dtype)?;
2120 let u = crate::tensor::from_storage(
2121 Storage::Rocm(uy),
2122 (nrows, n),
2123 crate::op::BackpropOp::none(),
2124 false,
2125 )
2126 .reshape((t, topk, n))?
2127 .to_dtype(out_dtype)?;
2128 return Ok((g, u));
2129 }
2130 }
2131 }
2132 }
2133 }
2134 }
2135 #[cfg(feature = "vulkan")]
2142 if std::env::var_os("VK_MOE_GU_FUSE_OFF").is_none() {
2143 if let (QMatMul::QTensor(gq), QMatMul::QTensor(uq)) = (gate, up) {
2144 if let (QStorage::Vulkan(_, dev), QStorage::Vulkan(..)) = (&gq.storage, &uq.storage) {
2145 let dt = gq.storage.dtype();
2146 if dt == uq.storage.dtype() && dev.has_int_dot8() {
2147 let (e_cnt, n, k) = gq.shape().dims3()?;
2148 if uq.shape().dims3()? == (e_cnt, n, k) && x.dim(1)? == 1 {
2150 if let Some((blk, with_xsum)) = vk_moe_blk_dp4a_kernel(dt, n, k) {
2151 let (t, topk) = ids.dims2()?;
2152 let nrows = t * topk;
2153 let x_flat = x
2154 .broadcast_as((t, topk, k))?
2155 .reshape((nrows, k))?
2156 .to_dtype(DType::F32)?
2157 .contiguous()?;
2158 let ids_u32 =
2159 ids.reshape((nrows,))?.to_dtype(DType::U32)?.contiguous()?;
2160 let (xstore, _) = x_flat.storage_and_layout();
2161 let xv = match &*xstore {
2162 Storage::Vulkan(v) => v,
2163 _ => {
2164 crate::bail!("moe_gate_up: x not on vulkan after contiguous()")
2165 }
2166 };
2167 let (idstore, _) = ids_u32.storage_and_layout();
2168 let idv = match &*idstore {
2169 Storage::Vulkan(v) => v,
2170 _ => crate::bail!("moe_gate_up: ids not on vulkan"),
2171 };
2172 let (xq, xs, xsum) = dev.quantize_act_q8(xv, nrows, k)?;
2174 let gbank = gq.vulkan_moe_bank_split(dev, e_cnt, n, k)?;
2175 let ubank = uq.vulkan_moe_bank_split(dev, e_cnt, n, k)?;
2176 let out_dtype = x.dtype();
2177 let (gy, uy) = if t > 1
2181 && dt == GgmlDType::Q4K
2182 && std::env::var_os("VK_MOE_PREFILL_GEMM_OFF").is_none()
2183 {
2184 let (counts, rows) =
2185 dev.moe_expert_rows_vk(idv, nrows, e_cnt, t)?;
2186 let gy = dev.mmq_q4k_id_pre_gpu(
2187 gbank.as_ref(),
2188 &xq,
2189 &xs,
2190 &xsum,
2191 &rows,
2192 &counts,
2193 nrows,
2194 e_cnt,
2195 t,
2196 n,
2197 k,
2198 )?;
2199 let uy = dev.mmq_q4k_id_pre_gpu(
2200 ubank.as_ref(),
2201 &xq,
2202 &xs,
2203 &xsum,
2204 &rows,
2205 &counts,
2206 nrows,
2207 e_cnt,
2208 t,
2209 n,
2210 k,
2211 )?;
2212 (gy, uy)
2213 } else {
2214 let gy = dev.moe_matvec_blk_dp4a_pre_gpu(
2215 blk,
2216 with_xsum,
2217 gbank.as_ref(),
2218 &xq,
2219 &xs,
2220 &xsum,
2221 idv,
2222 nrows,
2223 n,
2224 )?;
2225 let uy = dev.moe_matvec_blk_dp4a_pre_gpu(
2226 blk,
2227 with_xsum,
2228 ubank.as_ref(),
2229 &xq,
2230 &xs,
2231 &xsum,
2232 idv,
2233 nrows,
2234 n,
2235 )?;
2236 (gy, uy)
2237 };
2238 let shape = |o| -> Result<Tensor> {
2239 crate::tensor::from_storage(
2240 Storage::Vulkan(o),
2241 (nrows, n),
2242 crate::op::BackpropOp::none(),
2243 false,
2244 )
2245 .reshape((t, topk, n))?
2246 .to_dtype(out_dtype)
2247 };
2248 return Ok((shape(gy)?, shape(uy)?));
2249 }
2250 }
2251 }
2252 }
2253 }
2254 }
2255 Ok((
2256 gate.indexed_moe_forward(x, ids)?,
2257 up.indexed_moe_forward(x, ids)?,
2258 ))
2259}
2260
2261impl QMatMul {
2262 pub fn from_arc(qtensor: std::sync::Arc<QTensor>) -> Result<Self> {
2263 #[cfg(feature = "vulkan")]
2269 {
2270 let dt = qtensor.dtype();
2271 let native_vk = matches!(
2272 dt,
2273 GgmlDType::Q4_0
2274 | GgmlDType::Q8_0
2275 | GgmlDType::Q4K
2276 | GgmlDType::Q5K
2277 | GgmlDType::Q6K
2278 | GgmlDType::Q2K
2279 | GgmlDType::Q3K
2280 | GgmlDType::IQ4_XS
2281 | GgmlDType::IQ4_NL
2282 | GgmlDType::TQ2_0
2283 | GgmlDType::IQ2_XXS
2284 | GgmlDType::IQ2_S
2285 | GgmlDType::IQ3_XXS
2286 | GgmlDType::IQ3_S
2287 | GgmlDType::IQ1_S
2288 | GgmlDType::IQ1_M
2289 | GgmlDType::IQ2_XS
2290 );
2291 if native_vk {
2292 if let Device::Vulkan(d) = qtensor.device() {
2293 if let Ok((n, k)) = qtensor.shape().dims2() {
2294 let blk = dt.block_size();
2295 if k % blk == 0 {
2296 let bytes = qtensor.data()?;
2297 let wq = match dt {
2304 GgmlDType::Q6K => d.quantize_q6k(&bytes, n, k)?,
2305 GgmlDType::Q3K => d.quantize_q3k(&bytes, n, k)?,
2306 GgmlDType::Q8_0 => d.quantize_q8_blocks(&bytes, n, k)?,
2307 GgmlDType::IQ2_XXS => d.quantize_iq2xxs(&bytes, n, k)?,
2308 GgmlDType::IQ2_XS => d.quantize_iq2xs(&bytes, n, k)?,
2309 GgmlDType::IQ1_M => d.quantize_iq1m(&bytes, n, k)?,
2310 GgmlDType::IQ1_S => d.quantize_iq1s(&bytes, n, k)?,
2311 GgmlDType::IQ3_S => d.quantize_iq3s(&bytes, n, k)?,
2312 GgmlDType::IQ3_XXS => d.quantize_iq3xxs(&bytes, n, k)?,
2313 GgmlDType::IQ2_S => d.quantize_iq2s(&bytes, n, k)?,
2314 _ => d.upload_qweight(&bytes)?,
2315 };
2316 return Ok(Self::VulkanQuant {
2317 qtensor,
2318 wq: std::sync::Arc::new(wq),
2319 dtype: dt,
2320 n,
2321 k,
2322 });
2323 }
2324 }
2325 }
2326 }
2327 }
2328 #[cfg(feature = "wgpu")]
2331 {
2332 let dt = qtensor.dtype();
2333 let native_wgpu = matches!(dt, GgmlDType::Q4_0 | GgmlDType::Q8_0 | GgmlDType::Q4K);
2334 if native_wgpu {
2335 if let Device::Wgpu(d) = qtensor.device() {
2336 if let Ok((n, k)) = qtensor.shape().dims2() {
2337 let blk = dt.block_size();
2338 if k % blk == 0 {
2339 let bytes = qtensor.data()?;
2340 let wq = d.upload_qweight(&bytes)?;
2341 return Ok(Self::WgpuQuant {
2342 qtensor,
2343 wq: std::sync::Arc::new(wq),
2344 dtype: dt,
2345 n,
2346 k,
2347 });
2348 }
2349 }
2350 }
2351 }
2352 }
2353 #[cfg(feature = "rocm")]
2359 {
2360 let dt = qtensor.dtype();
2361 if let Some(qt) = crate::RocmQuantType::from_ggml(dt) {
2366 if let Device::Rocm(d) = qtensor.device() {
2367 if let Ok((n, k)) = qtensor.shape().dims2() {
2368 let blk_ok = k % qt.block_elems() == 0;
2369 if blk_ok {
2370 use crate::backend::BackendDevice;
2371 let bytes = qtensor.data()?;
2372 let wq = d.storage_from_slice(bytes.as_ref())?;
2373 return Ok(Self::RocmQuant {
2374 qtensor,
2375 wq: std::sync::Arc::new(wq),
2376 dtype: dt,
2377 n,
2378 k,
2379 });
2380 }
2381 }
2382 }
2383 }
2384 }
2385 #[cfg(feature = "rocm")]
2391 {
2392 if qtensor.device().is_rocm()
2393 && qtensor.shape().dims().len() == 3
2394 && crate::RocmQuantType::from_ggml(qtensor.dtype()).is_some()
2395 {
2396 return Ok(Self::QTensor(qtensor));
2397 }
2398 }
2399 #[cfg(feature = "vulkan")]
2405 {
2406 if qtensor.device().is_vulkan()
2407 && qtensor.shape().dims().len() == 3
2408 && vk_moe_kernel(qtensor.dtype()).is_some()
2409 {
2410 return Ok(Self::QTensor(qtensor));
2411 }
2412 }
2413 #[cfg(feature = "wgpu")]
2414 {
2415 if qtensor.device().is_wgpu()
2416 && qtensor.shape().dims().len() == 3
2417 && wgpu_moe_kernel(qtensor.dtype()).is_some()
2418 {
2419 return Ok(Self::QTensor(qtensor));
2420 }
2421 }
2422 let dequantize = match qtensor.dtype() {
2423 GgmlDType::F32 | GgmlDType::F16 | GgmlDType::BF16 | GgmlDType::I32 => true,
2424 _ => {
2427 qtensor.device().is_vulkan()
2428 || qtensor.device().is_wgpu()
2429 || qtensor.device().is_rocm()
2430 }
2431 };
2432 let t = if dequantize {
2433 if qtensor.device().is_rocm() {
2437 Self::TensorF16(qtensor.dequantize_f16(&qtensor.device())?)
2438 } else {
2439 Self::Tensor(qtensor.dequantize(&qtensor.device())?)
2440 }
2441 } else {
2442 Self::QTensor(qtensor)
2443 };
2444 Ok(t)
2445 }
2446
2447 pub fn from_qtensor(qtensor: QTensor) -> Result<Self> {
2448 Self::from_arc(std::sync::Arc::new(qtensor))
2449 }
2450
2451 pub fn dequantize_f16(&self) -> Result<Tensor> {
2452 match self {
2453 Self::QTensor(t) => t.dequantize_f16(&t.device()),
2454 Self::Tensor(t) => t.to_dtype(DType::F16),
2455 Self::TensorF16(t) => Ok(t.clone()),
2456 #[cfg(feature = "rocm")]
2457 Self::RocmQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
2458 #[cfg(feature = "vulkan")]
2459 Self::VulkanQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
2460 #[cfg(feature = "wgpu")]
2461 Self::WgpuQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
2462 }
2463 }
2464
2465 pub fn forward_via_f16(&self, xs: &Tensor) -> Result<Tensor> {
2466 let w = self.dequantize_f16()?;
2467 let in_dtype = xs.dtype();
2468 let w = match *xs.dims() {
2469 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
2470 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
2471 _ => w.t()?,
2472 };
2473 xs.to_dtype(DType::F16)?.matmul(&w)?.to_dtype(in_dtype)
2474 }
2475
2476 pub fn indexed_moe_forward(&self, x: &Tensor, ids: &Tensor) -> Result<Tensor> {
2477 match self {
2478 Self::QTensor(t) => t.indexed_moe_forward(x, ids),
2479 #[cfg(feature = "rocm")]
2484 Self::RocmQuant {
2485 qtensor, wq, dtype, ..
2486 } if crate::RocmQuantType::from_ggml(*dtype).is_some() => {
2487 let qt = crate::RocmQuantType::from_ggml(*dtype).unwrap();
2488 let wbank = wq.as_ref();
2489 let (_e_cnt, n, k) = qtensor.shape().dims3()?;
2491 let (t, topk) = ids.dims2()?;
2492 let s = x.dim(1)?; let x_exp = if s == topk {
2494 x.clone()
2495 } else {
2496 x.broadcast_as((t, topk, k))?
2497 };
2498 let nrows = t * topk;
2499 let use_qmmq = t > 1 && qt.qmmq_capable();
2505 let x_flat = match x_exp.dtype() {
2506 DType::F16 | DType::F32 if use_qmmq => {
2510 x_exp.reshape((nrows, k))?.contiguous()?
2511 }
2512 _ if use_qmmq => x_exp
2513 .reshape((nrows, k))?
2514 .to_dtype(DType::F16)?
2515 .contiguous()?,
2516 DType::BF16 | DType::F16 => x_exp.reshape((nrows, k))?.contiguous()?,
2517 DType::F32 if qt.dp4a_active() => x_exp.reshape((nrows, k))?.contiguous()?,
2520 _ => x_exp
2521 .reshape((nrows, k))?
2522 .to_dtype(DType::F16)?
2523 .contiguous()?,
2524 };
2525 let out_dtype = x.dtype();
2526 let ids_u32 = ids
2531 .reshape((nrows,))?
2532 .to_dtype(crate::DType::U32)?
2533 .contiguous()?;
2534 let (xstore, _) = x_flat.storage_and_layout();
2535 let xr = match &*xstore {
2536 crate::Storage::Rocm(r) => r,
2537 _ => crate::bail!("rocm MoE: x not on rocm after contiguous()"),
2538 };
2539 let (idstore, _) = ids_u32.storage_and_layout();
2540 let idr = match &*idstore {
2541 crate::Storage::Rocm(r) => r,
2542 _ => crate::bail!("rocm MoE: ids not on rocm"),
2543 };
2544 let y = if use_qmmq {
2545 wbank
2546 .device
2547 .moe_qmmq_quant(qt, wbank, xr, idr, nrows, n, k)?
2548 } else {
2549 wbank
2550 .device
2551 .moe_matvec_quant(qt, wbank, xr, idr, nrows, n, k)?
2552 };
2553 let out = crate::tensor::from_storage(
2554 crate::Storage::Rocm(y),
2555 (nrows, n),
2556 crate::op::BackpropOp::none(),
2557 false,
2558 );
2559 out.reshape((t, topk, n))?.to_dtype(out_dtype)
2560 }
2561 #[cfg(feature = "rocm")]
2563 Self::RocmQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
2564 #[cfg(feature = "vulkan")]
2565 Self::VulkanQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
2566 #[cfg(feature = "wgpu")]
2567 Self::WgpuQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
2568 _ => {
2569 panic!("Not implemented!")
2570 }
2571 }
2572 }
2573
2574 pub fn embedding(&self, ids: &Tensor) -> Result<Tensor> {
2575 match self {
2576 Self::QTensor(t) => t.embedding(ids),
2577 #[cfg(feature = "vulkan")]
2578 Self::VulkanQuant { qtensor, .. } => qtensor.embedding(ids),
2579 #[cfg(feature = "wgpu")]
2580 Self::WgpuQuant { qtensor, .. } => qtensor.embedding(ids),
2581 #[cfg(feature = "rocm")]
2582 Self::RocmQuant { qtensor, .. } => qtensor.embedding(ids),
2583 Self::Tensor(w) | Self::TensorF16(w) => {
2584 let mut final_dims = ids.dims().to_vec();
2585 final_dims.push(w.dim(D::Minus1)?);
2586 let ids = ids.to_device(w.device())?.flatten_all()?;
2587 w.index_select(&ids, 0)?.reshape(final_dims)
2588 }
2589 }
2590 }
2591}
2592
2593impl QTensor {
2594 pub fn gemv_fused_shared_lhs(ts: &[&Self], lhs: &Tensor) -> Result<Option<Vec<Tensor>>> {
2597 #[cfg(target_arch = "aarch64")]
2598 {
2599 if ts.is_empty() || !lhs.device().is_cpu() || lhs.dtype() != crate::DType::F32 {
2600 return Ok(None);
2601 }
2602 if !lhs.is_contiguous() {
2603 return Ok(None);
2604 }
2605 let dims = lhs.dims();
2606 let Some((&k, batch)) = dims.split_last() else {
2607 return Ok(None);
2608 };
2609 if batch.iter().product::<usize>() != 1 {
2610 return Ok(None);
2611 }
2612 let mut ns = Vec::with_capacity(ts.len());
2613 let mut parts: Vec<(&dyn QuantizedType, &repack::PackedCache)> =
2614 Vec::with_capacity(ts.len());
2615 for t in ts {
2616 let (n, tk) = t.shape.dims2()?;
2617 if tk != k {
2618 return Ok(None);
2619 }
2620 let QStorage::Cpu(s) = &t.storage else {
2621 return Ok(None);
2622 };
2623 ns.push(n);
2624 parts.push((s.as_ref(), &t.repacked_qs));
2625 }
2626 let storage = lhs.storage();
2627 let crate::Storage::Cpu(cpu) = &*storage else {
2628 return Ok(None);
2629 };
2630 let slice = cpu.as_slice::<f32>()?;
2631 let offset = lhs.layout().start_offset();
2632 let slice = &slice[offset..offset + k];
2633 let mut dsts: Vec<Vec<f32>> = ns.iter().map(|&n| vec![0f32; n]).collect();
2634 if !repack::try_gemv_fused(k, slice, &parts, &mut dsts)? {
2635 return Ok(None);
2636 }
2637 drop(storage);
2638 let outs = dsts
2639 .into_iter()
2640 .zip(&ns)
2641 .map(|(d, &n)| {
2642 let mut shape = dims.to_vec();
2643 *shape.last_mut().unwrap() = n;
2644 Tensor::from_vec(d, shape, &crate::Device::Cpu)
2645 })
2646 .collect::<Result<Vec<_>>>()?;
2647 Ok(Some(outs))
2648 }
2649 #[cfg(target_arch = "x86_64")]
2650 {
2651 if repack_x86::level() != Some(repack_x86::X86Level::Avx512Vnni) {
2654 return Ok(None);
2655 }
2656 if ts.is_empty() || !lhs.device().is_cpu() || lhs.dtype() != crate::DType::F32 {
2657 return Ok(None);
2658 }
2659 if !lhs.is_contiguous() {
2660 return Ok(None);
2661 }
2662 let dims = lhs.dims();
2663 let Some((&k, batch)) = dims.split_last() else {
2664 return Ok(None);
2665 };
2666 let m: usize = batch.iter().product::<usize>().max(1);
2667 let dtype = ts[0].dtype();
2668 let mut shapes = Vec::with_capacity(ts.len());
2669 for t in ts {
2670 let Ok((n, tk)) = t.shape.dims2() else {
2671 return Ok(None);
2672 };
2673 if tk != k || t.dtype() != dtype || !repack_x86::select(dtype, n, k) {
2674 return Ok(None);
2675 }
2676 shapes.push(n);
2677 }
2678 let guard = lhs.storage();
2679 let crate::Storage::Cpu(cpu) = &*guard else {
2680 return Ok(None);
2681 };
2682 let slice = cpu.as_slice::<f32>()?;
2683 let offset = lhs.layout().start_offset();
2684 let lhs_data = &slice[offset..offset + m * k];
2685 let lhs_q = repack_x86::quantize_lhs(lhs_data, m, k);
2686 let packs: Vec<&repack_x86::PackedX86> = ts
2687 .iter()
2688 .enumerate()
2689 .map(|(i, t)| {
2690 let n = shapes[i];
2691 t.repacked_qs
2692 .x86_get_or_init(|| repack_x86::pack(dtype, t.storage_ref(), n, k))
2693 })
2694 .collect();
2695 let parts: Vec<(&repack_x86::PackedX86, usize)> = packs
2696 .iter()
2697 .zip(shapes.iter())
2698 .map(|(p, &n)| (*p, n))
2699 .collect();
2700 let mut dsts: Vec<Vec<f32>> = shapes.iter().map(|&n| vec![0f32; m * n]).collect();
2701 repack_x86::gemv_fused(&parts, &lhs_q, m, k, &mut dsts);
2702 drop(guard);
2703 let mut out = Vec::with_capacity(ts.len());
2704 let mut out_dims = dims.to_vec();
2705 for (dst, &n) in dsts.into_iter().zip(shapes.iter()) {
2706 *out_dims.last_mut().unwrap() = n;
2707 out.push(Tensor::from_vec(
2708 dst,
2709 out_dims.clone(),
2710 &crate::Device::Cpu,
2711 )?);
2712 }
2713 Ok(Some(out))
2714 }
2715
2716 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
2717 {
2718 let _ = (ts, lhs);
2719 Ok(None)
2720 }
2721 }
2722}
2723
2724impl QTensor {
2725 #[cfg(target_arch = "x86_64")]
2726 pub(crate) fn storage_ref(&self) -> &dyn QuantizedType {
2727 match &self.storage {
2728 QStorage::Cpu(s) => s.as_ref(),
2729 _ => unreachable!("cpu-only path"),
2730 }
2731 }
2732
2733 pub fn indexed_gemv(&self, x: &Tensor, ids: &Tensor) -> Result<Option<Tensor>> {
2737 #[cfg(target_arch = "aarch64")]
2738 {
2739 if !x.device().is_cpu()
2740 || !matches!(x.dtype(), crate::DType::F32 | crate::DType::BF16)
2741 || !x.is_contiguous()
2742 {
2743 return Ok(None);
2744 }
2745 let Ok((n_experts, n_out, k)) = self.shape.dims3() else {
2746 return Ok(None);
2747 };
2748 let Ok((batch, x_t, xk)) = x.dims3() else {
2749 return Ok(None);
2750 };
2751 let Ok((ids_b, topk)) = ids.dims2() else {
2752 return Ok(None);
2753 };
2754 if xk != k || ids_b != batch || (x_t != 1 && x_t != topk) {
2755 return Ok(None);
2756 }
2757 let QStorage::Cpu(storage) = &self.storage else {
2758 return Ok(None);
2759 };
2760 let ids_v: Vec<u32> = ids
2761 .to_dtype(crate::DType::U32)?
2762 .flatten_all()?
2763 .to_vec1::<u32>()?;
2764 if ids_v.iter().any(|&e| e as usize >= n_experts) {
2765 crate::bail!("expert index out of range");
2766 }
2767 let guard = x.storage();
2768 let crate::Storage::Cpu(cpu) = &*guard else {
2769 return Ok(None);
2770 };
2771 let offset = x.layout().start_offset();
2772 let n_rows = batch * x_t;
2773 let widened;
2774 let lhs: &[f32] = if x.dtype() == crate::DType::BF16 {
2775 let bslice = cpu.as_slice::<half::bf16>()?;
2776 widened = widen_bf16(&bslice[offset..offset + n_rows * k]);
2777 &widened
2778 } else {
2779 let slice = cpu.as_slice::<f32>()?;
2780 &slice[offset..offset + n_rows * k]
2781 };
2782 let mut dst = vec![0f32; batch * topk * n_out];
2783 let ok = repack::try_indexed_gemv(
2784 storage.as_ref(),
2785 &self.repacked_qs,
2786 n_experts,
2787 n_out,
2788 k,
2789 lhs,
2790 n_rows,
2791 &ids_v,
2792 topk,
2793 &mut dst,
2794 )?;
2795 drop(guard);
2796 if !ok {
2797 return Ok(None);
2798 }
2799 let out = Tensor::from_vec(dst, (batch, topk, n_out), &crate::Device::Cpu)?;
2800 if x.dtype() == crate::DType::BF16 {
2801 Ok(Some(out.to_dtype(crate::DType::BF16)?))
2802 } else {
2803 Ok(Some(out))
2804 }
2805 }
2806 #[cfg(not(target_arch = "aarch64"))]
2807 {
2808 let _ = (x, ids);
2809 Ok(None)
2810 }
2811 }
2812}
2813
2814fn widen_bf16(src: &[half::bf16]) -> Vec<f32> {
2815 let mut out: Vec<f32> = Vec::with_capacity(src.len());
2816 let out_ptr = out.as_mut_ptr() as usize;
2817 let n_units = src.len().div_ceil(WIDEN_CHUNK);
2818 crate::utils::barrier_pool().execute_chunked(n_units, |range| {
2819 let out_ptr = out_ptr as *mut f32;
2820 for unit in range {
2821 let lo = unit * WIDEN_CHUNK;
2822 let hi = src.len().min(lo + WIDEN_CHUNK);
2823 for (i, v) in src[lo..hi].iter().enumerate() {
2824 unsafe { *out_ptr.add(lo + i) = v.to_f32() };
2825 }
2826 }
2827 });
2828 unsafe { out.set_len(src.len()) };
2830 out
2831}
2832
2833fn narrow_bf16(src: &[f32]) -> Vec<half::bf16> {
2834 let mut out: Vec<half::bf16> = Vec::with_capacity(src.len());
2835 let out_ptr = out.as_mut_ptr() as usize;
2836 let n_units = src.len().div_ceil(WIDEN_CHUNK);
2837 crate::utils::barrier_pool().execute_chunked(n_units, |range| {
2838 let out_ptr = out_ptr as *mut half::bf16;
2839 for unit in range {
2840 let lo = unit * WIDEN_CHUNK;
2841 let hi = src.len().min(lo + WIDEN_CHUNK);
2842 for (i, v) in src[lo..hi].iter().enumerate() {
2843 unsafe { *out_ptr.add(lo + i) = half::bf16::from_f32(*v) };
2844 }
2845 }
2846 });
2847 unsafe { out.set_len(src.len()) };
2849 out
2850}
2851
2852const WIDEN_CHUNK: usize = 32 * 1024;
2853
2854impl crate::CustomOp1 for QTensor {
2855 fn name(&self) -> &'static str {
2856 "qmatmul"
2857 }
2858
2859 fn cpu_fwd(
2860 &self,
2861 storage: &crate::CpuStorage,
2862 layout: &crate::Layout,
2863 ) -> Result<(crate::CpuStorage, Shape)> {
2864 if !layout.is_contiguous() {
2865 crate::bail!("input tensor is not contiguous {layout:?}")
2866 }
2867 let src_shape = layout.shape();
2868 let (n, k) = self.shape.dims2()?;
2870 if src_shape.rank() < 2 {
2871 crate::bail!("input tensor has only one dimension {layout:?}")
2872 }
2873 let mut dst_shape = src_shape.dims().to_vec();
2874 let last_k = dst_shape.pop().unwrap();
2875 if last_k != k {
2876 crate::bail!("input tensor {layout:?} incompatible with {:?}", self.shape)
2877 }
2878 dst_shape.push(n);
2879 let dst_shape = Shape::from(dst_shape);
2880 #[allow(clippy::infallible_destructuring_match)]
2881 let self_storage = match &self.storage {
2882 QStorage::Cpu(storage) => storage,
2883 #[cfg(feature = "rocm")]
2884 QStorage::Rocm(..) => crate::bail!("Invalid storage"),
2885 #[cfg(feature = "vulkan")]
2886 QStorage::Vulkan(..) => crate::bail!("Invalid storage"),
2887 #[cfg(feature = "wgpu")]
2888 QStorage::Wgpu(..) => crate::bail!("Invalid storage"),
2889 QStorage::Metal(_) | QStorage::Cuda(_) | QStorage::Stream(_) => {
2890 crate::bail!("Invalid storage")
2891 }
2892 };
2893 match storage.dtype() {
2894 DType::F32 => {
2895 let slice = storage.as_slice::<f32>()?;
2896 let slice =
2897 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
2898 let mut dst_storage = vec![0f32; dst_shape.elem_count()];
2899
2900 let mkn = (dst_shape.elem_count() / n, k, n);
2901 let used_packed = repack::try_matmul_f32(
2902 self_storage.as_ref(),
2903 &self.repacked_qs,
2904 mkn,
2905 slice,
2906 &mut dst_storage,
2907 )?;
2908 if used_packed {
2909 return Ok((crate::CpuStorage::F32(dst_storage), dst_shape));
2910 }
2911
2912 self_storage.matmul_t(mkn, slice, &mut dst_storage)?;
2913 Ok((crate::CpuStorage::F32(dst_storage), dst_shape))
2914 }
2915 DType::F16 => {
2916 let slice = storage.as_slice::<f16>()?;
2917 let slice =
2918 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
2919 let mut dst_storage = vec![f16::ZERO; dst_shape.elem_count()];
2920 self_storage.matmul_t_f16(
2921 (dst_shape.elem_count() / n, k, n),
2922 slice,
2923 &mut dst_storage,
2924 )?;
2925 Ok((crate::CpuStorage::F16(dst_storage), dst_shape))
2926 }
2927 DType::BF16 => {
2928 let slice = storage.as_slice::<half::bf16>()?;
2930 let slice =
2931 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
2932 let lhs = widen_bf16(slice);
2933 let mut dst_storage = vec![0f32; dst_shape.elem_count()];
2934
2935 let mkn = (dst_shape.elem_count() / n, k, n);
2936 let used_packed = repack::try_matmul_f32(
2937 self_storage.as_ref(),
2938 &self.repacked_qs,
2939 mkn,
2940 &lhs,
2941 &mut dst_storage,
2942 )?;
2943 if !used_packed {
2944 self_storage.matmul_t(mkn, &lhs, &mut dst_storage)?;
2945 }
2946 let dst: Vec<half::bf16> = narrow_bf16(&dst_storage);
2947 Ok((crate::CpuStorage::BF16(dst), dst_shape))
2948 }
2949 _ => crate::bail!("Expected f32/f16/bf16"),
2950 }
2951 }
2952
2953 fn metal_fwd(
2954 &self,
2955 storage: &crate::MetalStorage,
2956 layout: &crate::Layout,
2957 ) -> Result<(crate::MetalStorage, Shape)> {
2958 let self_storage = match &self.storage {
2959 QStorage::Metal(metal) => metal,
2960 _ => unreachable!("Cannot call metal matmul on non metal QTensor"),
2961 };
2962 self_storage.fwd(&self.shape, storage, layout)
2963 }
2964
2965 fn cuda_fwd(
2966 &self,
2967 storage: &crate::CudaStorage,
2968 layout: &crate::Layout,
2969 ) -> Result<(crate::CudaStorage, Shape)> {
2970 let self_storage = match &self.storage {
2971 QStorage::Cuda(cuda) => cuda,
2972 _ => unreachable!("Cannot call cuda matmul on non cuda QTensor"),
2973 };
2974 self_storage.fwd(&self.shape, storage, layout)
2975 }
2976}
2977
2978fn dense_matmul(xs: &Tensor, w: &Tensor) -> Result<Tensor> {
2988 let k = *w.dims().last().unwrap();
2989 let rows = xs.elem_count() / k;
2990 if rows == 1 && xs.device().is_rocm() {
2991 let n = w.dim(0)?;
2992 #[cfg(feature = "rocm")]
2993 {
2994 let d = match xs.device() {
2998 Device::Rocm(d) => d.clone(),
2999 _ => unreachable!(),
3000 };
3001 let xs1 = xs.reshape((k,))?.to_dtype(w.dtype())?.contiguous()?;
3002 let w = w.contiguous()?;
3003 let (wstore, _) = w.storage_and_layout();
3004 let wr = match &*wstore {
3005 crate::Storage::Rocm(r) => r,
3006 _ => crate::bail!("dense_matmul: weight not on rocm"),
3007 };
3008 let (xstore, _) = xs1.storage_and_layout();
3009 let xr = match &*xstore {
3010 crate::Storage::Rocm(r) => r,
3011 _ => crate::bail!("dense_matmul: x not on rocm"),
3012 };
3013 let y = d.dense_gemv(wr, xr, n, k)?;
3014 let mut dims = xs.dims().to_vec();
3015 *dims.last_mut().unwrap() = n;
3016 return crate::tensor::from_storage(
3017 crate::Storage::Rocm(y),
3018 dims,
3019 crate::op::BackpropOp::none(),
3020 false,
3021 )
3022 .to_dtype(xs.dtype());
3023 }
3024 #[cfg(not(feature = "rocm"))]
3025 {
3026 let out = xs.reshape((1, k))?.broadcast_mul(w)?.sum(D::Minus1)?;
3027 let mut dims = xs.dims().to_vec();
3028 *dims.last_mut().unwrap() = n;
3029 return out.reshape(dims);
3030 }
3031 }
3032 let w = match *xs.dims() {
3033 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3034 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3035 _ => w.t()?,
3036 };
3037 xs.matmul(&w)
3038}
3039
3040#[cfg(feature = "vulkan")]
3055fn vulkan_prefill_gemm_max_rows(dtype: GgmlDType) -> usize {
3056 match dtype {
3057 GgmlDType::Q4_0 | GgmlDType::Q8_0 | GgmlDType::Q4K | GgmlDType::Q5K | GgmlDType::Q6K => {
3058 usize::MAX
3059 }
3060 _ => 0,
3061 }
3062}
3063
3064#[cfg(feature = "vulkan")]
3071fn vulkan_act_offset0(xs: &Tensor) -> Result<Tensor> {
3072 let xs = xs.contiguous()?;
3073 if xs.layout().start_offset() == 0 {
3074 Ok(xs)
3075 } else {
3076 xs.force_contiguous()
3077 }
3078}
3079
3080impl crate::Module for QMatMul {
3081 fn forward(&self, xs: &Tensor) -> Result<Tensor> {
3082 match self {
3083 #[cfg(feature = "rocm")]
3084 Self::RocmQuant {
3085 qtensor,
3086 wq,
3087 dtype,
3088 n,
3089 k,
3090 } => {
3091 let xs_recovered = if xs.device().is_rocm() {
3095 None
3096 } else {
3097 Some(xs.to_device(&qtensor.device())?)
3098 };
3099 let xs = xs_recovered.as_ref().unwrap_or(xs);
3100 let rows: usize = xs.elem_count() / *k;
3101 #[cfg(feature = "rocm")]
3107 let unified_qt = crate::RocmQuantType::from_ggml(*dtype);
3108 #[cfg(not(feature = "rocm"))]
3109 let unified_qt: Option<()> = None;
3110 #[cfg(feature = "rocm")]
3115 let qmmq_ok = unified_qt.map(|qt| qt.qmmq_capable()).unwrap_or(false);
3116 #[cfg(not(feature = "rocm"))]
3117 let qmmq_ok = false;
3118 if rows == 1 && unified_qt.is_some() {
3119 #[cfg(feature = "rocm")]
3130 let keep_f32 = unified_qt.map(|qt| qt.dp4a_active()).unwrap_or(false);
3131 #[cfg(not(feature = "rocm"))]
3132 let keep_f32 = false;
3133 let xs = match xs.dtype() {
3134 DType::BF16 | DType::F16 => xs.contiguous()?,
3135 DType::F32 if keep_f32 => xs.contiguous()?,
3136 _ => xs.to_dtype(DType::F16)?.contiguous()?,
3137 };
3138 let d = match xs.device() {
3139 Device::Rocm(d) => d,
3140 _ => crate::bail!("RocmQuant input not on rocm"),
3141 };
3142 let y = {
3143 let (store, _) = xs.storage_and_layout();
3144 let xr = match &*store {
3145 crate::Storage::Rocm(r) => r,
3146 _ => crate::bail!("RocmQuant expected rocm storage"),
3147 };
3148 #[cfg(feature = "rocm")]
3149 {
3150 d.matvec_quant(unified_qt.unwrap(), wq, xr, *n, *k)?
3151 }
3152 #[cfg(not(feature = "rocm"))]
3153 {
3154 crate::bail!("rocm feature disabled")
3155 }
3156 };
3157 let mut dims = xs.dims().to_vec();
3158 let last = dims.len() - 1;
3159 dims[last] = *n;
3160 Ok(crate::tensor::from_storage(
3161 crate::Storage::Rocm(y),
3162 dims,
3163 crate::op::BackpropOp::none(),
3164 false,
3165 ))
3166 } else if let Some(qt) = unified_qt.filter(|_| qmmq_ok) {
3167 let xs = xs.to_dtype(DType::F16)?.contiguous()?;
3176 let d = match xs.device() {
3177 Device::Rocm(d) => d,
3178 _ => crate::bail!("RocmQuant input not on rocm"),
3179 };
3180 let m = xs.elem_count() / *k;
3181 let y = {
3182 let (store, _) = xs.storage_and_layout();
3183 let xr = match &*store {
3184 crate::Storage::Rocm(r) => r,
3185 _ => crate::bail!("RocmQuant expected rocm storage"),
3186 };
3187 #[cfg(feature = "rocm")]
3188 {
3189 d.qmmq_quant(qt, xr, wq, m, *n, *k)?
3190 }
3191 #[cfg(not(feature = "rocm"))]
3192 {
3193 let _ = qt;
3194 crate::bail!("rocm feature disabled")
3195 }
3196 };
3197 let mut dims = xs.dims().to_vec();
3198 let last = dims.len() - 1;
3199 dims[last] = *n;
3200 Ok(crate::tensor::from_storage(
3201 crate::Storage::Rocm(y),
3202 dims,
3203 crate::op::BackpropOp::none(),
3204 false,
3205 ))
3206 } else {
3207 let w = match (unified_qt, xs.device()) {
3212 #[cfg(feature = "rocm")]
3213 (Some(qt), Device::Rocm(d)) => {
3214 let dense = d.dequantize_quant(qt, wq, *n * *k, DType::F16)?;
3215 crate::tensor::from_storage(
3216 crate::Storage::Rocm(dense),
3217 (*n, *k),
3218 crate::op::BackpropOp::none(),
3219 false,
3220 )
3221 }
3222 _ => qtensor.dequantize_f16(&xs.device())?,
3223 };
3224 let w = match *xs.dims() {
3225 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3226 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3227 _ => w.t()?,
3228 };
3229 xs.to_dtype(DType::F16)?.matmul(&w)
3230 }
3231 }
3232 #[cfg(feature = "vulkan")]
3233 Self::VulkanQuant {
3234 qtensor,
3235 wq,
3236 dtype,
3237 n,
3238 k,
3239 } => {
3240 let vdev = qtensor.device();
3245 let xs_on_vdev = if xs.device().same_device(&vdev) {
3246 None
3247 } else {
3248 Some(xs.to_device(&vdev)?)
3249 };
3250 let xs = xs_on_vdev.as_ref().unwrap_or(xs);
3251 let rows: usize = xs.elem_count() / *k;
3252 if rows == 1 {
3253 let xs = vulkan_act_offset0(xs)?;
3256 let d = match xs.device() {
3257 Device::Vulkan(d) => d,
3258 _ => crate::bail!("VulkanQuant input not on vulkan"),
3259 };
3260 let y = {
3261 let (store, _) = xs.storage_and_layout();
3262 let xv = match &*store {
3263 crate::Storage::Vulkan(v) => v,
3264 _ => crate::bail!("VulkanQuant expected vulkan storage"),
3265 };
3266 match dtype {
3267 GgmlDType::Q4_0 => d.matvec_q4_0_gpu(wq, xv, *n, *k)?,
3268 GgmlDType::Q8_0 => d.matvec_q8_gpu(wq, xv, *n, *k)?,
3271 GgmlDType::Q4K => d.matvec_q4k_gpu(wq, xv, *n, *k)?,
3272 GgmlDType::Q5K => d.matvec_q5k_gpu(wq, xv, *n, *k)?,
3273 GgmlDType::Q6K => d.matvec_q6k_gpu(wq, xv, *n, *k)?,
3274 GgmlDType::Q2K => d.matvec_q2k_gpu(wq, xv, *n, *k)?,
3275 GgmlDType::Q3K => d.matvec_q3k_gpu(wq, xv, *n, *k)?,
3276 GgmlDType::IQ4_XS => d.matvec_iq4xs_gpu(wq, xv, *n, *k)?,
3277 GgmlDType::IQ4_NL => d.matvec_iq4nl_gpu(wq, xv, *n, *k)?,
3278 GgmlDType::IQ2_XXS => d.matvec_iq2xxs_gpu(wq, xv, *n, *k)?,
3279 GgmlDType::IQ2_XS => d.matvec_iq2xs_gpu(wq, xv, *n, *k)?,
3280 GgmlDType::IQ1_M => d.matvec_iq1m_gpu(wq, xv, *n, *k)?,
3281 GgmlDType::IQ1_S => d.matvec_iq1s_gpu(wq, xv, *n, *k)?,
3282 GgmlDType::IQ3_S => d.matvec_iq3s_gpu(wq, xv, *n, *k)?,
3283 GgmlDType::IQ3_XXS => d.matvec_iq3xxs_gpu(wq, xv, *n, *k)?,
3284 GgmlDType::IQ2_S => d.matvec_iq2s_gpu(wq, xv, *n, *k)?,
3285 GgmlDType::TQ2_0 => d.matvec_tq2_0_gpu(wq, xv, *n, *k)?,
3286 other => crate::bail!("VulkanQuant: no native matvec for {other:?}"),
3287 }
3288 };
3289 let mut dims = xs.dims().to_vec();
3290 let last = dims.len() - 1;
3291 dims[last] = *n;
3292 Ok(crate::tensor::from_storage(
3293 crate::Storage::Vulkan(y),
3294 dims,
3295 crate::op::BackpropOp::none(),
3296 false,
3297 ))
3298 } else if rows <= vulkan_prefill_gemm_max_rows(*dtype) {
3299 let m = rows;
3308 let xs = vulkan_act_offset0(xs)?;
3309 let d = match xs.device() {
3310 Device::Vulkan(d) => d,
3311 _ => crate::bail!("VulkanQuant input not on vulkan"),
3312 };
3313 let y = {
3314 let (store, _) = xs.storage_and_layout();
3315 let xv = match &*store {
3316 crate::Storage::Vulkan(v) => v,
3317 _ => crate::bail!("VulkanQuant expected vulkan storage"),
3318 };
3319 match dtype {
3320 GgmlDType::Q4_0 => d.matmul_q4_0_gpu(wq, xv, m, *n, *k)?,
3321 GgmlDType::Q8_0 => d.matmul_q8_gpu(wq, xv, m, *n, *k)?,
3322 GgmlDType::Q4K
3327 if *n == 2048
3328 && *k == 2048
3329 && std::env::var_os("VK_MMQ_Q4K").is_some() =>
3330 {
3331 let bank = qtensor.vulkan_moe_bank_split(d, 1, *n, *k)?;
3332 let (xq, xsq, xsum) = d.quantize_act_q8(xv, m, *k)?;
3333 d.mmq_q4k_gpu(&xq, &xsq, &xsum, bank.as_ref(), m, *n)?
3334 }
3335 GgmlDType::Q4K => d.matmul_q4k_gpu(wq, xv, m, *n, *k)?,
3336 GgmlDType::Q5K => d.matmul_q5k_gpu(wq, xv, m, *n, *k)?,
3337 GgmlDType::Q6K => d.matmul_q6k_gpu(wq, xv, m, *n, *k)?,
3338 other => crate::bail!("VulkanQuant: no native matmul for {other:?}"),
3339 }
3340 };
3341 let mut dims = xs.dims().to_vec();
3342 let last = dims.len() - 1;
3343 dims[last] = *n;
3344 Ok(crate::tensor::from_storage(
3345 crate::Storage::Vulkan(y),
3346 dims,
3347 crate::op::BackpropOp::none(),
3348 false,
3349 ))
3350 } else {
3351 let w = qtensor.dequantize(&xs.device())?;
3356 let w = match *xs.dims() {
3357 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3358 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3359 _ => w.t()?,
3360 };
3361 xs.matmul(&w)
3362 }
3363 }
3364 #[cfg(feature = "wgpu")]
3365 Self::WgpuQuant {
3366 qtensor,
3367 wq,
3368 dtype,
3369 n,
3370 k,
3371 } => {
3372 let vdev = qtensor.device();
3376 let xs_on_vdev = if xs.device().same_device(&vdev) {
3377 None
3378 } else {
3379 Some(xs.to_device(&vdev)?)
3380 };
3381 let xs = xs_on_vdev.as_ref().unwrap_or(xs);
3382 let rows: usize = xs.elem_count() / *k;
3383 if rows == 1 {
3384 let xs = xs.contiguous()?;
3387 let d = match xs.device() {
3388 Device::Wgpu(d) => d,
3389 _ => crate::bail!("WgpuQuant input not on wgpu"),
3390 };
3391 let y = {
3392 let (store, _) = xs.storage_and_layout();
3393 let xv = match &*store {
3394 crate::Storage::Wgpu(v) => v,
3395 _ => crate::bail!("WgpuQuant expected wgpu storage"),
3396 };
3397 match dtype {
3398 GgmlDType::Q4_0 => d.matvec_q4_0_gpu(wq, xv, *n, *k)?,
3399 GgmlDType::Q8_0 => d.matvec_q8_0_gpu(wq, xv, *n, *k)?,
3400 GgmlDType::Q4K => d.matvec_q4k_gpu(wq, xv, *n, *k)?,
3401 other => crate::bail!("WgpuQuant: no native matvec for {other:?}"),
3402 }
3403 };
3404 let mut dims = xs.dims().to_vec();
3405 let last = dims.len() - 1;
3406 dims[last] = *n;
3407 Ok(crate::tensor::from_storage(
3408 crate::Storage::Wgpu(y),
3409 dims,
3410 crate::op::BackpropOp::none(),
3411 false,
3412 ))
3413 } else {
3414 let w = qtensor.dequantize(&xs.device())?;
3416 let w = match *xs.dims() {
3417 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
3418 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
3419 _ => w.t()?,
3420 };
3421 xs.matmul(&w)
3422 }
3423 }
3424 Self::QTensor(t) => xs.apply_op1_no_bwd(t.as_ref()),
3425 Self::Tensor(w) => dense_matmul(xs, w),
3426 Self::TensorF16(w) => {
3427 let in_dtype = xs.dtype();
3428 dense_matmul(&xs.to_dtype(DType::F16)?, w)?.to_dtype(in_dtype)
3429 }
3430 }
3431 }
3432}
3433
3434#[cfg(test)]
3435mod tests {
3436 use super::GgmlDType;
3437
3438 #[test]
3439 fn block_align_divides_type_size() {
3440 let dtypes: Vec<GgmlDType> = (0..1024u32)
3443 .filter_map(|id| GgmlDType::from_u32(id).ok())
3444 .collect();
3445 for dtype in [
3446 GgmlDType::F32,
3447 GgmlDType::BF16,
3448 GgmlDType::I32,
3449 GgmlDType::NVFP4,
3450 ] {
3451 assert!(dtypes.contains(&dtype), "{dtype:?} has no GGML id");
3452 }
3453 for dtype in dtypes {
3454 assert_eq!(
3455 GgmlDType::from_u32(dtype.to_u32()).unwrap(),
3456 dtype,
3457 "{dtype:?} does not survive a to_u32/from_u32 round trip"
3458 );
3459 let (size, align) = (dtype.type_size(), dtype.block_align());
3460 assert!(align.is_power_of_two(), "{dtype:?}: align {align}");
3461 assert_eq!(
3462 size % align,
3463 0,
3464 "{dtype:?}: type_size {size} block_align {align}"
3465 );
3466 }
3467 }
3468}