1use crate::{empty, groups, run, u32buf, unibuf, Tensor};
7use ferric_core::Context;
8use std::sync::Arc;
9use wgpu::util::DeviceExt;
10
11#[derive(Clone, Copy, PartialEq, Debug)]
12pub enum DType {
13 F16,
14 BF16,
15}
16impl DType {
17 fn code(self) -> u32 { match self { DType::F16 => 0, DType::BF16 => 1 } }
18}
19
20pub struct Half {
22 ctx: Arc<Context>,
23 buf: Arc<wgpu::Buffer>,
24 pub shape: Vec<usize>,
25 pub dtype: DType,
26}
27
28impl Half {
29 pub fn numel(&self) -> usize { self.shape.iter().product() }
30 pub fn nbytes(&self) -> usize { self.numel().div_ceil(2) * 4 }
32
33 pub fn from_bits(ctx: &Arc<Context>, bits: &[u16], shape: &[usize], dtype: DType) -> Half {
35 assert_eq!(bits.len(), shape.iter().product::<usize>(), "bits len != shape");
36 let words: Vec<u32> = bits.chunks(2).map(|c| c[0] as u32 | ((*c.get(1).unwrap_or(&0) as u32) << 16)).collect();
37 let buf = ctx.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
38 label: Some("half"),
39 contents: bytemuck::cast_slice(&words),
40 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
41 });
42 Half { ctx: ctx.clone(), buf: Arc::new(buf), shape: shape.to_vec(), dtype }
43 }
44
45 pub fn dequant(&self) -> Tensor {
47 let n = self.numel();
48 let out = empty(&self.ctx, n);
49 run(&self.ctx, DEQUANT_WGSL, "dequant", &[self.buf.as_ref(), &out, &u32buf(&self.ctx, &[n as u32, self.dtype.code()])], groups(n));
50 Tensor::from_parts(&self.ctx, out, self.shape.clone())
51 }
52}
53
54impl Tensor {
55 pub fn to_half(&self, dtype: DType) -> Half {
57 let c = self.contiguous();
58 let n = c.numel();
59 let words = n.div_ceil(2);
60 let out = empty(&self.ctx, words);
61 run(&self.ctx, QUANTIZE_WGSL, "quantize", &[c.buf.as_ref(), &out, &u32buf(&self.ctx, &[n as u32, dtype.code()])], groups(words));
62 Half { ctx: self.ctx.clone(), buf: Arc::new(out), shape: c.shape.clone(), dtype }
63 }
64}
65
66pub struct QTensor {
68 ctx: Arc<Context>,
69 buf: Arc<wgpu::Buffer>,
70 pub scale: f32, pub shape: Vec<usize>,
72}
73
74impl Tensor {
75 pub async fn quantize_i8(&self) -> QTensor {
79 let c = self.contiguous();
80 let n = c.numel();
81 let axes: Vec<usize> = (0..c.rank()).collect();
82 let s = c.abs().max(&axes, false).to_vec().await[0] / 127.0;
83 let s = if s == 0.0 { 1.0 } else { s };
84 let words = n.div_ceil(4);
85 let out = empty(&self.ctx, words);
86 run(&self.ctx, QUANT_I8_WGSL, "quant_i8", &[c.buf.as_ref(), &out, &u32buf(&self.ctx, &[n as u32, s.to_bits()])], groups(words));
87 QTensor { ctx: self.ctx.clone(), buf: Arc::new(out), scale: s, shape: c.shape.clone() }
88 }
89}
90
91impl QTensor {
92 pub fn matmul(&self, o: &QTensor) -> Tensor {
94 let (ra, rb) = (self.shape.len(), o.shape.len());
95 assert!(ra == 2 && rb == 2, "quantized matmul is 2D for now");
96 let (m, k, n) = (self.shape[0], self.shape[1], o.shape[1]);
97 assert_eq!(k, o.shape[0], "inner dims mismatch");
98 let out = empty(&self.ctx, m * n);
99 let info = [m as u32, k as u32, n as u32, (self.scale * o.scale).to_bits()];
100 run(&self.ctx, MATMUL_I8_WGSL, "matmul_i8", &[self.buf.as_ref(), o.buf.as_ref(), &out, &u32buf(&self.ctx, &info)], groups(m * n));
101 Tensor::from_parts(&self.ctx, out, vec![m, n])
102 }
103}
104
105pub struct QRow {
108 ctx: Arc<Context>,
109 buf: Arc<wgpu::Buffer>,
110 scale: Arc<wgpu::Buffer>, pub rows: usize,
112 pub cols: usize,
113 pub bits: u32,
114}
115
116impl Tensor {
117 pub fn quantize_rowwise(&self, bits: u32) -> QRow {
119 let c = self.contiguous();
120 assert_eq!(c.rank(), 2, "rowwise quant is 2D");
121 let (rows, cols) = (c.shape[0], c.shape[1]);
122 let qmax = ((1u32 << (bits - 1)) - 1) as f32;
123 let scale = c.abs().max(&[1], false).mul(&c.scalar(1.0 / qmax)); let per_word = (32 / bits) as usize;
125 let words = (rows * cols).div_ceil(per_word);
126 let out = empty(&self.ctx, words);
127 run(&self.ctx, QUANT_ROW_WGSL, "quant_row", &[c.buf.as_ref(), scale.buf.as_ref(), &out, &u32buf(&self.ctx, &[rows as u32, cols as u32, bits, qmax.to_bits()])], groups(words));
128 QRow { ctx: self.ctx.clone(), buf: Arc::new(out), scale: scale.buf.clone(), rows, cols, bits }
129 }
130}
131
132impl Tensor {
133 pub fn matmul_qweight(&self, w: &QRow) -> Tensor {
137 let x = self.contiguous();
138 assert_eq!(x.rank(), 2, "matmul_qweight is 2D");
139 let (rows, inn) = (x.shape[0], x.shape[1]);
140 assert_eq!(inn, w.cols, "inner dims mismatch: x[..,{inn}] vs W[..,{}]", w.cols);
141 let out = empty(&self.ctx, rows * w.rows);
142 run(&self.ctx, MATMUL_QW_WGSL, "matmul_qw", &[x.buf.as_ref(), w.buf.as_ref(), w.scale.as_ref(), &out, &unibuf(&self.ctx, &[rows as u32, w.rows as u32, inn as u32, w.bits])], groups(rows * w.rows));
143 Tensor::from_parts(&self.ctx, out, vec![rows, w.rows])
144 }
145}
146
147impl QRow {
148 pub fn nbytes(&self) -> usize { (self.rows * self.cols * self.bits as usize).div_ceil(8) }
149 pub fn dequant(&self) -> Tensor {
151 let n = self.rows * self.cols;
152 let out = empty(&self.ctx, n);
153 run(&self.ctx, DEQUANT_ROW_WGSL, "dequant_row", &[self.buf.as_ref(), self.scale.as_ref(), &out, &u32buf(&self.ctx, &[self.rows as u32, self.cols as u32, self.bits])], groups(n));
154 Tensor::from_parts(&self.ctx, out, vec![self.rows, self.cols])
155 }
156}
157
158pub struct Ternary {
162 ctx: Arc<Context>,
163 buf: Arc<wgpu::Buffer>,
164 scale: Arc<wgpu::Buffer>, pub rows: usize,
166 pub cols: usize,
167}
168
169impl Tensor {
170 pub fn quantize_ternary(&self) -> Ternary {
172 let c = self.contiguous();
173 assert_eq!(c.rank(), 2, "ternary quant is 2D");
174 let (rows, cols) = (c.shape[0], c.shape[1]);
175 let scale = c.abs().mean(&[1], false); let words = (rows * cols).div_ceil(16);
177 let out = empty(&self.ctx, words);
178 run(&self.ctx, QUANT_TERNARY_WGSL, "quant_ternary", &[c.buf.as_ref(), scale.buf.as_ref(), &out, &u32buf(&self.ctx, &[rows as u32, cols as u32])], groups(words));
179 Ternary { ctx: self.ctx.clone(), buf: Arc::new(out), scale: scale.buf.clone(), rows, cols }
180 }
181 pub fn matmul_ternary(&self, w: &Ternary) -> Tensor {
183 let x = self.contiguous();
184 let (rows, inn) = (x.shape[0], x.shape[1]);
185 assert_eq!(inn, w.cols, "inner dims mismatch");
186 let out = empty(&self.ctx, rows * w.rows);
187 run(&self.ctx, MATMUL_TERNARY_WGSL, "matmul_ternary", &[x.buf.as_ref(), w.buf.as_ref(), w.scale.as_ref(), &out, &unibuf(&self.ctx, &[rows as u32, w.rows as u32, inn as u32, 0])], groups(rows * w.rows));
188 Tensor::from_parts(&self.ctx, out, vec![rows, w.rows])
189 }
190}
191impl Ternary {
192 pub fn nbytes(&self) -> usize { (self.rows * self.cols * 2).div_ceil(8) }
193}
194
195const QUANT_TERNARY_WGSL: &str = r#"
196@group(0) @binding(0) var<storage,read> inp: array<f32>;
197@group(0) @binding(1) var<storage,read> scale: array<f32>; // [rows] absmean
198@group(0) @binding(2) var<storage,read_write> out: array<u32>; // 16 ternary codes per word
199@group(0) @binding(3) var<storage,read> info: array<u32>; // rows, cols
200@compute @workgroup_size(64)
201fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
202 let w = gid.x; let rows = info[0]; let cols = info[1]; let n = rows * cols; let words = (n + 15u) / 16u;
203 if (w >= words) { return; }
204 var word: u32 = 0u;
205 for (var lane: u32 = 0u; lane < 16u; lane = lane + 1u) {
206 let idx = 16u * w + lane;
207 if (idx < n) {
208 var s = scale[idx / cols]; if (s == 0.0) { s = 1.0; }
209 let t = clamp(round(inp[idx] / s), -1.0, 1.0); // {−1,0,+1}
210 let code = u32(i32(t) + 1); // {0,1,2}
211 word = word | (code << (2u * lane));
212 }
213 }
214 out[w] = word;
215}
216"#;
217
218const MATMUL_TERNARY_WGSL: &str = r#"
219@group(0) @binding(0) var<storage,read> x: array<f32>; // [rows, in]
220@group(0) @binding(1) var<storage,read> tw: array<u32>; // packed ternary [out, in]
221@group(0) @binding(2) var<storage,read> scale: array<f32>; // [out]
222@group(0) @binding(3) var<storage,read_write> out: array<f32>; // [rows, out]
223@group(0) @binding(4) var<uniform> info: vec4<u32>; // rows, out, in
224@compute @workgroup_size(64)
225fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
226 let idx = gid.x; let rows = info.x; let o_dim = info.y; let in_dim = info.z;
227 if (idx >= rows * o_dim) { return; }
228 let o = idx % o_dim; let r = idx / o_dim;
229 var acc = 0.0;
230 for (var i: u32 = 0u; i < in_dim; i = i + 1u) {
231 let widx = o * in_dim + i;
232 let code = (tw[widx / 16u] >> (2u * (widx % 16u))) & 3u; // {0,1,2}
233 let t = f32(i32(code) - 1); // {−1,0,+1} (multiply-free in spirit)
234 acc = acc + x[r * in_dim + i] * t;
235 }
236 out[idx] = acc * scale[o];
237}
238"#;
239
240const MATMUL_QW_WGSL: &str = r#"
241@group(0) @binding(0) var<storage,read> x: array<f32>; // [rows, in]
242@group(0) @binding(1) var<storage,read> qw: array<u32>; // packed per-row int, [out, in]
243@group(0) @binding(2) var<storage,read> scale: array<f32>; // [out]
244@group(0) @binding(3) var<storage,read_write> out: array<f32>; // [rows, out]
245@group(0) @binding(4) var<uniform> info: vec4<u32>; // rows, out, in, bits
246@compute @workgroup_size(64)
247fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
248 let idx = gid.x; let rows = info.x; let o_dim = info.y; let in_dim = info.z; let bits = info.w;
249 if (idx >= rows * o_dim) { return; }
250 let o = idx % o_dim; let r = idx / o_dim;
251 let per = 32u / bits; let mask = (1u << bits) - 1u; let signbit = 1u << (bits - 1u);
252 var acc = 0.0;
253 for (var i: u32 = 0u; i < in_dim; i = i + 1u) {
254 let widx = o * in_dim + i; // element in W's flat [out,in]
255 var q = i32((qw[widx / per] >> (bits * (widx % per))) & mask);
256 if (q >= i32(signbit)) { q = q - i32(1u << bits); }
257 acc = acc + x[r * in_dim + i] * f32(q); // weight dequantized on the fly
258 }
259 out[idx] = acc * scale[o];
260}
261"#;
262
263const QUANT_ROW_WGSL: &str = r#"
264@group(0) @binding(0) var<storage,read> inp: array<f32>;
265@group(0) @binding(1) var<storage,read> scale: array<f32>; // [rows]
266@group(0) @binding(2) var<storage,read_write> out: array<u32>;
267@group(0) @binding(3) var<storage,read> info: array<u32>; // rows, cols, bits, bitcast(qmax)
268@compute @workgroup_size(64)
269fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
270 let w = gid.x; let rows = info[0]; let cols = info[1]; let bits = info[2]; let qmax = bitcast<f32>(info[3]);
271 let per = 32u / bits; let n = rows * cols; let words = (n + per - 1u) / per;
272 if (w >= words) { return; }
273 let mask = (1u << bits) - 1u;
274 var word: u32 = 0u;
275 for (var lane: u32 = 0u; lane < per; lane = lane + 1u) {
276 let idx = w * per + lane;
277 if (idx < n) {
278 var s = scale[idx / cols]; if (s == 0.0) { s = 1.0; }
279 let q = i32(clamp(round(inp[idx] / s), -qmax, qmax));
280 word = word | ((u32(q) & mask) << (bits * lane));
281 }
282 }
283 out[w] = word;
284}
285"#;
286
287const DEQUANT_ROW_WGSL: &str = r#"
288@group(0) @binding(0) var<storage,read> inp: array<u32>;
289@group(0) @binding(1) var<storage,read> scale: array<f32>; // [rows]
290@group(0) @binding(2) var<storage,read_write> out: array<f32>;
291@group(0) @binding(3) var<storage,read> info: array<u32>; // rows, cols, bits
292@compute @workgroup_size(64)
293fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
294 let idx = gid.x; let rows = info[0]; let cols = info[1]; let bits = info[2];
295 let n = rows * cols; if (idx >= n) { return; }
296 let per = 32u / bits; let mask = (1u << bits) - 1u; let signbit = 1u << (bits - 1u);
297 let word = inp[idx / per]; let lane = idx % per;
298 var v = i32((word >> (bits * lane)) & mask);
299 if (v >= i32(signbit)) { v = v - i32(1u << bits); }
300 out[idx] = f32(v) * scale[idx / cols];
301}
302"#;
303
304const QUANT_I8_WGSL: &str = r#"
305@group(0) @binding(0) var<storage,read> inp: array<f32>;
306@group(0) @binding(1) var<storage,read_write> out: array<u32>; // 4x int8 per word
307@group(0) @binding(2) var<storage,read> info: array<u32>; // n, bitcast(scale)
308@compute @workgroup_size(64)
309fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
310 let w = gid.x; let n = info[0]; let words = (n + 3u) / 4u;
311 if (w >= words) { return; }
312 let s = bitcast<f32>(info[1]);
313 var word: u32 = 0u;
314 for (var lane: u32 = 0u; lane < 4u; lane = lane + 1u) {
315 let idx = 4u * w + lane;
316 if (idx < n) {
317 let q = i32(clamp(round(inp[idx] / s), -127.0, 127.0));
318 word = word | ((u32(q) & 0xffu) << (8u * lane));
319 }
320 }
321 out[w] = word;
322}
323"#;
324
325const MATMUL_I8_WGSL: &str = r#"
326@group(0) @binding(0) var<storage,read> a: array<u32>; // packed [m,k]
327@group(0) @binding(1) var<storage,read> b: array<u32>; // packed [k,n]
328@group(0) @binding(2) var<storage,read_write> out: array<f32>;
329@group(0) @binding(3) var<storage,read> info: array<u32>; // m,k,n, bitcast(scaleA*scaleB)
330@compute @workgroup_size(64)
331fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
332 let idx = gid.x; let m = info[0]; let k = info[1]; let n = info[2];
333 let sc = bitcast<f32>(info[3]);
334 if (idx >= m * n) { return; }
335 let j = idx % n; let i = idx / n;
336 var acc: i32 = 0;
337 for (var l: u32 = 0u; l < k; l = l + 1u) {
338 let ai = i * k + l; let wa = a[ai >> 2u]; var av = i32((wa >> (8u * (ai & 3u))) & 0xffu); if (av > 127) { av = av - 256; }
339 let bi = l * n + j; let wb = b[bi >> 2u]; var bv = i32((wb >> (8u * (bi & 3u))) & 0xffu); if (bv > 127) { bv = bv - 256; }
340 acc = acc + av * bv;
341 }
342 out[idx] = f32(acc) * sc;
343}
344"#;
345
346const DEQUANT_WGSL: &str = r#"
347@group(0) @binding(0) var<storage,read> inp: array<u32>; // packed 2x16
348@group(0) @binding(1) var<storage,read_write> out: array<f32>;
349@group(0) @binding(2) var<storage,read> info: array<u32>; // n, kind(0=f16,1=bf16)
350@compute @workgroup_size(64)
351fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
352 let i = gid.x; let n = info[0]; let kind = info[1];
353 if (i >= n) { return; }
354 let word = inp[i >> 1u]; let sel = i & 1u;
355 if (kind == 0u) {
356 let pair = unpack2x16float(word); // two f16 → f32
357 out[i] = select(pair.x, pair.y, sel == 1u);
358 } else {
359 let h = (word >> (16u * sel)) & 0xffffu;
360 out[i] = bitcast<f32>(h << 16u); // bf16 → f32
361 }
362}
363"#;
364
365const QUANTIZE_WGSL: &str = r#"
366@group(0) @binding(0) var<storage,read> inp: array<f32>;
367@group(0) @binding(1) var<storage,read_write> out: array<u32>; // packed 2x16
368@group(0) @binding(2) var<storage,read> info: array<u32>; // n, kind
369fn bf16_rne(x: f32) -> u32 {
370 let b = bitcast<u32>(x);
371 let r = b + 0x7fffu + ((b >> 16u) & 1u); // round-to-nearest-even bias
372 return r >> 16u;
373}
374@compute @workgroup_size(64)
375fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
376 let w = gid.x; let n = info[0]; let kind = info[1];
377 let words = (n + 1u) / 2u;
378 if (w >= words) { return; }
379 let i0 = 2u * w; let i1 = i0 + 1u;
380 let x0 = inp[i0];
381 var x1 = 0.0;
382 if (i1 < n) { x1 = inp[i1]; }
383 if (kind == 0u) {
384 out[w] = pack2x16float(vec2<f32>(x0, x1));
385 } else {
386 out[w] = bf16_rne(x0) | (bf16_rne(x1) << 16u);
387 }
388}
389"#;