1use crate::{
2 backend::BackendStorage, CpuStorage, DType, Device, Result, Shape, Storage, Tensor, D,
3};
4use iq_quants::*;
5use k_quants::*;
6use std::borrow::Cow;
7
8#[cfg(target_feature = "avx2")]
9pub mod avx;
10mod dummy_cuda;
11mod dummy_metal;
12pub mod ggml_file;
13pub mod gguf_file;
14mod iq_grids;
15pub mod iq_quants;
16pub mod imatrix_file;
17pub mod k_quants;
18#[cfg(feature = "metal")]
19pub mod metal;
20#[cfg(not(target_arch = "wasm32"))]
21pub mod tokenizer;
22#[cfg(not(feature = "metal"))]
23mod metal {
24 pub use super::dummy_metal::*;
25}
26#[cfg(feature = "cuda")]
27pub mod cuda;
28#[cfg(feature = "cuda")]
29pub mod fast_mmq;
30#[cfg(feature = "cuda")]
31pub mod fast_mmvq;
32#[cfg(not(feature = "cuda"))]
33mod cuda {
34 pub use super::dummy_cuda::*;
35}
36
37#[cfg(target_feature = "neon")]
38pub mod neon;
39#[cfg(target_feature = "simd128")]
40pub mod simd128;
41pub mod utils;
42pub mod quant_format;
45use half::{bf16, f16};
46
47pub use k_quants::GgmlType;
48
49fn as_t_slice<T>(data: &[u8]) -> &[T] {
53 let size = std::mem::size_of::<T>();
54 assert_eq!(
55 data.len() % size,
56 0,
57 "Data length must be a multiple of T's size"
58 );
59 let ptr = data.as_ptr();
60 assert_eq!(
61 (ptr as usize) % std::mem::align_of::<T>(),
62 0,
63 "Data pointer must be aligned to T's alignment"
64 );
65 unsafe { std::slice::from_raw_parts(ptr as *const T, data.len() / size) }
66}
67
68pub struct QTensor {
69 storage: QStorage,
70 shape: Shape,
71}
72
73impl Device {
74 fn qzeros(&self, elem_count: usize, dtype: GgmlDType) -> Result<QStorage> {
75 match self {
76 Device::Cpu => {
77 let storage = dtype.cpu_zeros(elem_count);
78 Ok(QStorage::Cpu(storage))
79 }
80 Device::Metal(metal) => {
81 let storage = metal::QMetalStorage::zeros(metal, elem_count, dtype)?;
82 Ok(QStorage::Metal(storage))
83 }
84 Device::Cuda(cuda) => {
85 let storage = cuda::QCudaStorage::zeros(cuda, elem_count, dtype)?;
86 Ok(QStorage::Cuda(storage))
87 }
88 #[cfg(feature = "rocm")]
89 Device::Rocm(d) => {
90 let storage = dtype.cpu_zeros(elem_count);
93 Ok(QStorage::Rocm(storage, d.clone()))
94 }
95 #[cfg(feature = "vulkan")]
96 Device::Vulkan(d) => {
97 let storage = dtype.cpu_zeros(elem_count);
100 Ok(QStorage::Vulkan(storage, d.clone()))
101 }
102 #[cfg(feature = "wgpu")]
103 Device::Wgpu(d) => {
104 let storage = dtype.cpu_zeros(elem_count);
107 Ok(QStorage::Wgpu(storage, d.clone()))
108 }
109 }
110 }
111}
112
113pub enum QStorage {
114 Cpu(Box<dyn QuantizedType>),
115 Metal(metal::QMetalStorage),
116 Cuda(cuda::QCudaStorage),
117 #[cfg(feature = "rocm")]
121 Rocm(Box<dyn QuantizedType>, crate::RocmDevice),
122 #[cfg(feature = "vulkan")]
126 Vulkan(Box<dyn QuantizedType>, crate::VulkanDevice),
127 #[cfg(feature = "wgpu")]
131 Wgpu(Box<dyn QuantizedType>, crate::WgpuDevice),
132}
133
134impl QStorage {
135 pub fn from_data(data: Cow<'_, [u8]>, device: &Device, dtype: GgmlDType) -> Result<Self> {
136 match device {
137 Device::Cpu => Ok(Self::Cpu(dtype.from_data(data))),
138 Device::Metal(d) => match dtype {
139 GgmlDType::F32 => metal::load_quantized(d, as_t_slice::<f32>(&data)),
140 GgmlDType::F16 => metal::load_quantized(d, as_t_slice::<f16>(&data)),
141 GgmlDType::Q4_0 => metal::load_quantized(d, as_t_slice::<BlockQ4_0>(&data)),
142 GgmlDType::Q4_1 => metal::load_quantized(d, as_t_slice::<BlockQ4_1>(&data)),
143 GgmlDType::Q5_0 => metal::load_quantized(d, as_t_slice::<BlockQ5_0>(&data)),
144 GgmlDType::Q5_1 => metal::load_quantized(d, as_t_slice::<BlockQ5_1>(&data)),
145 GgmlDType::Q8_0 => metal::load_quantized(d, as_t_slice::<BlockQ8_0>(&data)),
146 GgmlDType::Q8_1 => metal::load_quantized(d, as_t_slice::<BlockQ8_1>(&data)),
147 GgmlDType::Q2K => metal::load_quantized(d, as_t_slice::<BlockQ2K>(&data)),
148 GgmlDType::Q3K => metal::load_quantized(d, as_t_slice::<BlockQ3K>(&data)),
149 GgmlDType::Q4K => metal::load_quantized(d, as_t_slice::<BlockQ4K>(&data)),
150 GgmlDType::Q5K => metal::load_quantized(d, as_t_slice::<BlockQ5K>(&data)),
151 GgmlDType::Q6K => metal::load_quantized(d, as_t_slice::<BlockQ6K>(&data)),
152 GgmlDType::Q8K => metal::load_quantized(d, as_t_slice::<BlockQ8K>(&data)),
153 GgmlDType::IQ4_NL => metal::load_quantized(d, as_t_slice::<BlockIQ4nl>(&data)),
154 GgmlDType::IQ4_XS => metal::load_quantized(d, as_t_slice::<BlockIQ4xs>(&data)),
155 GgmlDType::MXFP4 => metal::load_quantized(d, as_t_slice::<BlockMXFP4>(&data)),
156 GgmlDType::BF16 => metal::load_quantized(d, as_t_slice::<bf16>(&data)),
157 other => crate::bail!("{other:?} is not supported on the Metal backend"),
159 },
160 Device::Cuda(d) => match dtype {
161 GgmlDType::F32 => cuda::load_quantized(d, as_t_slice::<f32>(&data)),
162 GgmlDType::F16 => cuda::load_quantized(d, as_t_slice::<f16>(&data)),
163 GgmlDType::Q4_0 => cuda::load_quantized(d, as_t_slice::<BlockQ4_0>(&data)),
164 GgmlDType::Q4_1 => cuda::load_quantized(d, as_t_slice::<BlockQ4_1>(&data)),
165 GgmlDType::Q5_0 => cuda::load_quantized(d, as_t_slice::<BlockQ5_0>(&data)),
166 GgmlDType::Q5_1 => cuda::load_quantized(d, as_t_slice::<BlockQ5_1>(&data)),
167 GgmlDType::Q8_0 => cuda::load_quantized(d, as_t_slice::<BlockQ8_0>(&data)),
168 GgmlDType::Q8_1 => cuda::load_quantized(d, as_t_slice::<BlockQ8_1>(&data)),
169 GgmlDType::Q2K => cuda::load_quantized(d, as_t_slice::<BlockQ2K>(&data)),
170 GgmlDType::Q3K => cuda::load_quantized(d, as_t_slice::<BlockQ3K>(&data)),
171 GgmlDType::Q4K => cuda::load_quantized(d, as_t_slice::<BlockQ4K>(&data)),
172 GgmlDType::Q5K => cuda::load_quantized(d, as_t_slice::<BlockQ5K>(&data)),
173 GgmlDType::Q6K => cuda::load_quantized(d, as_t_slice::<BlockQ6K>(&data)),
174 GgmlDType::Q8K => cuda::load_quantized(d, as_t_slice::<BlockQ8K>(&data)),
175 GgmlDType::IQ4_NL => cuda::load_quantized(d, as_t_slice::<BlockIQ4nl>(&data)),
176 GgmlDType::IQ4_XS => cuda::load_quantized(d, as_t_slice::<BlockIQ4xs>(&data)),
177 GgmlDType::MXFP4 => cuda::load_quantized(d, as_t_slice::<BlockMXFP4>(&data)),
178 GgmlDType::BF16 => cuda::load_quantized(d, as_t_slice::<bf16>(&data)),
179 GgmlDType::IQ2_XXS => cuda::load_quantized(d, as_t_slice::<BlockIQ2xxs>(&data)),
187 GgmlDType::IQ2_XS => cuda::load_quantized(d, as_t_slice::<BlockIQ2xs>(&data)),
188 GgmlDType::IQ2_S => cuda::load_quantized(d, as_t_slice::<BlockIQ2s>(&data)),
189 GgmlDType::IQ3_XXS => cuda::load_quantized(d, as_t_slice::<BlockIQ3xxs>(&data)),
190 GgmlDType::IQ3_S => cuda::load_quantized(d, as_t_slice::<BlockIQ3s>(&data)),
191 GgmlDType::IQ1_S => cuda::load_quantized(d, as_t_slice::<BlockIQ1s>(&data)),
192 GgmlDType::IQ1_M => cuda::load_quantized(d, as_t_slice::<BlockIQ1m>(&data)),
193 GgmlDType::TQ1_0 => cuda::load_quantized(d, as_t_slice::<BlockTQ1_0>(&data)),
194 GgmlDType::TQ2_0 => cuda::load_quantized(d, as_t_slice::<BlockTQ2_0>(&data)),
195 GgmlDType::NVFP4 => cuda::load_quantized(d, as_t_slice::<BlockNVFP4>(&data)),
196 GgmlDType::Q1_0 => cuda::load_quantized(d, as_t_slice::<BlockQ1_0>(&data)),
197 },
198 #[cfg(feature = "rocm")]
199 Device::Rocm(d) => Ok(Self::Rocm(dtype.from_data(data), d.clone())),
200 #[cfg(feature = "vulkan")]
201 Device::Vulkan(d) => Ok(Self::Vulkan(dtype.from_data(data), d.clone())),
202 #[cfg(feature = "wgpu")]
203 Device::Wgpu(d) => Ok(Self::Wgpu(dtype.from_data(data), d.clone())),
204 }
205 }
206
207 fn block_size(&self) -> usize {
208 match self {
209 QStorage::Cpu(storage) => storage.block_size(),
210 QStorage::Metal(storage) => storage.dtype().block_size(),
211 QStorage::Cuda(storage) => storage.dtype().block_size(),
212 #[cfg(feature = "rocm")]
213 QStorage::Rocm(storage, _) => storage.block_size(),
214 #[cfg(feature = "vulkan")]
215 QStorage::Vulkan(storage, _) => storage.block_size(),
216 #[cfg(feature = "wgpu")]
217 QStorage::Wgpu(storage, _) => storage.block_size(),
218 }
219 }
220
221 fn dtype(&self) -> GgmlDType {
222 match self {
223 QStorage::Cpu(storage) => storage.dtype(),
224 QStorage::Metal(storage) => storage.dtype(),
225 QStorage::Cuda(storage) => storage.dtype(),
226 #[cfg(feature = "rocm")]
227 QStorage::Rocm(storage, _) => storage.dtype(),
228 #[cfg(feature = "vulkan")]
229 QStorage::Vulkan(storage, _) => storage.dtype(),
230 #[cfg(feature = "wgpu")]
231 QStorage::Wgpu(storage, _) => storage.dtype(),
232 }
233 }
234
235 fn device(&self) -> Device {
236 match self {
237 QStorage::Cpu(_storage) => Device::Cpu,
238 QStorage::Metal(storage) => Device::Metal(storage.device().clone()),
239 QStorage::Cuda(storage) => Device::Cuda(storage.device().clone()),
240 #[cfg(feature = "rocm")]
241 QStorage::Rocm(_storage, device) => Device::Rocm(device.clone()),
242 #[cfg(feature = "vulkan")]
243 QStorage::Vulkan(_storage, device) => Device::Vulkan(device.clone()),
244 #[cfg(feature = "wgpu")]
245 QStorage::Wgpu(_storage, device) => Device::Wgpu(device.clone()),
246 }
247 }
248
249 fn size_in_bytes(&self) -> usize {
250 match self {
251 QStorage::Cpu(storage) => storage.storage_size_in_bytes(),
252 QStorage::Metal(storage) => storage.storage_size_in_bytes(),
253 QStorage::Cuda(storage) => storage.storage_size_in_bytes(),
254 #[cfg(feature = "rocm")]
255 QStorage::Rocm(storage, _) => storage.storage_size_in_bytes(),
256 #[cfg(feature = "vulkan")]
257 QStorage::Vulkan(storage, _) => storage.storage_size_in_bytes(),
258 #[cfg(feature = "wgpu")]
259 QStorage::Wgpu(storage, _) => storage.storage_size_in_bytes(),
260 }
261 }
262
263 fn quantize(&mut self, src: &Storage) -> Result<()> {
264 match (self, src) {
265 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
266 storage.from_float(src.as_slice::<f32>()?);
267 }
268 (QStorage::Metal(storage), Storage::Metal(src)) => storage.quantize(src)?,
269 (QStorage::Cuda(storage), Storage::Cuda(src)) => storage.quantize(src)?,
270 _ => crate::bail!("Invalid quantize storage locations do not match"),
271 }
272 Ok(())
273 }
274
275 fn quantize_imatrix(
276 &mut self,
277 src: &Storage,
278 imatrix_weights: &[f32],
279 n_per_row: usize,
280 ) -> Result<()> {
281 match (self, src) {
282 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
283 storage.from_float_imatrix(src.as_slice::<f32>()?, imatrix_weights, n_per_row);
284 }
285 (QStorage::Metal(storage), Storage::Metal(src)) => {
286 storage.quantize_imatrix(src, imatrix_weights, n_per_row)?
287 }
288 (QStorage::Cuda(storage), Storage::Cuda(src)) => {
289 storage.quantize_imatrix(src, imatrix_weights, n_per_row)?
290 }
291 _ => crate::bail!("Invalid quantize storage locations do not match"),
292 }
293 Ok(())
294 }
295
296 fn quantize_onto(&mut self, src: &Storage) -> Result<()> {
297 match (self, src) {
298 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
299 storage.from_float(src.as_slice::<f32>()?);
300 }
301 (QStorage::Metal(storage), Storage::Cpu(src)) => storage.quantize_onto(src)?,
302 (QStorage::Cuda(storage), Storage::Cpu(src)) => storage.quantize_onto(src)?,
303 _ => crate::bail!("Invalid quantize source storage locations: not on cpu"),
304 }
305 Ok(())
306 }
307
308 fn quantize_imatrix_onto(
309 &mut self,
310 src: &Storage,
311 imatrix_weights: &[f32],
312 n_per_row: usize,
313 ) -> Result<()> {
314 match (self, src) {
315 (QStorage::Cpu(storage), Storage::Cpu(src)) => {
316 storage.from_float_imatrix(src.as_slice::<f32>()?, imatrix_weights, n_per_row);
317 }
318 (QStorage::Metal(storage), Storage::Cpu(src)) => {
319 storage.quantize_imatrix_onto(src, imatrix_weights, n_per_row)?
320 }
321 (QStorage::Cuda(storage), Storage::Cpu(src)) => {
322 storage.quantize_imatrix_onto(src, imatrix_weights, n_per_row)?
323 }
324 _ => crate::bail!("Invalid quantize storage locations do not match"),
325 }
326 Ok(())
327 }
328
329 fn dequantize(&self, elem_count: usize) -> Result<Storage> {
330 match self {
331 QStorage::Cpu(storage) => Ok(Storage::Cpu(storage.dequantize(elem_count)?)),
332 QStorage::Metal(storage) => Ok(Storage::Metal(storage.dequantize(elem_count)?)),
333 QStorage::Cuda(storage) => Ok(Storage::Cuda(storage.dequantize(elem_count)?)),
334 #[cfg(feature = "rocm")]
335 QStorage::Rocm(storage, device) => {
336 use crate::backend::BackendDevice;
338 let cpu = storage.dequantize(elem_count)?;
339 Ok(Storage::Rocm(device.storage_from_cpu_storage(&cpu)?))
340 }
341 #[cfg(feature = "vulkan")]
342 QStorage::Vulkan(storage, device) => {
343 let cpu = storage.dequantize(elem_count)?;
345 Ok(Storage::Vulkan(device.upload_f32(cpu.as_slice::<f32>()?)?))
346 }
347 #[cfg(feature = "wgpu")]
348 QStorage::Wgpu(storage, device) => {
349 let cpu = storage.dequantize(elem_count)?;
351 Ok(Storage::Wgpu(device.upload_f32(cpu.as_slice::<f32>()?)?))
352 }
353 }
354 }
355
356 fn data(&self) -> Result<Cow<'_, [u8]>> {
357 match self {
358 QStorage::Cpu(storage) => {
359 let data_ptr = storage.as_ptr();
360 let size_in_bytes = storage.storage_size_in_bytes();
361 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
362 Ok(Cow::from(data))
363 }
364 QStorage::Cuda(storage) => Ok(Cow::from(storage.data()?)),
365 QStorage::Metal(storage) => Ok(Cow::from(storage.data()?)),
366 #[cfg(feature = "rocm")]
367 QStorage::Rocm(storage, _) => {
368 let data_ptr = storage.as_ptr();
369 let size_in_bytes = storage.storage_size_in_bytes();
370 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
371 Ok(Cow::from(data))
372 }
373 #[cfg(feature = "vulkan")]
374 QStorage::Vulkan(storage, _) => {
375 let data_ptr = storage.as_ptr();
376 let size_in_bytes = storage.storage_size_in_bytes();
377 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
378 Ok(Cow::from(data))
379 }
380 #[cfg(feature = "wgpu")]
381 QStorage::Wgpu(storage, _) => {
382 let data_ptr = storage.as_ptr();
383 let size_in_bytes = storage.storage_size_in_bytes();
384 let data = unsafe { std::slice::from_raw_parts(data_ptr, size_in_bytes) };
385 Ok(Cow::from(data))
386 }
387 }
388 }
389
390 pub fn device_ptr(&self) -> Result<*const u8> {
391 match self {
392 QStorage::Cuda(storage) => storage.device_ptr(),
393 #[cfg(feature = "rocm")]
394 QStorage::Rocm(..) => crate::bail!("not implemented"),
395 #[cfg(feature = "vulkan")]
396 QStorage::Vulkan(..) => crate::bail!("not implemented"),
397 #[cfg(feature = "wgpu")]
398 QStorage::Wgpu(..) => crate::bail!("not implemented"),
399 QStorage::Metal(_) | QStorage::Cpu(_) => {
400 crate::bail!("not implemented");
401 }
402 }
403 }
404
405 #[cfg(feature = "cuda")]
406 pub fn device_ptr_with_guard<'a>(
407 &'a self,
408 stream: &'a crate::cuda_backend::cudarc::driver::CudaStream,
409 ) -> Result<(
410 *const u8,
411 crate::cuda_backend::cudarc::driver::SyncOnDrop<'a>,
412 )> {
413 match self {
414 QStorage::Cuda(storage) => storage.device_ptr_with_guard(stream),
415 QStorage::Metal(_) | QStorage::Cpu(_) => {
416 crate::bail!("not implemented");
417 }
418 }
419 }
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
423pub enum GgmlDType {
424 F32,
425 F16,
426 BF16,
427 Q4_0,
428 Q4_1,
429 Q5_0,
430 Q5_1,
431 Q8_0,
432 Q8_1,
433 Q2K,
434 Q3K,
435 Q4K,
436 Q5K,
437 Q6K,
438 Q8K,
439 #[allow(non_camel_case_types)]
440 IQ4_NL,
441 #[allow(non_camel_case_types)]
442 IQ4_XS,
443 MXFP4,
445 #[allow(non_camel_case_types)]
447 IQ2_XXS,
448 #[allow(non_camel_case_types)]
449 IQ2_XS,
450 #[allow(non_camel_case_types)]
451 IQ3_XXS,
452 #[allow(non_camel_case_types)]
453 IQ1_S,
454 #[allow(non_camel_case_types)]
455 IQ3_S,
456 #[allow(non_camel_case_types)]
457 IQ2_S,
458 #[allow(non_camel_case_types)]
459 IQ1_M,
460 TQ1_0,
461 TQ2_0,
462 NVFP4,
463 Q1_0,
464}
465
466use crate::for_each_quant;
478
479macro_rules! gen_from_u32 {
480 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
481 pub(crate) fn from_u32(u: u32) -> Result<Self> {
482 let dtype = match u {
483 0 => Self::F32,
484 1 => Self::F16,
485 30 => Self::BF16,
486 $( $id => Self::$v, )+
487 _ => crate::bail!("unknown dtype for tensor {u}"),
488 };
489 Ok(dtype)
490 }
491 };
492}
493
494macro_rules! gen_to_u32 {
495 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
496 pub fn to_u32(self) -> u32 {
500 match self {
501 Self::F32 => 0,
502 Self::F16 => 1,
503 Self::BF16 => 30,
504 $( Self::$v => $id, )+
505 }
506 }
507 };
508}
509
510macro_rules! gen_cpu_zeros {
511 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
512 pub fn cpu_zeros(&self, elem_count: usize) -> Box<dyn QuantizedType> {
514 match self {
515 Self::F32 => Box::new(vec![f32::zeros(); elem_count]),
516 Self::F16 => Box::new(vec![f16::zeros(); elem_count]),
517 Self::BF16 => Box::new(vec![bf16::zeros(); elem_count]),
518 $( Self::$v => Box::new(vec![<$b>::zeros(); elem_count / <$b>::BLCK_SIZE]), )+
519 }
520 }
521 };
522}
523
524macro_rules! gen_from_data {
525 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
526 pub fn from_data(&self, data: Cow<'_, [u8]>) -> Box<dyn QuantizedType> {
527 match self {
528 Self::F32 => Box::new(as_t_slice::<f32>(&data).to_vec()),
529 Self::F16 => Box::new(as_t_slice::<f16>(&data).to_vec()),
530 Self::BF16 => Box::new(as_t_slice::<bf16>(&data).to_vec()),
531 $( Self::$v => Box::new(as_t_slice::<$b>(&data).to_vec()), )+
532 }
533 }
534 };
535}
536
537macro_rules! gen_type_size {
538 ($($v:ident => $b:ident @ $id:literal),+ $(,)?) => {
539 pub fn type_size(&self) -> usize {
541 use k_quants::*;
542 match self {
543 Self::F32 => 4,
544 Self::F16 | Self::BF16 => 2,
545 $( Self::$v => std::mem::size_of::<$b>(), )+
546 }
547 }
548 };
549}
550
551impl GgmlDType {
552 for_each_quant!(gen_from_u32);
553 for_each_quant!(gen_to_u32);
554 for_each_quant!(gen_cpu_zeros);
555 for_each_quant!(gen_from_data);
556 for_each_quant!(gen_type_size);
557
558 pub fn block_size(&self) -> usize {
560 match self {
561 Self::F32 => 1,
562 Self::F16 | Self::BF16 => 1,
563 Self::Q4_0 => k_quants::QK4_0,
564 Self::Q4_1 => k_quants::QK4_1,
565 Self::Q5_0 => k_quants::QK5_0,
566 Self::Q5_1 => k_quants::QK5_1,
567 Self::Q8_0 => k_quants::QK8_0,
568 Self::Q8_1 => k_quants::QK8_1,
569 Self::IQ4_NL => k_quants::QK4_NL,
570 Self::MXFP4 => k_quants::QK_MXFP4,
571 Self::Q1_0 => iq_quants::QK1_0,
572 Self::NVFP4 => iq_quants::QK_NVFP4,
573 Self::Q2K
574 | Self::Q3K
575 | Self::Q4K
576 | Self::Q5K
577 | Self::Q6K
578 | Self::Q8K
579 | Self::IQ4_XS
580 | Self::IQ2_XXS
581 | Self::IQ2_XS
582 | Self::IQ3_XXS
583 | Self::IQ1_S
584 | Self::IQ3_S
585 | Self::IQ2_S
586 | Self::IQ1_M
587 | Self::TQ1_0
588 | Self::TQ2_0 => k_quants::QK_K,
589 }
590 }
591}
592
593pub trait QuantizedType: Send + Sync {
595 fn dtype(&self) -> GgmlDType;
596 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()>;
597 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()>;
598 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage>;
599 fn storage_size_in_bytes(&self) -> usize;
600 fn as_ptr(&self) -> *const u8;
601 fn block_size(&self) -> usize;
602 #[allow(clippy::wrong_self_convention)]
603 fn from_float(&mut self, xs: &[f32]);
604 #[allow(clippy::wrong_self_convention)]
605 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize);
606 fn size(&self) -> usize;
607}
608
609impl<T: k_quants::GgmlType + Send + Sync> QuantizedType for Vec<T> {
610 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()> {
611 k_quants::matmul(mkn, lhs, self.as_slice(), dst)
612 }
613 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()> {
614 k_quants::matmul_f16(mkn, lhs, self.as_slice(), dst)
615 }
616
617 fn size(&self) -> usize {
618 self.len() * core::mem::size_of::<T>()
619 }
620
621 fn from_float(&mut self, xs: &[f32]) {
622 T::from_float(xs, self)
623 }
624
625 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize) {
626 T::from_float_imatrix(xs, self, imatrix_weights, n_per_row)
627 }
628
629 fn dtype(&self) -> GgmlDType {
630 T::DTYPE
631 }
632
633 fn block_size(&self) -> usize {
634 T::BLCK_SIZE
635 }
636
637 fn dequantize(&self, elem_count: usize) -> Result<CpuStorage> {
638 let mut ys = vec![0.0f32; elem_count];
639 T::to_float(self.as_slice(), &mut ys);
640 Ok(CpuStorage::F32(ys))
641 }
642
643 fn storage_size_in_bytes(&self) -> usize {
644 self.len() * std::mem::size_of::<T>()
645 }
646
647 fn as_ptr(&self) -> *const u8 {
648 self.as_ptr() as *const u8
649 }
650}
651
652impl std::fmt::Debug for QTensor {
653 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
654 write!(f, "QTensor[{:?}; {:?}]", self.shape, self.dtype())
655 }
656}
657
658fn check_shape(shape: &Shape, block_size: usize) -> Result<()> {
659 let dims = shape.dims();
660 if dims.is_empty() {
661 crate::bail!("scalar tensor cannot be quantized {shape:?}")
662 }
663 if !dims[dims.len() - 1].is_multiple_of(block_size) {
664 crate::bail!(
665 "quantized tensor must have their last dim divisible by block size {shape:?} {}",
666 block_size
667 )
668 }
669 Ok(())
670}
671
672impl QTensor {
673 pub fn new<S: Into<Shape>>(storage: QStorage, shape: S) -> Result<Self> {
674 let shape = shape.into();
675 check_shape(&shape, storage.block_size())?;
676 Ok(Self { storage, shape })
677 }
678
679 pub fn quantize(src: &Tensor, dtype: GgmlDType) -> Result<Self> {
680 let shape = src.shape();
681 let block_size = dtype.block_size();
682 check_shape(shape, block_size)?;
683 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
684 let elem_count = shape.elem_count();
685 if !elem_count.is_multiple_of(block_size) {
686 crate::bail!(
687 "tensor size ({shape:?}) is not divisible by block size {}",
688 block_size
689 )
690 }
691 let mut storage = src.device().qzeros(elem_count, dtype)?;
692 storage.quantize(&src.storage())?;
693 Ok(Self {
694 storage,
695 shape: shape.clone(),
696 })
697 }
698
699 pub fn quantize_imatrix(
700 src: &Tensor,
701 imatrix_weights: &[f32],
702 dtype: GgmlDType,
703 ) -> Result<Self> {
704 let n_per_row = src.dim(D::Minus1)?;
707 if imatrix_weights.len() != n_per_row {
708 crate::bail!(
709 "imatrix weights must have the same length {} as the last dim of src {}",
710 imatrix_weights.len(),
711 src.dim(D::Minus1)?
712 );
713 }
714
715 let shape = src.shape();
716 let block_size = dtype.block_size();
717 check_shape(shape, block_size)?;
718 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
719 let elem_count = shape.elem_count();
720 if !elem_count.is_multiple_of(block_size) {
721 crate::bail!(
722 "tensor size ({shape:?}) is not divisible by block size {}",
723 block_size
724 );
725 }
726 let mut storage = src.device().qzeros(elem_count, dtype)?;
727 storage.quantize_imatrix(&src.storage(), imatrix_weights, n_per_row)?;
728 Ok(Self {
729 storage,
730 shape: shape.clone(),
731 })
732 }
733
734 pub fn quantize_imatrix_onto(
736 src: &Tensor,
737 imatrix_weights: &[f32],
738 dtype: GgmlDType,
739 dev: &Device,
740 ) -> Result<Self> {
741 if !src.device().is_cpu() {
742 crate::bail!(
743 "`quantize_onto` expects a `src` to be on the cpu, got {:?}.",
744 src.device()
745 )
746 }
747 let n_per_row = src.dim(D::Minus1)?;
750 if imatrix_weights.len() != n_per_row {
751 crate::bail!(
752 "imatrix weights must have the same length {} as the last dim of src {}",
753 imatrix_weights.len(),
754 src.dim(D::Minus1)?
755 );
756 }
757 let shape = src.shape();
758 let block_size = dtype.block_size();
759 check_shape(shape, block_size)?;
760 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
761 let elem_count = shape.elem_count();
762 if !elem_count.is_multiple_of(block_size) {
763 crate::bail!(
764 "tensor size ({shape:?}) is not divisible by block size {}",
765 block_size
766 )
767 }
768 let mut storage = dev.qzeros(elem_count, dtype)?;
770 storage.quantize_imatrix_onto(&src.storage(), imatrix_weights, n_per_row)?;
771 Ok(Self {
772 storage,
773 shape: shape.clone(),
774 })
775 }
776
777 pub fn quantize_onto(src: &Tensor, dtype: GgmlDType, dev: &Device) -> Result<Self> {
779 if !src.device().is_cpu() {
780 crate::bail!(
781 "`quantize_onto` expects a `src` to be on the cpu, got {:?}.",
782 src.device()
783 )
784 }
785 let shape = src.shape();
786 let block_size = dtype.block_size();
787 check_shape(shape, block_size)?;
788 let src = src.to_dtype(crate::DType::F32)?.flatten_all()?;
789 let elem_count = shape.elem_count();
790 if !elem_count.is_multiple_of(block_size) {
791 crate::bail!(
792 "tensor size ({shape:?}) is not divisible by block size {}",
793 block_size
794 )
795 }
796 let mut storage = dev.qzeros(elem_count, dtype)?;
798 storage.quantize_onto(&src.storage())?;
799 Ok(Self {
800 storage,
801 shape: shape.clone(),
802 })
803 }
804
805 pub fn dtype(&self) -> GgmlDType {
806 self.storage.dtype()
807 }
808
809 pub fn device(&self) -> Device {
810 self.storage.device()
811 }
812
813 pub fn rank(&self) -> usize {
814 self.shape.rank()
815 }
816
817 pub fn shape(&self) -> &Shape {
818 &self.shape
819 }
820
821 pub fn dequantize(&self, device: &Device) -> Result<Tensor> {
822 let storage = self.storage.dequantize(self.shape.elem_count())?;
823 let none = crate::op::BackpropOp::none();
824 crate::tensor::from_storage(storage, self.shape.clone(), none, false).to_device(device)
825 }
826
827 pub fn dequantize_f16(&self, device: &Device) -> Result<Tensor> {
828 match &self.storage {
831 QStorage::Cuda(s) => {
832 let s = s.dequantize_f16(self.shape.elem_count())?;
833 let none = crate::op::BackpropOp::none();
834 crate::tensor::from_storage(Storage::Cuda(s), self.shape.clone(), none, false)
835 .to_device(device)
836 }
837 _ => {
838 let s = self.dequantize(device)?.to_dtype(crate::DType::F16)?;
839 Ok(s)
840 }
841 }
842 }
843
844 pub fn storage_size_in_bytes(&self) -> usize {
845 self.storage.size_in_bytes()
846 }
847
848 pub fn data(&self) -> Result<Cow<'_, [u8]>> {
849 self.storage.data()
850 }
851
852 #[cfg(feature = "rocm")]
856 fn rocm_moe_bank(
857 &self,
858 dev: &crate::RocmDevice,
859 ) -> Result<std::sync::Arc<crate::RocmStorage>> {
860 use crate::backend::BackendDevice;
861 let bank = self.data()?;
862 let key = (bank.as_ref().as_ptr() as usize, bank.as_ref().len());
863 let cache = rocm_moe_bank_cache();
864 let mut guard = cache.lock().expect("moe bank cache lock");
865 if let Some(w) = guard.get(&key) {
866 Ok(w.clone())
867 } else {
868 let w = std::sync::Arc::new(dev.storage_from_slice(bank.as_ref())?);
869 guard.insert(key, w.clone());
870 Ok(w)
871 }
872 }
873
874 pub fn indexed_moe_forward(&self, x: &Tensor, ids: &Tensor) -> Result<Tensor> {
875 let ids = &ids.contiguous()?;
878 match &self.storage {
879 QStorage::Cuda(s) if cuda::QCudaStorage::supports_indexed_moe(s.dtype()) =>
885 {
886 match (&*x.storage(), &*ids.storage()) {
887 (Storage::Cuda(x_storage), Storage::Cuda(ids_storage)) => {
888 let (storage, out_shape) = s.indexed_moe_forward(
889 self.shape(),
890 x_storage,
891 x.layout(),
892 ids_storage,
893 ids.layout(),
894 )?;
895 Ok(crate::tensor::from_storage(
896 Storage::Cuda(storage),
897 out_shape,
898 crate::op::BackpropOp::none(),
899 false,
900 ))
901 }
902 _ => {
903 panic!("Non-cuda indexed_moe_forward is not implemented!");
904 }
905 }
906 },
907 #[cfg(feature = "cuda")]
913 QStorage::Cuda(s)
914 if cuda::QCudaStorage::supports_iquant_moe(s.dtype())
915 && !cuda::iq_dequant_fallback() =>
916 {
917 let out_dtype = x.dtype();
918 let (_e_cnt, n, k) = self.shape().dims3()?;
919 let (t, topk) = ids.dims2()?;
920 let nrows = t * topk;
921
922 if t > 1 && std::env::var("HANZO_MOE_QMMQ_FALLBACK").is_err() {
928 let x_f32 = x.to_dtype(crate::DType::F32)?.contiguous()?;
929 let ids_u32 = ids.to_dtype(crate::DType::U32)?.contiguous()?;
930 let (xs, _) = x_f32.storage_and_layout();
931 let xc = match &*xs {
932 Storage::Cuda(c) => c,
933 _ => crate::bail!("cuda i-quant MoE: x not on cuda after contiguous()"),
934 };
935 let (ids_s, _) = ids_u32.storage_and_layout();
936 let idc = match &*ids_s {
937 Storage::Cuda(c) => c,
938 _ => crate::bail!("cuda i-quant MoE: ids not on cuda"),
939 };
940 if let Some((st, sh)) = s.moe_iquant_qmmq(
941 self.shape(),
942 xc.as_cuda_slice::<f32>()?,
943 x.shape(),
944 &idc.as_cuda_slice::<u32>()?.slice(0..),
945 ids.shape(),
946 )? {
947 return crate::tensor::from_storage(
948 Storage::Cuda(st),
949 sh,
950 crate::op::BackpropOp::none(),
951 false,
952 )
953 .to_dtype(out_dtype);
954 }
955 }
956
957 let sdim = x.dim(1)?; let x_exp = if sdim == topk {
962 x.clone()
963 } else {
964 x.broadcast_as((t, topk, k))?
965 };
966 let x_flat = x_exp
967 .reshape((nrows, k))?
968 .to_dtype(crate::DType::F32)?
969 .contiguous()?;
970 let ids_flat = ids.reshape((nrows,))?.to_dtype(crate::DType::U32)?.contiguous()?;
971 let (xstore, _) = x_flat.storage_and_layout();
972 let xc = match &*xstore {
973 Storage::Cuda(c) => c,
974 _ => crate::bail!("cuda i-quant MoE: x not on cuda after contiguous()"),
975 };
976 let (idstore, _) = ids_flat.storage_and_layout();
977 let idc = match &*idstore {
978 Storage::Cuda(c) => c,
979 _ => crate::bail!("cuda i-quant MoE: ids not on cuda"),
980 };
981 let y = s.moe_iquant_dp4a(
982 &xc.as_cuda_slice::<f32>()?.slice(0..),
983 &idc.as_cuda_slice::<u32>()?.slice(0..),
984 nrows,
985 n,
986 k,
987 )?;
988 let out = crate::tensor::from_storage(
989 Storage::Cuda(y),
990 (nrows, n),
991 crate::op::BackpropOp::none(),
992 false,
993 );
994 out.reshape((t, topk, n))?.to_dtype(out_dtype)
995 }
996 #[cfg(feature = "vulkan")]
1002 QStorage::Vulkan(_, vk_dev)
1003 if matches!(
1004 self.storage.dtype(),
1005 GgmlDType::Q4_0 | GgmlDType::Q8_0 | GgmlDType::Q4K
1006 ) =>
1007 {
1008 let out_dtype = x.dtype();
1009 let (e_cnt, n, k) = self.shape().dims3()?;
1010 let (t, topk) = ids.dims2()?;
1011 let s = x.dim(1)?; let x_exp = if s == topk {
1013 x.clone()
1014 } else {
1015 x.broadcast_as((t, topk, k))?
1016 };
1017 let nrows = t * topk;
1019 let x_flat = x_exp
1020 .reshape((nrows, k))?
1021 .to_dtype(crate::DType::F32)?
1022 .contiguous()?;
1023 let ids_vec = ids
1024 .reshape((nrows,))?
1025 .to_dtype(crate::DType::U32)?
1026 .to_vec1::<u32>()?;
1027 if let Some(&bad) = ids_vec.iter().find(|&&e| e as usize >= e_cnt) {
1029 crate::bail!("indexed_moe_forward: expert id {bad} >= num_experts {e_cnt}");
1030 }
1031
1032 let kernel = match self.storage.dtype() {
1033 GgmlDType::Q4_0 => "moe_matvec_q4_0",
1034 GgmlDType::Q8_0 => "moe_matvec_q8_0",
1035 GgmlDType::Q4K => "moe_matvec_q4k",
1036 other => crate::bail!("vulkan MoE: no kernel for {other:?}"),
1037 };
1038 let bank = self.data()?; let wbank = match self.storage.dtype() {
1042 GgmlDType::Q8_0 => vk_dev.quantize_q8_blocks(&bank, e_cnt * n, k)?,
1043 _ => vk_dev.upload_qweight(&bank)?,
1044 };
1045 let ids_buf = vk_dev.upload_ids(&ids_vec)?;
1046 let y = {
1047 let (store, _) = x_flat.storage_and_layout();
1048 let xv = match &*store {
1049 Storage::Vulkan(v) => v,
1050 _ => crate::bail!("vulkan MoE: x not on vulkan after contiguous()"),
1051 };
1052 vk_dev.moe_matvec_gpu(kernel, &wbank, xv, &ids_buf, nrows, n, k)?
1053 };
1054 let out = crate::tensor::from_storage(
1055 Storage::Vulkan(y),
1056 (nrows, n),
1057 crate::op::BackpropOp::none(),
1058 false,
1059 );
1060 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1061 }
1062 #[cfg(feature = "wgpu")]
1066 QStorage::Wgpu(_, wgpu_dev)
1067 if matches!(
1068 self.storage.dtype(),
1069 GgmlDType::Q4_0 | GgmlDType::Q8_0 | GgmlDType::Q4K
1070 ) =>
1071 {
1072 let out_dtype = x.dtype();
1073 let (e_cnt, n, k) = self.shape().dims3()?;
1074 let (t, topk) = ids.dims2()?;
1075 let s = x.dim(1)?; let x_exp = if s == topk {
1077 x.clone()
1078 } else {
1079 x.broadcast_as((t, topk, k))?
1080 };
1081 let nrows = t * topk;
1082 let x_flat = x_exp
1083 .reshape((nrows, k))?
1084 .to_dtype(crate::DType::F32)?
1085 .contiguous()?;
1086 let ids_vec = ids
1087 .reshape((nrows,))?
1088 .to_dtype(crate::DType::U32)?
1089 .to_vec1::<u32>()?;
1090 if let Some(&bad) = ids_vec.iter().find(|&&e| e as usize >= e_cnt) {
1091 crate::bail!("indexed_moe_forward: expert id {bad} >= num_experts {e_cnt}");
1092 }
1093 let kernel = match self.storage.dtype() {
1094 GgmlDType::Q4_0 => "moe_matvec_q4_0",
1095 GgmlDType::Q8_0 => "moe_matvec_q8_0",
1096 GgmlDType::Q4K => "moe_matvec_q4k",
1097 other => crate::bail!("wgpu MoE: no kernel for {other:?}"),
1098 };
1099 let bank = self.data()?; let wbank = wgpu_dev.upload_qweight(&bank)?;
1101 let ids_buf = wgpu_dev.upload_ids(&ids_vec)?;
1102 let y = {
1103 let (store, _) = x_flat.storage_and_layout();
1104 let xv = match &*store {
1105 Storage::Wgpu(v) => v,
1106 _ => crate::bail!("wgpu MoE: x not on wgpu after contiguous()"),
1107 };
1108 wgpu_dev.moe_matvec_gpu(kernel, &wbank, xv, &ids_buf, nrows, n, k)?
1109 };
1110 let out = crate::tensor::from_storage(
1111 Storage::Wgpu(y),
1112 (nrows, n),
1113 crate::op::BackpropOp::none(),
1114 false,
1115 );
1116 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1117 }
1118 #[cfg(feature = "rocm")]
1123 QStorage::Rocm(_, rocm_dev)
1124 if crate::RocmQuantType::from_ggml(self.storage.dtype()).is_some() =>
1125 {
1126 let qt = crate::RocmQuantType::from_ggml(self.storage.dtype()).unwrap();
1127 let out_dtype = x.dtype();
1128 let (_e_cnt, n, k) = self.shape().dims3()?;
1131 let (t, topk) = ids.dims2()?;
1132 let s = x.dim(1)?; let x_exp = if s == topk {
1134 x.clone()
1135 } else {
1136 x.broadcast_as((t, topk, k))?
1137 };
1138 let nrows = t * topk;
1139 let use_qmmq =
1145 t > 1 && qt.qmmq_capable() && std::env::var("HANZO_MOE_QMMQ_FALLBACK").is_err();
1146 let x_flat = match x_exp.dtype() {
1147 DType::F16 | DType::F32 if use_qmmq => x_exp.reshape((nrows, k))?.contiguous()?,
1151 _ if use_qmmq => x_exp.reshape((nrows, k))?.to_dtype(DType::F16)?.contiguous()?,
1152 DType::BF16 | DType::F16 => x_exp.reshape((nrows, k))?.contiguous()?,
1153 DType::F32 if qt.dp4a_active() => x_exp.reshape((nrows, k))?.contiguous()?,
1157 _ => x_exp.reshape((nrows, k))?.to_dtype(DType::F16)?.contiguous()?,
1158 };
1159 let wbank = self.rocm_moe_bank(rocm_dev)?;
1160 let ids_u32 = ids
1167 .reshape((nrows,))?
1168 .to_dtype(crate::DType::U32)?
1169 .contiguous()?;
1170 let (store, _) = x_flat.storage_and_layout();
1171 let xr = match &*store {
1172 crate::Storage::Rocm(r) => r,
1173 _ => crate::bail!("rocm MoE: x not on rocm after contiguous()"),
1174 };
1175 let (idstore, _) = ids_u32.storage_and_layout();
1176 let idr = match &*idstore {
1177 crate::Storage::Rocm(r) => r,
1178 _ => crate::bail!("rocm MoE: ids not on rocm"),
1179 };
1180 let y = if use_qmmq {
1181 rocm_dev.moe_qmmq_quant(qt, wbank.as_ref(), xr, idr, nrows, n, k)?
1182 } else {
1183 rocm_dev.moe_matvec_quant(qt, wbank.as_ref(), xr, idr, nrows, n, k)?
1184 };
1185 let out = crate::tensor::from_storage(
1186 crate::Storage::Rocm(y),
1187 (nrows, n),
1188 crate::op::BackpropOp::none(),
1189 false,
1190 );
1191 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1192 }
1193 _ => {
1194 use crate::Module; use std::collections::HashMap;
1201 use std::sync::Arc;
1202 let device = x.device();
1203 let out_dtype = x.dtype();
1204 let (e_cnt, n, k) = self.shape().dims3()?;
1205 let (t, topk) = ids.dims2()?;
1206 let s = x.dim(1)?; let x_exp = if s == topk {
1208 x.clone()
1209 } else {
1210 x.broadcast_as((t, topk, k))?
1211 };
1212 let x_flat = x_exp
1213 .reshape((t * topk, k))?
1214 .to_dtype(crate::DType::F32)?
1215 .contiguous()?;
1216 let ids_flat = ids.reshape((t * topk,))?.to_dtype(crate::DType::U32)?;
1217 let ids_vec = ids_flat.to_vec1::<u32>()?;
1218 let mut groups: HashMap<u32, Vec<u32>> = HashMap::new();
1219 for (slot, eid) in ids_vec.iter().enumerate() {
1220 groups.entry(*eid).or_default().push(slot as u32);
1221 }
1222 let dtype = self.storage.dtype();
1223 let all_bytes = self.data()?;
1224 let expert_bytes = all_bytes.len() / e_cnt;
1225 let mut out_flat = Tensor::zeros((t * topk, n), crate::DType::F32, device)?;
1226 for (eid, slots) in groups.into_iter() {
1227 let off = eid as usize * expert_bytes;
1228 let qs = QStorage::from_data(
1229 std::borrow::Cow::Borrowed(&all_bytes[off..off + expert_bytes]),
1230 device,
1231 dtype,
1232 )?;
1233 let shape: crate::Shape = (n, k).into();
1234 let w_e = QTensor { storage: qs, shape };
1235 let qm = QMatMul::from_arc(Arc::new(w_e))?;
1236 let m = slots.len();
1237 let idx = Tensor::from_vec(slots, (m,), device)?;
1238 let x_e = x_flat.index_select(&idx, 0)?; let y_e = qm.forward(&x_e)?.to_dtype(crate::DType::F32)?; out_flat = out_flat.index_add(&idx, &y_e, 0)?;
1241 }
1242 out_flat.reshape((t, topk, n))?.to_dtype(out_dtype)
1243 }
1244 }
1245 }
1246
1247 pub fn device_ptr(&self) -> Result<*const u8> {
1248 match &self.storage {
1249 QStorage::Cuda(storage) => storage.device_ptr(),
1250 #[cfg(feature = "rocm")]
1251 QStorage::Rocm(..) => crate::bail!("not implemented"),
1252 #[cfg(feature = "vulkan")]
1253 QStorage::Vulkan(..) => crate::bail!("not implemented"),
1254 #[cfg(feature = "wgpu")]
1255 QStorage::Wgpu(..) => crate::bail!("not implemented"),
1256 QStorage::Metal(_) | QStorage::Cpu(_) => {
1257 crate::bail!("not implemented");
1258 }
1259 }
1260 }
1261
1262 #[cfg(feature = "cuda")]
1263 pub fn device_ptr_with_guard<'a>(
1264 &'a self,
1265 stream: &'a crate::cuda_backend::cudarc::driver::CudaStream,
1266 ) -> Result<(
1267 *const u8,
1268 crate::cuda_backend::cudarc::driver::SyncOnDrop<'a>,
1269 )> {
1270 self.storage.device_ptr_with_guard(stream)
1271 }
1272}
1273
1274#[derive(Clone, Debug)]
1275pub enum QMatMul {
1276 QTensor(std::sync::Arc<QTensor>),
1277 Tensor(Tensor),
1278 TensorF16(Tensor),
1279 #[cfg(feature = "vulkan")]
1285 VulkanQuant {
1286 qtensor: std::sync::Arc<QTensor>,
1287 wq: std::sync::Arc<crate::VulkanStorage>,
1288 dtype: GgmlDType,
1289 n: usize,
1290 k: usize,
1291 },
1292 #[cfg(feature = "wgpu")]
1296 WgpuQuant {
1297 qtensor: std::sync::Arc<QTensor>,
1298 wq: std::sync::Arc<crate::WgpuStorage>,
1299 dtype: GgmlDType,
1300 n: usize,
1301 k: usize,
1302 },
1303 #[cfg(feature = "rocm")]
1309 RocmQuant {
1310 qtensor: std::sync::Arc<QTensor>,
1311 wq: std::sync::Arc<crate::RocmStorage>,
1312 dtype: GgmlDType,
1313 n: usize,
1314 k: usize,
1315 },
1316}
1317
1318#[cfg(feature = "rocm")]
1323fn rocm_moe_bank_cache(
1324) -> &'static std::sync::Mutex<std::collections::HashMap<(usize, usize), std::sync::Arc<crate::RocmStorage>>>
1325{
1326 static CACHE: std::sync::OnceLock<
1327 std::sync::Mutex<std::collections::HashMap<(usize, usize), std::sync::Arc<crate::RocmStorage>>>,
1328 > = std::sync::OnceLock::new();
1329 CACHE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
1330}
1331
1332pub fn moe_combine(ys: &Tensor, scores: &Tensor) -> Result<Tensor> {
1338 let (t, topk, n) = ys.dims3()?;
1339 #[cfg(feature = "rocm")]
1340 if let Device::Rocm(dev) = ys.device() {
1341 if std::env::var("HANZO_MOE_COMBINE_FALLBACK").is_ok() {
1342 return ys.broadcast_mul(&scores.unsqueeze(D::Minus1)?)?.sum(D::Minus2);
1343 }
1344 let ys_c = ys.contiguous()?;
1345 let scores_c = scores.to_dtype(DType::F32)?.contiguous()?;
1346 let (ys_store, _) = ys_c.storage_and_layout();
1347 let yr = match &*ys_store {
1348 Storage::Rocm(r) => r,
1349 _ => crate::bail!("moe_combine: ys not on rocm after contiguous()"),
1350 };
1351 let (sc_store, _) = scores_c.storage_and_layout();
1352 let sr = match &*sc_store {
1353 Storage::Rocm(r) => r,
1354 _ => crate::bail!("moe_combine: scores not on rocm after contiguous()"),
1355 };
1356 let out = dev.moe_combine(yr, sr, t, topk, n)?;
1357 return Ok(crate::tensor::from_storage(
1358 Storage::Rocm(out),
1359 (t, n),
1360 crate::op::BackpropOp::none(),
1361 false,
1362 ));
1363 }
1364 ys.broadcast_mul(&scores.unsqueeze(D::Minus1)?)?.sum(D::Minus2)
1365}
1366
1367pub fn moe_route(logits: &Tensor, topk: usize, norm: bool) -> Result<(Tensor, Tensor)> {
1372 let (ntok, n_experts) = logits.dims2()?;
1373 #[cfg(feature = "rocm")]
1374 if let Device::Rocm(dev) = logits.device() {
1375 if std::env::var("HANZO_MOE_ROUTE_FALLBACK").is_err() {
1376 let logits_c = logits.to_dtype(DType::F32)?.contiguous()?;
1377 let (lg_store, _) = logits_c.storage_and_layout();
1378 let lr = match &*lg_store {
1379 Storage::Rocm(r) => r,
1380 _ => crate::bail!("moe_route: logits not on rocm after contiguous()"),
1381 };
1382 let (ids, w) = dev.moe_route(lr, ntok, n_experts, topk, norm)?;
1383 let ids_t = crate::tensor::from_storage(
1384 Storage::Rocm(ids),
1385 (ntok, topk),
1386 crate::op::BackpropOp::none(),
1387 false,
1388 );
1389 let w_t = crate::tensor::from_storage(
1390 Storage::Rocm(w),
1391 (ntok, topk),
1392 crate::op::BackpropOp::none(),
1393 false,
1394 );
1395 return Ok((ids_t, w_t));
1396 }
1397 }
1398 let lf = logits.to_dtype(DType::F32)?;
1399 let mx = lf.max_keepdim(D::Minus1)?;
1400 let e = lf.broadcast_sub(&mx)?.exp()?;
1401 let z = e.sum_keepdim(D::Minus1)?;
1402 let p = e.broadcast_div(&z)?;
1403 let (sv, si) = p.sort_last_dim(false)?;
1404 let ids = si.narrow(D::Minus1, 0, topk)?.contiguous()?;
1405 let mut w = sv.narrow(D::Minus1, 0, topk)?.contiguous()?;
1406 if norm {
1407 w = w.broadcast_div(&w.sum_keepdim(D::Minus1)?)?;
1408 }
1409 Ok((ids, w))
1410}
1411
1412pub fn moe_gate_up(
1420 x: &Tensor,
1421 ids: &Tensor,
1422 gate: &QMatMul,
1423 up: &QMatMul,
1424) -> Result<(Tensor, Tensor)> {
1425 #[cfg(feature = "rocm")]
1426 if std::env::var("HANZO_MOE_GATEUP_FALLBACK").is_err() {
1427 if let (QMatMul::QTensor(gq), QMatMul::QTensor(uq)) = (gate, up) {
1428 if let (QStorage::Rocm(_, dev), QStorage::Rocm(..)) = (&gq.storage, &uq.storage) {
1429 let dt = gq.storage.dtype();
1430 if dt == uq.storage.dtype() {
1431 if let Some(qt) = crate::RocmQuantType::from_ggml(dt) {
1432 let (_e, n, k) = gq.shape().dims3()?;
1433 let (t, topk) = ids.dims2()?;
1434 let use_qmmq = t > 1
1435 && qt.qmmq_capable()
1436 && std::env::var("HANZO_MOE_QMMQ_FALLBACK").is_err();
1437 if x.dim(1)? == 1 && !use_qmmq {
1438 let nrows = t * topk;
1439 let x_exp = x.broadcast_as((t, topk, k))?;
1440 let x_flat = match x_exp.dtype() {
1441 DType::BF16 | DType::F16 => x_exp.reshape((nrows, k))?.contiguous()?,
1442 DType::F32 if qt.dp4a_active() => {
1443 x_exp.reshape((nrows, k))?.contiguous()?
1444 }
1445 _ => {
1446 x_exp.reshape((nrows, k))?.to_dtype(DType::F16)?.contiguous()?
1447 }
1448 };
1449 let out_dtype = x.dtype();
1450 let ids_u32 =
1451 ids.reshape((nrows,))?.to_dtype(DType::U32)?.contiguous()?;
1452 let gwb = gq.rocm_moe_bank(dev)?;
1453 let uwb = uq.rocm_moe_bank(dev)?;
1454 let (xstore, _) = x_flat.storage_and_layout();
1455 let xr = match &*xstore {
1456 Storage::Rocm(r) => r,
1457 _ => crate::bail!("moe_gate_up: x not on rocm after contiguous()"),
1458 };
1459 let (idstore, _) = ids_u32.storage_and_layout();
1460 let idr = match &*idstore {
1461 Storage::Rocm(r) => r,
1462 _ => crate::bail!("moe_gate_up: ids not on rocm"),
1463 };
1464 let (gy, uy) = dev.moe_matvec_pair(
1465 qt,
1466 gwb.as_ref(),
1467 uwb.as_ref(),
1468 xr,
1469 idr,
1470 nrows,
1471 n,
1472 k,
1473 )?;
1474 let g = crate::tensor::from_storage(
1475 Storage::Rocm(gy),
1476 (nrows, n),
1477 crate::op::BackpropOp::none(),
1478 false,
1479 )
1480 .reshape((t, topk, n))?
1481 .to_dtype(out_dtype)?;
1482 let u = crate::tensor::from_storage(
1483 Storage::Rocm(uy),
1484 (nrows, n),
1485 crate::op::BackpropOp::none(),
1486 false,
1487 )
1488 .reshape((t, topk, n))?
1489 .to_dtype(out_dtype)?;
1490 return Ok((g, u));
1491 }
1492 }
1493 }
1494 }
1495 }
1496 }
1497 Ok((gate.indexed_moe_forward(x, ids)?, up.indexed_moe_forward(x, ids)?))
1498}
1499
1500thread_local! {
1501 static DEQUANTIZE_ALL: bool = {
1502 match std::env::var("DEQUANTIZE_ALL") {
1503 Ok(s) => {
1504 !s.is_empty() && s != "0"
1505 },
1506 Err(_) => false,
1507 }
1508 }
1509}
1510
1511thread_local! {
1512 static DEQUANTIZE_ALL_F16: bool = {
1513 match std::env::var("DEQUANTIZE_ALL_F16") {
1514 Ok(s) => {
1515 !s.is_empty() && s != "0"
1516 },
1517 Err(_) => false,
1518 }
1519 }
1520}
1521
1522impl QMatMul {
1523 pub fn from_arc(qtensor: std::sync::Arc<QTensor>) -> Result<Self> {
1524 #[cfg(feature = "vulkan")]
1530 {
1531 let dt = qtensor.dtype();
1532 let native_vk = matches!(
1533 dt,
1534 GgmlDType::Q4_0
1535 | GgmlDType::Q8_0
1536 | GgmlDType::Q4K
1537 | GgmlDType::Q5K
1538 | GgmlDType::Q6K
1539 | GgmlDType::Q2K
1540 | GgmlDType::Q3K
1541 | GgmlDType::IQ4_XS
1542 | GgmlDType::IQ4_NL
1543 | GgmlDType::TQ2_0
1544 | GgmlDType::IQ2_XXS
1545 | GgmlDType::IQ2_S
1546 | GgmlDType::IQ3_XXS
1547 | GgmlDType::IQ3_S
1548 | GgmlDType::IQ1_S
1549 | GgmlDType::IQ1_M
1550 | GgmlDType::IQ2_XS
1551 );
1552 if native_vk {
1553 if let Device::Vulkan(d) = qtensor.device() {
1554 if let Ok((n, k)) = qtensor.shape().dims2() {
1555 let blk = dt.block_size();
1556 if k % blk == 0 {
1557 let bytes = qtensor.data()?;
1558 let wq = match dt {
1565 GgmlDType::Q6K => d.quantize_q6k(&bytes, n, k)?,
1566 GgmlDType::Q3K => d.quantize_q3k(&bytes, n, k)?,
1567 GgmlDType::Q8_0 => d.quantize_q8_blocks(&bytes, n, k)?,
1568 GgmlDType::IQ2_XXS => d.quantize_iq2xxs(&bytes, n, k)?,
1569 GgmlDType::IQ2_XS => d.quantize_iq2xs(&bytes, n, k)?,
1570 GgmlDType::IQ1_M => d.quantize_iq1m(&bytes, n, k)?,
1571 GgmlDType::IQ1_S => d.quantize_iq1s(&bytes, n, k)?,
1572 GgmlDType::IQ3_S => d.quantize_iq3s(&bytes, n, k)?,
1573 GgmlDType::IQ3_XXS => d.quantize_iq3xxs(&bytes, n, k)?,
1574 GgmlDType::IQ2_S => d.quantize_iq2s(&bytes, n, k)?,
1575 _ => d.upload_qweight(&bytes)?,
1576 };
1577 return Ok(Self::VulkanQuant {
1578 qtensor,
1579 wq: std::sync::Arc::new(wq),
1580 dtype: dt,
1581 n,
1582 k,
1583 });
1584 }
1585 }
1586 }
1587 }
1588 }
1589 #[cfg(feature = "wgpu")]
1592 {
1593 let dt = qtensor.dtype();
1594 let native_wgpu = matches!(dt, GgmlDType::Q4_0 | GgmlDType::Q8_0 | GgmlDType::Q4K);
1595 if native_wgpu {
1596 if let Device::Wgpu(d) = qtensor.device() {
1597 if let Ok((n, k)) = qtensor.shape().dims2() {
1598 let blk = dt.block_size();
1599 if k % blk == 0 {
1600 let bytes = qtensor.data()?;
1601 let wq = d.upload_qweight(&bytes)?;
1602 return Ok(Self::WgpuQuant {
1603 qtensor,
1604 wq: std::sync::Arc::new(wq),
1605 dtype: dt,
1606 n,
1607 k,
1608 });
1609 }
1610 }
1611 }
1612 }
1613 }
1614 #[cfg(feature = "rocm")]
1620 {
1621 let dt = qtensor.dtype();
1622 if let Some(qt) = crate::RocmQuantType::from_ggml(dt) {
1627 if let Device::Rocm(d) = qtensor.device() {
1628 if let Ok((n, k)) = qtensor.shape().dims2() {
1629 let blk_ok = k % qt.block_elems() == 0;
1630 if blk_ok {
1631 use crate::backend::BackendDevice;
1632 let bytes = qtensor.data()?;
1633 let wq = d.storage_from_slice(bytes.as_ref())?;
1634 return Ok(Self::RocmQuant {
1635 qtensor,
1636 wq: std::sync::Arc::new(wq),
1637 dtype: dt,
1638 n,
1639 k,
1640 });
1641 }
1642 }
1643 }
1644 }
1645 }
1646 #[cfg(feature = "rocm")]
1652 {
1653 if qtensor.device().is_rocm()
1654 && qtensor.shape().dims().len() == 3
1655 && crate::RocmQuantType::from_ggml(qtensor.dtype()).is_some()
1656 && !DEQUANTIZE_ALL.with(|b| *b)
1657 {
1658 return Ok(Self::QTensor(qtensor));
1659 }
1660 }
1661 let dequantize = match qtensor.dtype() {
1662 GgmlDType::F32 | GgmlDType::F16 | GgmlDType::BF16 => true,
1663 _ => {
1666 DEQUANTIZE_ALL.with(|b| *b)
1667 || qtensor.device().is_vulkan()
1668 || qtensor.device().is_wgpu()
1669 || qtensor.device().is_rocm()
1670 }
1671 };
1672 let t = if dequantize {
1673 if qtensor.device().is_rocm() {
1677 Self::TensorF16(qtensor.dequantize_f16(&qtensor.device())?)
1678 } else {
1679 Self::Tensor(qtensor.dequantize(&qtensor.device())?)
1680 }
1681 } else if DEQUANTIZE_ALL_F16.with(|b| *b) {
1682 let tensor = qtensor.dequantize_f16(&qtensor.device())?;
1683 Self::TensorF16(tensor)
1684 } else {
1685 Self::QTensor(qtensor)
1686 };
1687 Ok(t)
1688 }
1689
1690 pub fn from_qtensor(qtensor: QTensor) -> Result<Self> {
1691 Self::from_arc(std::sync::Arc::new(qtensor))
1692 }
1693
1694 pub fn dequantize_f16(&self) -> Result<Tensor> {
1695 match self {
1696 Self::QTensor(t) => t.dequantize_f16(&t.device()),
1697 Self::Tensor(t) => t.to_dtype(DType::F16),
1698 Self::TensorF16(t) => Ok(t.clone()),
1699 #[cfg(feature = "rocm")]
1700 Self::RocmQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
1701 #[cfg(feature = "vulkan")]
1702 Self::VulkanQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
1703 #[cfg(feature = "wgpu")]
1704 Self::WgpuQuant { qtensor, .. } => qtensor.dequantize_f16(&qtensor.device()),
1705 }
1706 }
1707
1708 pub fn forward_via_f16(&self, xs: &Tensor) -> Result<Tensor> {
1709 let w = self.dequantize_f16()?;
1710 let in_dtype = xs.dtype();
1711 let w = match *xs.dims() {
1712 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
1713 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
1714 _ => w.t()?,
1715 };
1716 xs.to_dtype(DType::F16)?.matmul(&w)?.to_dtype(in_dtype)
1717 }
1718
1719 pub fn indexed_moe_forward(&self, x: &Tensor, ids: &Tensor) -> Result<Tensor> {
1720 match self {
1721 Self::QTensor(t) => t.indexed_moe_forward(x, ids),
1722 #[cfg(feature = "rocm")]
1727 Self::RocmQuant {
1728 qtensor, wq, dtype, ..
1729 } if crate::RocmQuantType::from_ggml(*dtype).is_some() => {
1730 let qt = crate::RocmQuantType::from_ggml(*dtype).unwrap();
1731 let wbank = wq.as_ref();
1732 let (_e_cnt, n, k) = qtensor.shape().dims3()?;
1734 let (t, topk) = ids.dims2()?;
1735 let s = x.dim(1)?; let x_exp = if s == topk {
1737 x.clone()
1738 } else {
1739 x.broadcast_as((t, topk, k))?
1740 };
1741 let nrows = t * topk;
1742 let use_qmmq =
1749 t > 1 && qt.qmmq_capable() && std::env::var("HANZO_MOE_QMMQ_FALLBACK").is_err();
1750 let x_flat = match x_exp.dtype() {
1751 DType::F16 | DType::F32 if use_qmmq => x_exp.reshape((nrows, k))?.contiguous()?,
1755 _ if use_qmmq => x_exp.reshape((nrows, k))?.to_dtype(DType::F16)?.contiguous()?,
1756 DType::BF16 | DType::F16 => x_exp.reshape((nrows, k))?.contiguous()?,
1757 DType::F32 if qt.dp4a_active() => x_exp.reshape((nrows, k))?.contiguous()?,
1760 _ => x_exp.reshape((nrows, k))?.to_dtype(DType::F16)?.contiguous()?,
1761 };
1762 let out_dtype = x.dtype();
1763 let ids_u32 = ids
1768 .reshape((nrows,))?
1769 .to_dtype(crate::DType::U32)?
1770 .contiguous()?;
1771 let (xstore, _) = x_flat.storage_and_layout();
1772 let xr = match &*xstore {
1773 crate::Storage::Rocm(r) => r,
1774 _ => crate::bail!("rocm MoE: x not on rocm after contiguous()"),
1775 };
1776 let (idstore, _) = ids_u32.storage_and_layout();
1777 let idr = match &*idstore {
1778 crate::Storage::Rocm(r) => r,
1779 _ => crate::bail!("rocm MoE: ids not on rocm"),
1780 };
1781 let y = if use_qmmq {
1782 wbank.device.moe_qmmq_quant(qt, wbank, xr, idr, nrows, n, k)?
1783 } else {
1784 wbank.device.moe_matvec_quant(qt, wbank, xr, idr, nrows, n, k)?
1785 };
1786 let out = crate::tensor::from_storage(
1787 crate::Storage::Rocm(y),
1788 (nrows, n),
1789 crate::op::BackpropOp::none(),
1790 false,
1791 );
1792 out.reshape((t, topk, n))?.to_dtype(out_dtype)
1793 }
1794 #[cfg(feature = "rocm")]
1796 Self::RocmQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
1797 #[cfg(feature = "vulkan")]
1798 Self::VulkanQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
1799 #[cfg(feature = "wgpu")]
1800 Self::WgpuQuant { qtensor, .. } => qtensor.indexed_moe_forward(x, ids),
1801 _ => {
1802 panic!("Not implemented!")
1803 }
1804 }
1805 }
1806}
1807
1808impl crate::CustomOp1 for QTensor {
1809 fn name(&self) -> &'static str {
1810 "qmatmul"
1811 }
1812
1813 fn cpu_fwd(
1814 &self,
1815 storage: &crate::CpuStorage,
1816 layout: &crate::Layout,
1817 ) -> Result<(crate::CpuStorage, Shape)> {
1818 if !layout.is_contiguous() {
1819 crate::bail!("input tensor is not contiguous {layout:?}")
1820 }
1821 let src_shape = layout.shape();
1822 let (n, k) = self.shape.dims2()?;
1824 if src_shape.rank() < 2 {
1825 crate::bail!("input tensor has only one dimension {layout:?}")
1826 }
1827 let mut dst_shape = src_shape.dims().to_vec();
1828 let last_k = dst_shape.pop().unwrap();
1829 if last_k != k {
1830 crate::bail!("input tensor {layout:?} incompatible with {:?}", self.shape)
1831 }
1832 dst_shape.push(n);
1833 let dst_shape = Shape::from(dst_shape);
1834 #[allow(clippy::infallible_destructuring_match)]
1835 let self_storage = match &self.storage {
1836 QStorage::Cpu(storage) => storage,
1837 #[cfg(feature = "rocm")]
1838 QStorage::Rocm(..) => crate::bail!("Invalid storage"),
1839 #[cfg(feature = "vulkan")]
1840 QStorage::Vulkan(..) => crate::bail!("Invalid storage"),
1841 #[cfg(feature = "wgpu")]
1842 QStorage::Wgpu(..) => crate::bail!("Invalid storage"),
1843 QStorage::Metal(_) | QStorage::Cuda(_) => crate::bail!("Invalid storage"),
1844 };
1845 match storage.dtype() {
1846 DType::F32 => {
1847 let slice = storage.as_slice::<f32>()?;
1848 let slice =
1849 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
1850 let mut dst_storage = vec![0f32; dst_shape.elem_count()];
1851 self_storage.matmul_t(
1852 (dst_shape.elem_count() / n, k, n),
1853 slice,
1854 &mut dst_storage,
1855 )?;
1856 Ok((crate::CpuStorage::F32(dst_storage), dst_shape))
1857 }
1858 DType::F16 => {
1859 let slice = storage.as_slice::<f16>()?;
1860 let slice =
1861 &slice[layout.start_offset()..layout.start_offset() + src_shape.elem_count()];
1862 let mut dst_storage = vec![f16::ZERO; dst_shape.elem_count()];
1863 self_storage.matmul_t_f16(
1864 (dst_shape.elem_count() / n, k, n),
1865 slice,
1866 &mut dst_storage,
1867 )?;
1868 Ok((crate::CpuStorage::F16(dst_storage), dst_shape))
1869 }
1870 _ => crate::bail!("Expected f32/f16"),
1871 }
1872 }
1873
1874 fn metal_fwd(
1875 &self,
1876 storage: &crate::MetalStorage,
1877 layout: &crate::Layout,
1878 ) -> Result<(crate::MetalStorage, Shape)> {
1879 let self_storage = match &self.storage {
1880 QStorage::Metal(metal) => metal,
1881 _ => unreachable!("Cannot call metal matmul on non metal QTensor"),
1882 };
1883 self_storage.fwd(&self.shape, storage, layout)
1884 }
1885
1886 fn cuda_fwd(
1887 &self,
1888 storage: &crate::CudaStorage,
1889 layout: &crate::Layout,
1890 ) -> Result<(crate::CudaStorage, Shape)> {
1891 let self_storage = match &self.storage {
1892 QStorage::Cuda(cuda) => cuda,
1893 _ => unreachable!("Cannot call cuda matmul on non cuda QTensor"),
1894 };
1895 self_storage.fwd(&self.shape, storage, layout)
1896 }
1897}
1898
1899fn dense_matmul(xs: &Tensor, w: &Tensor) -> Result<Tensor> {
1909 let k = *w.dims().last().unwrap();
1910 let rows = xs.elem_count() / k;
1911 if rows == 1 && xs.device().is_rocm() {
1912 let n = w.dim(0)?;
1913 #[cfg(feature = "rocm")]
1914 {
1915 let d = match xs.device() {
1919 Device::Rocm(d) => d.clone(),
1920 _ => unreachable!(),
1921 };
1922 let xs1 = xs.reshape((k,))?.to_dtype(w.dtype())?.contiguous()?;
1923 let w = w.contiguous()?;
1924 let (wstore, _) = w.storage_and_layout();
1925 let wr = match &*wstore {
1926 crate::Storage::Rocm(r) => r,
1927 _ => crate::bail!("dense_matmul: weight not on rocm"),
1928 };
1929 let (xstore, _) = xs1.storage_and_layout();
1930 let xr = match &*xstore {
1931 crate::Storage::Rocm(r) => r,
1932 _ => crate::bail!("dense_matmul: x not on rocm"),
1933 };
1934 let y = d.dense_gemv(wr, xr, n, k)?;
1935 let mut dims = xs.dims().to_vec();
1936 *dims.last_mut().unwrap() = n;
1937 return crate::tensor::from_storage(
1938 crate::Storage::Rocm(y),
1939 dims,
1940 crate::op::BackpropOp::none(),
1941 false,
1942 )
1943 .to_dtype(xs.dtype());
1944 }
1945 #[cfg(not(feature = "rocm"))]
1946 {
1947 let out = xs.reshape((1, k))?.broadcast_mul(w)?.sum(D::Minus1)?;
1948 let mut dims = xs.dims().to_vec();
1949 *dims.last_mut().unwrap() = n;
1950 return out.reshape(dims);
1951 }
1952 }
1953 let w = match *xs.dims() {
1954 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
1955 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
1956 _ => w.t()?,
1957 };
1958 xs.matmul(&w)
1959}
1960
1961#[cfg(feature = "vulkan")]
1972fn vulkan_prefill_gemm_max_rows(dtype: GgmlDType) -> usize {
1973 match dtype {
1974 GgmlDType::Q5K | GgmlDType::Q6K => 64,
1975 _ => 128,
1976 }
1977}
1978
1979impl crate::Module for QMatMul {
1980 fn forward(&self, xs: &Tensor) -> Result<Tensor> {
1981 match self {
1982 #[cfg(feature = "rocm")]
1983 Self::RocmQuant {
1984 qtensor,
1985 wq,
1986 dtype,
1987 n,
1988 k,
1989 } => {
1990 let xs_recovered = if xs.device().is_rocm() {
1994 None
1995 } else {
1996 if std::env::var("HANZO_DBG_PATH").is_ok() {
1997 eprintln!(
1998 "[ROCm-RECOVER] activation off-device {:?} dims {:?}",
1999 xs.device().location(),
2000 xs.dims()
2001 );
2002 }
2003 Some(xs.to_device(&qtensor.device())?)
2004 };
2005 let xs = xs_recovered.as_ref().unwrap_or(xs);
2006 let rows: usize = xs.elem_count() / *k;
2007 #[cfg(feature = "rocm")]
2008 {
2009 use std::sync::atomic::{AtomicUsize, Ordering};
2010 static DBG_DECODE: AtomicUsize = AtomicUsize::new(0);
2011 static DBG_PREFILL: AtomicUsize = AtomicUsize::new(0);
2012 static DBG_FALLBACK: AtomicUsize = AtomicUsize::new(0);
2013 if std::env::var("HANZO_DBG_PATH").is_ok() {
2014 let qt = crate::RocmQuantType::from_ggml(*dtype);
2018 let decode_wired = qt.is_some();
2019 let prefill_wired = qt.is_some_and(|qt| qt.qmmq_capable());
2020 let prefill_fb = std::env::var("HANZO_QMMQ_FALLBACK").is_ok();
2021 if rows == 1 && decode_wired {
2022 DBG_DECODE.fetch_add(1, Ordering::Relaxed);
2023 } else if rows > 1 && prefill_wired && !prefill_fb {
2024 DBG_PREFILL.fetch_add(1, Ordering::Relaxed);
2025 } else {
2026 DBG_FALLBACK.fetch_add(1, Ordering::Relaxed);
2027 }
2028 let tot = DBG_DECODE.load(Ordering::Relaxed)
2031 + DBG_PREFILL.load(Ordering::Relaxed)
2032 + DBG_FALLBACK.load(Ordering::Relaxed);
2033 if rows > 1 || tot % 200 == 0 {
2034 eprintln!(
2035 "[DBG_PATH] decode={} prefill(native)={} fallback={} (dt={:?} rows={} n={} k={})",
2036 DBG_DECODE.load(Ordering::Relaxed),
2037 DBG_PREFILL.load(Ordering::Relaxed),
2038 DBG_FALLBACK.load(Ordering::Relaxed),
2039 dtype, rows, *n, *k
2040 );
2041 }
2042 }
2043 }
2044 #[cfg(feature = "rocm")]
2051 let unified_qt = crate::RocmQuantType::from_ggml(*dtype);
2052 #[cfg(not(feature = "rocm"))]
2053 let unified_qt: Option<()> = None;
2054 #[cfg(feature = "rocm")]
2059 let qmmq_ok = unified_qt.map(|qt| qt.qmmq_capable()).unwrap_or(false);
2060 #[cfg(not(feature = "rocm"))]
2061 let qmmq_ok = false;
2062 if rows == 1 && unified_qt.is_some() {
2063 #[cfg(feature = "rocm")]
2074 let keep_f32 = unified_qt.map(|qt| qt.dp4a_active()).unwrap_or(false);
2075 #[cfg(not(feature = "rocm"))]
2076 let keep_f32 = false;
2077 let xs = match xs.dtype() {
2078 DType::BF16 | DType::F16 => xs.contiguous()?,
2079 DType::F32 if keep_f32 => xs.contiguous()?,
2080 _ => xs.to_dtype(DType::F16)?.contiguous()?,
2081 };
2082 let d = match xs.device() {
2083 Device::Rocm(d) => d,
2084 _ => crate::bail!("RocmQuant input not on rocm"),
2085 };
2086 let y = {
2087 let (store, _) = xs.storage_and_layout();
2088 let xr = match &*store {
2089 crate::Storage::Rocm(r) => r,
2090 _ => crate::bail!("RocmQuant expected rocm storage"),
2091 };
2092 #[cfg(feature = "rocm")]
2093 {
2094 d.matvec_quant(unified_qt.unwrap(), wq, xr, *n, *k)?
2095 }
2096 #[cfg(not(feature = "rocm"))]
2097 {
2098 crate::bail!("rocm feature disabled")
2099 }
2100 };
2101 let mut dims = xs.dims().to_vec();
2102 let last = dims.len() - 1;
2103 dims[last] = *n;
2104 Ok(crate::tensor::from_storage(
2105 crate::Storage::Rocm(y),
2106 dims,
2107 crate::op::BackpropOp::none(),
2108 false,
2109 ))
2110 } else if let Some(qt) =
2111 unified_qt.filter(|_| qmmq_ok && std::env::var("HANZO_QMMQ_FALLBACK").is_err())
2112 {
2113 let xs = xs.to_dtype(DType::F16)?.contiguous()?;
2123 let d = match xs.device() {
2124 Device::Rocm(d) => d,
2125 _ => crate::bail!("RocmQuant input not on rocm"),
2126 };
2127 let m = xs.elem_count() / *k;
2128 let y = {
2129 let (store, _) = xs.storage_and_layout();
2130 let xr = match &*store {
2131 crate::Storage::Rocm(r) => r,
2132 _ => crate::bail!("RocmQuant expected rocm storage"),
2133 };
2134 #[cfg(feature = "rocm")]
2135 {
2136 d.qmmq_quant(qt, xr, wq, m, *n, *k)?
2137 }
2138 #[cfg(not(feature = "rocm"))]
2139 {
2140 let _ = qt;
2141 crate::bail!("rocm feature disabled")
2142 }
2143 };
2144 let mut dims = xs.dims().to_vec();
2145 let last = dims.len() - 1;
2146 dims[last] = *n;
2147 Ok(crate::tensor::from_storage(
2148 crate::Storage::Rocm(y),
2149 dims,
2150 crate::op::BackpropOp::none(),
2151 false,
2152 ))
2153 } else {
2154 let w = qtensor.dequantize_f16(&xs.device())?;
2159 let w = match *xs.dims() {
2160 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
2161 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
2162 _ => w.t()?,
2163 };
2164 xs.to_dtype(DType::F16)?.matmul(&w)
2165 }
2166 }
2167 #[cfg(feature = "vulkan")]
2168 Self::VulkanQuant {
2169 qtensor,
2170 wq,
2171 dtype,
2172 n,
2173 k,
2174 } => {
2175 let rows: usize = xs.elem_count() / *k;
2176 if rows == 1 {
2177 let xs = xs.contiguous()?;
2180 let d = match xs.device() {
2181 Device::Vulkan(d) => d,
2182 _ => crate::bail!("VulkanQuant input not on vulkan"),
2183 };
2184 let y = {
2185 let (store, _) = xs.storage_and_layout();
2186 let xv = match &*store {
2187 crate::Storage::Vulkan(v) => v,
2188 _ => crate::bail!("VulkanQuant expected vulkan storage"),
2189 };
2190 match dtype {
2191 GgmlDType::Q4_0 => d.matvec_q4_0_gpu(wq, xv, *n, *k)?,
2192 GgmlDType::Q8_0 => d.matvec_q8_gpu(wq, xv, *n, *k)?,
2195 GgmlDType::Q4K => d.matvec_q4k_gpu(wq, xv, *n, *k)?,
2196 GgmlDType::Q5K => d.matvec_q5k_gpu(wq, xv, *n, *k)?,
2197 GgmlDType::Q6K => d.matvec_q6k_gpu(wq, xv, *n, *k)?,
2198 GgmlDType::Q2K => d.matvec_q2k_gpu(wq, xv, *n, *k)?,
2199 GgmlDType::Q3K => d.matvec_q3k_gpu(wq, xv, *n, *k)?,
2200 GgmlDType::IQ4_XS => d.matvec_iq4xs_gpu(wq, xv, *n, *k)?,
2201 GgmlDType::IQ4_NL => d.matvec_iq4nl_gpu(wq, xv, *n, *k)?,
2202 GgmlDType::IQ2_XXS => d.matvec_iq2xxs_gpu(wq, xv, *n, *k)?,
2203 GgmlDType::IQ2_XS => d.matvec_iq2xs_gpu(wq, xv, *n, *k)?,
2204 GgmlDType::IQ1_M => d.matvec_iq1m_gpu(wq, xv, *n, *k)?,
2205 GgmlDType::IQ1_S => d.matvec_iq1s_gpu(wq, xv, *n, *k)?,
2206 GgmlDType::IQ3_S => d.matvec_iq3s_gpu(wq, xv, *n, *k)?,
2207 GgmlDType::IQ3_XXS => d.matvec_iq3xxs_gpu(wq, xv, *n, *k)?,
2208 GgmlDType::IQ2_S => d.matvec_iq2s_gpu(wq, xv, *n, *k)?,
2209 GgmlDType::TQ2_0 => d.matvec_tq2_0_gpu(wq, xv, *n, *k)?,
2210 other => crate::bail!("VulkanQuant: no native matvec for {other:?}"),
2211 }
2212 };
2213 let mut dims = xs.dims().to_vec();
2214 let last = dims.len() - 1;
2215 dims[last] = *n;
2216 Ok(crate::tensor::from_storage(
2217 crate::Storage::Vulkan(y),
2218 dims,
2219 crate::op::BackpropOp::none(),
2220 false,
2221 ))
2222 } else if rows <= vulkan_prefill_gemm_max_rows(*dtype) {
2223 let m = rows;
2232 let xs = xs.contiguous()?;
2233 let d = match xs.device() {
2234 Device::Vulkan(d) => d,
2235 _ => crate::bail!("VulkanQuant input not on vulkan"),
2236 };
2237 let y = {
2238 let (store, _) = xs.storage_and_layout();
2239 let xv = match &*store {
2240 crate::Storage::Vulkan(v) => v,
2241 _ => crate::bail!("VulkanQuant expected vulkan storage"),
2242 };
2243 match dtype {
2244 GgmlDType::Q4_0 => d.matmul_q4_0_gpu(wq, xv, m, *n, *k)?,
2245 GgmlDType::Q8_0 => d.matmul_q8_gpu(wq, xv, m, *n, *k)?,
2246 GgmlDType::Q4K => d.matmul_q4k_gpu(wq, xv, m, *n, *k)?,
2247 GgmlDType::Q5K => d.matmul_q5k_gpu(wq, xv, m, *n, *k)?,
2248 GgmlDType::Q6K => d.matmul_q6k_gpu(wq, xv, m, *n, *k)?,
2249 other => crate::bail!("VulkanQuant: no native matmul for {other:?}"),
2250 }
2251 };
2252 let mut dims = xs.dims().to_vec();
2253 let last = dims.len() - 1;
2254 dims[last] = *n;
2255 Ok(crate::tensor::from_storage(
2256 crate::Storage::Vulkan(y),
2257 dims,
2258 crate::op::BackpropOp::none(),
2259 false,
2260 ))
2261 } else {
2262 let w = qtensor.dequantize(&xs.device())?;
2267 let w = match *xs.dims() {
2268 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
2269 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
2270 _ => w.t()?,
2271 };
2272 xs.matmul(&w)
2273 }
2274 }
2275 #[cfg(feature = "wgpu")]
2276 Self::WgpuQuant {
2277 qtensor,
2278 wq,
2279 dtype,
2280 n,
2281 k,
2282 } => {
2283 let rows: usize = xs.elem_count() / *k;
2284 if rows == 1 {
2285 let xs = xs.contiguous()?;
2288 let d = match xs.device() {
2289 Device::Wgpu(d) => d,
2290 _ => crate::bail!("WgpuQuant input not on wgpu"),
2291 };
2292 let y = {
2293 let (store, _) = xs.storage_and_layout();
2294 let xv = match &*store {
2295 crate::Storage::Wgpu(v) => v,
2296 _ => crate::bail!("WgpuQuant expected wgpu storage"),
2297 };
2298 match dtype {
2299 GgmlDType::Q4_0 => d.matvec_q4_0_gpu(wq, xv, *n, *k)?,
2300 GgmlDType::Q8_0 => d.matvec_q8_0_gpu(wq, xv, *n, *k)?,
2301 GgmlDType::Q4K => d.matvec_q4k_gpu(wq, xv, *n, *k)?,
2302 other => crate::bail!("WgpuQuant: no native matvec for {other:?}"),
2303 }
2304 };
2305 let mut dims = xs.dims().to_vec();
2306 let last = dims.len() - 1;
2307 dims[last] = *n;
2308 Ok(crate::tensor::from_storage(
2309 crate::Storage::Wgpu(y),
2310 dims,
2311 crate::op::BackpropOp::none(),
2312 false,
2313 ))
2314 } else {
2315 let w = qtensor.dequantize(&xs.device())?;
2317 let w = match *xs.dims() {
2318 [b1, b2, _, _] => w.broadcast_left((b1, b2))?.t()?,
2319 [bsize, _, _] => w.broadcast_left(bsize)?.t()?,
2320 _ => w.t()?,
2321 };
2322 xs.matmul(&w)
2323 }
2324 }
2325 Self::QTensor(t) => xs.apply_op1_no_bwd(t.as_ref()),
2326 Self::Tensor(w) => dense_matmul(xs, w),
2327 Self::TensorF16(w) => {
2328 let in_dtype = xs.dtype();
2329 dense_matmul(&xs.to_dtype(DType::F16)?, w)?.to_dtype(in_dtype)
2330 }
2331 }
2332 }
2333}