fuel_backend_contract/quantized.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Backend-agnostic quantized kernel registration traits.
3//!
4//! Per-backend quantized fast paths (CUDA, Metal, future Vulkan/CPU-SIMD)
5//! implement [`DynQuantizedStorage`] for their concrete storage type and
6//! [`QuantizedDeviceKernels`] for their device handle. fuel-core dispatches
7//! through these traits without naming concrete backend types.
8//!
9//! The CPU "fast path" is the bare ggml block math (avx/neon
10//! vec_dots inside `k_quants.rs`); it stays in fuel-core because
11//! `BlockQX` types and the file-format readers (gguf/ggml/imatrix) are a
12//! single cohesive numerics unit. The trait still covers the CPU case via
13//! a fuel-core-side adapter (see `fuel-core/src/quantized/mod.rs`).
14//!
15//! The `GgmlDType` block-format *data* tag lives in [`fuel_ir::quantized`].
16
17use crate::dyn_backend::{DynBackendDevice, DynBackendStorage};
18use fuel_ir::{Error, GgmlDType, Layout, Result, Shape};
19use std::any::Any;
20use std::borrow::Cow;
21
22/// Object-safe per-backend quantized storage. Each backend (CPU, CUDA,
23/// Metal, ...) supplies a concrete type that implements this trait;
24/// fuel-core holds them as `Box<dyn DynQuantizedStorage>` and dispatches
25/// without naming backends.
26///
27/// The `_src` arguments to `quantize*` are typed as `&dyn DynBackendStorage`
28/// so the implementor can downcast to its own concrete storage; the
29/// `_onto` variants take a `HostBuffer`-shaped CPU source.
30pub trait DynQuantizedStorage: Send + Sync + std::fmt::Debug {
31 fn dtype(&self) -> GgmlDType;
32 fn block_size(&self) -> usize;
33 fn storage_size_in_bytes(&self) -> usize;
34
35 /// Quantize an in-device source storage onto self.
36 fn quantize(&mut self, src: &dyn DynBackendStorage) -> Result<()>;
37
38 /// Quantize with importance-matrix weighting.
39 fn quantize_imatrix(
40 &mut self,
41 src: &dyn DynBackendStorage,
42 imatrix_weights: &[f32],
43 n_per_row: usize,
44 ) -> Result<()>;
45
46 /// Quantize a CPU source onto self (cross-device).
47 fn quantize_onto(&mut self, src: &dyn DynBackendStorage) -> Result<()>;
48
49 /// Quantize with importance matrix from a CPU source.
50 fn quantize_imatrix_onto(
51 &mut self,
52 src: &dyn DynBackendStorage,
53 imatrix_weights: &[f32],
54 n_per_row: usize,
55 ) -> Result<()>;
56
57 /// Dequantize to backend-native f32 storage.
58 fn dequantize(&self, elem_count: usize) -> Result<Box<dyn DynBackendStorage>>;
59
60 /// Dequantize to f16 (CUDA fast path; default impl rejects).
61 fn dequantize_f16(&self, _elem_count: usize) -> Result<Box<dyn DynBackendStorage>> {
62 Err(Error::Msg("dequantize_f16 not supported on this backend".into()).bt())
63 }
64
65 /// Raw bytes of the quantized data (host-readable copy).
66 fn data(&self) -> Result<Cow<'_, [u8]>>;
67
68 /// Device pointer for callers that need raw addressing (CUDA only today).
69 fn device_ptr(&self) -> Result<*const u8> {
70 Err(Error::Msg("device_ptr not supported on this backend".into()).bt())
71 }
72
73 /// QMatMul forward against an in-device input storage. Returns a fresh
74 /// device storage and its output shape. `self_shape` is the weight shape
75 /// (the QTensor's own shape).
76 fn fwd(
77 &self,
78 self_shape: &Shape,
79 input: &dyn DynBackendStorage,
80 layout: &Layout,
81 ) -> Result<(Box<dyn DynBackendStorage>, Shape)>;
82
83 /// Indexed MoE forward (CUDA-only today; default rejects).
84 fn indexed_moe_forward(
85 &self,
86 _self_shape: &Shape,
87 _input: &dyn DynBackendStorage,
88 _input_layout: &Layout,
89 _ids: &dyn DynBackendStorage,
90 _ids_layout: &Layout,
91 ) -> Result<(Box<dyn DynBackendStorage>, Shape)> {
92 Err(Error::Msg("indexed_moe_forward not supported on this backend".into()).bt())
93 }
94
95 /// Identity returns the host device tag this storage is on.
96 fn as_any(&self) -> &dyn Any;
97
98 /// Owning device handle. Lets fuel-core recover a `Device` from a
99 /// `QTensor` without keeping a parallel `device` field on QTensor.
100 fn device_arc_dyn(&self) -> std::sync::Arc<dyn DynBackendDevice>;
101}
102
103/// Backend device → quantized-kernel constructors. fuel-core looks this up
104/// per Device to allocate fresh QStorage of a given dtype, or to load
105/// pre-quantized bytes.
106pub trait QuantizedDeviceKernels: DynBackendDevice {
107 /// Allocate a zero-initialized quantized storage on this device.
108 fn qzeros(&self, elem_count: usize, dtype: GgmlDType) -> Result<Box<dyn DynQuantizedStorage>>;
109
110 /// Load pre-quantized block-format bytes onto this device. `dtype`
111 /// describes the block format; `data` is interpreted as a flat byte
112 /// slice of those blocks.
113 fn load_quantized(
114 &self,
115 dtype: GgmlDType,
116 data: Cow<'_, [u8]>,
117 ) -> Result<Box<dyn DynQuantizedStorage>>;
118}