@@ -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.
0 commit comments