Skip to content

Commit df7d55b

Browse files
refactor: share batched linear kernel (#199)
1 parent 18a11fb commit df7d55b

2 files changed

Lines changed: 68 additions & 7 deletions

File tree

crates/pwm-core/src/predictor.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,27 @@ pub fn linear(
7878
.collect()
7979
}
8080

81+
/// Exact integer batched dense linear over a flattened `[seq, cols]` input.
82+
///
83+
/// Returns a flattened `[seq, rows]` buffer, applying [`linear`] independently to
84+
/// each sequence row with the same weight matrix and bias vector.
85+
pub fn batched_linear(
86+
weight: &[BoundedInt],
87+
x: &[i64],
88+
bias: &[i64],
89+
seq: usize,
90+
rows: usize,
91+
cols: usize,
92+
) -> Vec<i64> {
93+
debug_assert_eq!(x.len(), seq * cols);
94+
let mut out = Vec::with_capacity(seq * rows);
95+
for tt in 0..seq {
96+
let start = tt * cols;
97+
out.extend(linear(weight, &x[start..start + cols], bias, rows, cols));
98+
}
99+
out
100+
}
101+
81102
/// AdaLN-zero modulation, per channel: `out = requant(x · (one + scale)) + shift`
82103
/// (le-wm `modulate(x, shift, scale) = x*(1+scale)+shift`, in fixed point). `one`
83104
/// is the fixed-point unit at `scale`'s scale; `shift_bits` rescales the product.
@@ -287,6 +308,9 @@ mod tests {
287308
use super::*;
288309
use alloc::vec;
289310

311+
use crate::field::Fp61;
312+
use crate::freivalds::{check_linear_biased, precompute_v};
313+
290314
fn idtable(id: u32, lo: i64, hi: i64) -> ActivationTable {
291315
ActivationTable {
292316
table_id: id,
@@ -295,6 +319,13 @@ mod tests {
295319
}
296320
}
297321

322+
fn lcg(state: &mut u64) -> u64 {
323+
*state = state
324+
.wrapping_mul(6_364_136_223_846_793_005)
325+
.wrapping_add(1_442_695_040_888_963_407);
326+
*state
327+
}
328+
298329
#[test]
299330
fn round_div_rounds_to_nearest() {
300331
assert_eq!(round_div(7, 2), 4); // 3.5 -> 4 (away from zero)
@@ -303,6 +334,41 @@ mod tests {
303334
assert_eq!(round_div(2, 4), 1); // 0.5 -> 1
304335
}
305336

337+
#[test]
338+
fn linear_kernels_match_freivalds_relation() {
339+
let mut state = 0x1820_5eed_cafe_f00d;
340+
for _ in 0..128 {
341+
let rows = 1 + (lcg(&mut state) % 6) as usize;
342+
let cols = 1 + (lcg(&mut state) % 6) as usize;
343+
let seq = 1 + (lcg(&mut state) % 5) as usize;
344+
345+
let w_i8: Vec<i8> = (0..rows * cols)
346+
.map(|_| (lcg(&mut state) % 17) as i8 - 8)
347+
.collect();
348+
let weight: Vec<BoundedInt> = w_i8
349+
.iter()
350+
.map(|&v| BoundedInt::new(v as i64, -128, 127).unwrap())
351+
.collect();
352+
let bias: Vec<i64> = (0..rows)
353+
.map(|_| (lcg(&mut state) % 23) as i64 - 11)
354+
.collect();
355+
let x: Vec<i64> = (0..seq * cols)
356+
.map(|_| (lcg(&mut state) % 31) as i64 - 15)
357+
.collect();
358+
let r: Vec<Fp61> = (0..rows).map(|_| Fp61::new(lcg(&mut state))).collect();
359+
let v = precompute_v(&r, &w_i8, rows, cols);
360+
361+
let batched = batched_linear(&weight, &x, &bias, seq, rows, cols);
362+
for tt in 0..seq {
363+
let xrow = &x[tt * cols..(tt + 1) * cols];
364+
let out = linear(&weight, xrow, &bias, rows, cols);
365+
let bout = &batched[tt * rows..(tt + 1) * rows];
366+
assert_eq!(bout, out.as_slice());
367+
assert!(check_linear_biased(&v, xrow, &bias, &r, bout));
368+
}
369+
}
370+
}
371+
306372
#[test]
307373
fn modulate_matches_le_wm_formula_at_unit_scale() {
308374
// shift_bits 0, one = 1: modulate = x*(1+scale)+shift exactly.

crates/pwm-prover/src/lib.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use pwm_core::field::{try_encode, OutOfRange};
2727
use pwm_core::fixed_point::{requantize, Rounding};
2828
use pwm_core::planning::{argmin, mse_cost};
2929
use pwm_core::predictor::{
30-
gate_vec, layernorm, linear, matmul, modulate_vec, residual_add, softmax_rows,
30+
batched_linear, gate_vec, layernorm, linear, matmul, modulate_vec, residual_add, softmax_rows,
3131
};
3232
use pwm_core::public_input::PublicInput;
3333
use pwm_core::relation::StatementType;
@@ -628,12 +628,7 @@ pub fn prove_block(
628628
.collect(),
629629
None => vec![0i64; rows],
630630
};
631-
// Same shared kernel as `Linear`, applied per sequence row.
632-
let mut out = Vec::with_capacity(s * rows);
633-
for tt in 0..s {
634-
let xrow = &x[tt * cols..(tt + 1) * cols];
635-
out.extend(linear(wd, xrow, &bias, rows, cols));
636-
}
631+
let out = batched_linear(wd, &x, &bias, s, rows, cols);
637632
bufs.insert(out_buf, out.clone());
638633
BlockOp::BatchedLinear {
639634
op_id,

0 commit comments

Comments
 (0)