poulpy/poulpy-hal/src/reference/fft64/convolution.rs
poulpy-hal/src/reference/fft64/convolution.rsRaw
1use crate::{
2 layouts::{
3 Backend, CnvPVecL, CnvPVecLToMut, CnvPVecLToRef, CnvPVecR, CnvPVecRToMut, CnvPVecRToRef, VecZnx, VecZnxBig,
4 VecZnxBigToMut, VecZnxDft, VecZnxDftToMut, VecZnxToRef, ZnxInfos, ZnxView, ZnxViewMut, ZnxZero,
5 },
6 reference::fft64::{
7 reim::{ReimAdd, ReimCopy, ReimDFTExecute, ReimFFTTable, ReimFromZnx, ReimZero},
8 reim4::{
9 Reim4Convolution, Reim4Convolution1Coeff, Reim4Convolution2Coeffs, Reim4Extract1BlkContiguous,
10 Reim4Save1BlkContiguous,
11 },
12 vec_znx_dft::vec_znx_dft_apply,
13 },
14};
15
16pub fn convolution_prepare_left<R, A, T, BE>(table: &ReimFFTTable<f64>, res: &mut R, a: &A, tmp: &mut T)
17where
18 BE: Backend<ScalarPrep = f64>
19 + ReimZero
20 + Reim4Extract1BlkContiguous
21 + ReimDFTExecute<ReimFFTTable<f64>, f64>
22 + ReimFromZnx
23 + ReimZero,
24 R: CnvPVecLToMut<BE> + ZnxInfos + ZnxViewMut<Scalar = BE::ScalarPrep>,
25 A: VecZnxToRef,
26 T: VecZnxDftToMut<BE>,
27{
28 convolution_prepare(table, res, a, tmp)
29}
30
31pub fn convolution_prepare_right<R, A, T, BE>(table: &ReimFFTTable<f64>, res: &mut R, a: &A, tmp: &mut T)
32where
33 BE: Backend<ScalarPrep = f64>
34 + ReimZero
35 + Reim4Extract1BlkContiguous
36 + ReimDFTExecute<ReimFFTTable<f64>, f64>
37 + ReimFromZnx
38 + ReimZero,
39 R: CnvPVecRToMut<BE> + ZnxInfos + ZnxViewMut<Scalar = BE::ScalarPrep>,
40 A: VecZnxToRef,
41 T: VecZnxDftToMut<BE>,
42{
43 convolution_prepare(table, res, a, tmp)
44}
45
46fn convolution_prepare<R, A, T, BE>(table: &ReimFFTTable<f64>, res: &mut R, a: &A, tmp: &mut T)
47where
48 BE: Backend<ScalarPrep = f64>
49 + ReimZero
50 + Reim4Extract1BlkContiguous
51 + ReimDFTExecute<ReimFFTTable<f64>, f64>
52 + ReimFromZnx
53 + ReimZero,
54 R: ZnxInfos + ZnxViewMut<Scalar = BE::ScalarPrep>,
55 A: VecZnxToRef,
56 T: VecZnxDftToMut<BE>,
57{
58 let a: &VecZnx<&[u8]> = &a.to_ref();
59 let tmp: &mut VecZnxDft<&mut [u8], BE> = &mut tmp.to_mut();
60
61 let cols: usize = res.cols();
62 assert_eq!(a.cols(), cols, "a.cols():{} != res.cols():{cols}", a.cols());
63
64 let res_size: usize = res.size();
65 let min_size: usize = res_size.min(a.size());
66
67 let m: usize = a.n() >> 1;
68
69 let n: usize = table.m() << 1;
70
71 let res_raw: &mut [f64] = res.raw_mut();
72
73 for i in 0..cols {
74 vec_znx_dft_apply(table, 1, 0, tmp, 0, a, i);
75
76 let tmp_raw: &[f64] = tmp.raw();
77 let res_col: &mut [f64] = &mut res_raw[i * n * res_size..];
78
79 for blk_i in 0..m / 4 {
80 BE::reim4_extract_1blk_contiguous(m, min_size, blk_i, &mut res_col[blk_i * res_size * 8..], tmp_raw);
81 BE::reim_zero(&mut res_col[blk_i * res_size * 8 + min_size * 8..(blk_i + 1) * res_size * 8]);
82 }
83 }
84}
85
86pub fn convolution_by_const_apply_tmp_bytes(res_size: usize, a_size: usize, b_size: usize) -> usize {
87 let min_size: usize = res_size.min(a_size + b_size - 1);
88 size_of::<i64>() * (min_size + a_size) * 8
89}
90
91pub fn convolution_by_const_apply<R, A, BE>(
92 res: &mut R,
93 res_offset: usize,
94 res_col: usize,
95 a: &A,
96 a_col: usize,
97 b: &[i64],
98 tmp: &mut [i64],
99) where
100 BE: Backend<ScalarBig = i64>
101 + I64ConvolutionByConst1Coeff
102 + I64ConvolutionByConst2Coeffs
103 + I64Extract1BlkContiguous
104 + I64Save1BlkContiguous,
105 R: VecZnxBigToMut<BE>,
106 A: VecZnxToRef,
107{
108 let res: &mut VecZnxBig<&mut [u8], BE> = &mut res.to_mut();
109 let a: &VecZnx<&[u8]> = &a.to_ref();
110
111 let n: usize = res.n();
112 assert_eq!(a.n(), n);
113
114 let res_size: usize = res.size();
115 let a_size: usize = a.size();
116 let b_size: usize = b.len();
117
118 let bound: usize = a_size + b_size - 1;
119 let min_size: usize = res_size.min(bound);
120 let offset: usize = res_offset.min(bound);
121
122 let a_sl: usize = n * a.cols();
123 let res_sl: usize = n * res.cols();
124
125 let res_raw: &mut [i64] = res.raw_mut();
126 let a_raw: &[i64] = a.raw();
127
128 let a_idx: usize = n * a_col;
129 let res_idx: usize = n * res_col;
130
131 let (res_blk, a_blk) = tmp[..(min_size + a_size) * 8].split_at_mut(min_size * 8);
132
133 for blk_i in 0..n / 8 {
134 BE::i64_extract_1blk_contiguous(a_sl, a_idx, a_size, blk_i, a_blk, a_raw);
135 BE::i64_convolution_by_const(res_blk, min_size, offset, a_blk, a_size, b);
136 BE::i64_save_1blk_contiguous(res_sl, res_idx, min_size, blk_i, res_raw, res_blk);
137 }
138
139 for j in min_size..res_size {
140 res.zero_at(res_col, j);
141 }
142}
143
144pub fn convolution_apply_dft_tmp_bytes(res_size: usize, a_size: usize, b_size: usize) -> usize {
145 let min_size: usize = res_size.min(a_size + b_size - 1);
146 size_of::<f64>() * 8 * min_size
147}
148
149#[allow(clippy::too_many_arguments)]
150pub fn convolution_apply_dft<R, A, B, BE>(
151 res: &mut R,
152 res_offset: usize,
153 res_col: usize,
154 a: &A,
155 a_col: usize,
156 b: &B,
157 b_col: usize,
158 tmp: &mut [f64],
159) where
160 BE: Backend<ScalarPrep = f64> + Reim4Save1BlkContiguous + Reim4Convolution1Coeff + Reim4Convolution2Coeffs,
161 R: VecZnxDftToMut<BE>,
162 A: CnvPVecLToRef<BE>,
163 B: CnvPVecRToRef<BE>,
164{
165 let res: &mut VecZnxDft<&mut [u8], BE> = &mut res.to_mut();
166 let a: &CnvPVecL<&[u8], BE> = &a.to_ref();
167 let b: &CnvPVecR<&[u8], BE> = &b.to_ref();
168
169 let n: usize = res.n();
170 assert_eq!(a.n(), n);
171 assert_eq!(b.n(), n);
172 let m: usize = n >> 1;
173
174 let res_size: usize = res.size();
175 let a_size: usize = a.size();
176 let b_size: usize = b.size();
177
178 let bound: usize = a_size + b_size - 1;
179 let min_size: usize = res_size.min(bound);
180 let offset: usize = res_offset.min(bound);
181
182 let dst: &mut [f64] = res.raw_mut();
183 let a_raw: &[f64] = a.raw();
184 let b_raw: &[f64] = b.raw();
185
186 let mut a_idx: usize = a_col * n * a_size;
187 let mut b_idx: usize = b_col * n * b_size;
188 let a_offset: usize = a_size * 8;
189 let b_offset: usize = b_size * 8;
190 for blk_i in 0..m / 4 {
191 BE::reim4_convolution(tmp, min_size, offset, &a_raw[a_idx..], a_size, &b_raw[b_idx..], b_size);
192 BE::reim4_save_1blk_contiguous(m, min_size, blk_i, dst, tmp);
193 a_idx += a_offset;
194 b_idx += b_offset;
195 }
196
197 for j in min_size..res_size {
198 res.zero_at(res_col, j);
199 }
200}
201
202pub fn convolution_pairwise_apply_dft_tmp_bytes(res_size: usize, a_size: usize, b_size: usize) -> usize {
203 convolution_apply_dft_tmp_bytes(res_size, a_size, b_size) + (a_size + b_size) * size_of::<f64>() * 8
204}
205
206#[allow(clippy::too_many_arguments)]
207pub fn convolution_pairwise_apply_dft<R, A, B, BE>(
208 res: &mut R,
209 res_offset: usize,
210 res_col: usize,
211 a: &A,
212 b: &B,
213 col_i: usize,
214 col_j: usize,
215 tmp: &mut [f64],
216) where
217 BE: Backend<ScalarPrep = f64>
218 + ReimZero
219 + ReimAdd
220 + ReimCopy
221 + Reim4Save1BlkContiguous
222 + Reim4Convolution1Coeff
223 + Reim4Convolution2Coeffs,
224 R: VecZnxDftToMut<BE>,
225 A: CnvPVecLToRef<BE>,
226 B: CnvPVecRToRef<BE>,
227{
228 if col_i == col_j {
229 convolution_apply_dft(res, res_offset, res_col, a, col_i, b, col_j, tmp);
230 return;
231 }
232
233 let res: &mut VecZnxDft<&mut [u8], BE> = &mut res.to_mut();
234 let a: &CnvPVecL<&[u8], BE> = &a.to_ref();
235 let b: &CnvPVecR<&[u8], BE> = &b.to_ref();
236
237 let n: usize = res.n();
238 let m: usize = n >> 1;
239
240 assert_eq!(a.n(), n);
241 assert_eq!(b.n(), n);
242
243 let res_size: usize = res.size();
244 let a_size: usize = a.size();
245 let b_size: usize = b.size();
246
247 assert_eq!(
248 tmp.len(),
249 convolution_pairwise_apply_dft_tmp_bytes(res_size, a_size, b_size) / size_of::<f64>()
250 );
251
252 let bound: usize = a_size + b_size - 1;
253 let min_size: usize = res_size.min(bound);
254 let offset: usize = res_offset.min(bound);
255
256 let res_raw: &mut [f64] = res.raw_mut();
257 let a_raw: &[f64] = a.raw();
258 let b_raw: &[f64] = b.raw();
259
260 let a_row_size: usize = a_size * 8;
261 let b_row_size: usize = b_size * 8;
262
263 let mut a0_idx: usize = col_i * n * a_size;
264 let mut a1_idx: usize = col_j * n * a_size;
265 let mut b0_idx: usize = col_i * n * b_size;
266 let mut b1_idx: usize = col_j * n * b_size;
267
268 let (tmp_a, tmp) = tmp.split_at_mut(a_row_size);
269 let (tmp_b, tmp_res) = tmp.split_at_mut(b_row_size);
270
271 for blk_i in 0..m / 4 {
272 let a0: &[f64] = &a_raw[a0_idx..];
273 let a1: &[f64] = &a_raw[a1_idx..];
274 let b0: &[f64] = &b_raw[b0_idx..];
275 let b1: &[f64] = &b_raw[b1_idx..];
276
277 BE::reim_add(tmp_a, &a0[..a_row_size], &a1[..a_row_size]);
278 BE::reim_add(tmp_b, &b0[..b_row_size], &b1[..b_row_size]);
279
280 BE::reim4_convolution(tmp_res, min_size, offset, tmp_a, a_size, tmp_b, b_size);
281 BE::reim4_save_1blk_contiguous(m, min_size, blk_i, res_raw, tmp_res);
282
283 a0_idx += a_row_size;
284 a1_idx += a_row_size;
285 b0_idx += b_row_size;
286 b1_idx += b_row_size;
287 }
288
289 for j in min_size..res_size {
290 res.zero_at(res_col, j);
291 }
292}
293
294pub trait I64Extract1BlkContiguous {
295 fn i64_extract_1blk_contiguous(n: usize, offset: usize, rows: usize, blk: usize, dst: &mut [i64], src: &[i64]);
296}
297
298pub trait I64Save1BlkContiguous {
299 fn i64_save_1blk_contiguous(n: usize, offset: usize, rows: usize, blk: usize, dst: &mut [i64], src: &[i64]);
300}
301
302#[inline(always)]
303pub fn i64_extract_1blk_contiguous_ref(n: usize, offset: usize, rows: usize, blk: usize, dst: &mut [i64], src: &[i64]) {
304 debug_assert!(blk < (n >> 3));
305 debug_assert!(dst.len() >= rows * 8, "dst.len(): {} < rows*8: {}", dst.len(), 8 * rows);
306
307 let offset: usize = offset + (blk << 3);
308
309 // src = 8-values chunks spaced by n, dst = sequential 8-values chunks
310 let src_rows = src.chunks_exact(n).take(rows);
311 let dst_chunks = dst.chunks_exact_mut(8).take(rows);
312
313 for (dst_chunk, src_row) in dst_chunks.zip(src_rows) {
314 dst_chunk.copy_from_slice(&src_row[offset..offset + 8]);
315 }
316}
317
318#[inline(always)]
319pub fn i64_save_1blk_contiguous_ref(n: usize, offset: usize, rows: usize, blk: usize, dst: &mut [i64], src: &[i64]) {
320 debug_assert!(blk < (n >> 3));
321 debug_assert!(src.len() >= rows * 8);
322
323 let offset: usize = offset + (blk << 3);
324
325 // dst = 4-values chunks spaced by m, src = sequential 4-values chunks
326 let dst_rows = dst.chunks_exact_mut(n).take(rows);
327 let src_chunks = src.chunks_exact(8).take(rows);
328
329 for (dst_row, src_chunk) in dst_rows.zip(src_chunks) {
330 dst_row[offset..offset + 8].copy_from_slice(src_chunk);
331 }
332}
333
334pub trait I64ConvolutionByConst1Coeff {
335 fn i64_convolution_by_const_1coeff(k: usize, dst: &mut [i64; 8], a: &[i64], a_size: usize, b: &[i64]);
336}
337
338#[inline(always)]
339pub fn i64_convolution_by_const_1coeff_ref(k: usize, dst: &mut [i64; 8], a: &[i64], a_size: usize, b: &[i64]) {
340 dst.fill(0);
341
342 let b_size: usize = b.len();
343
344 if k >= a_size + b_size {
345 return;
346 }
347 let j_min: usize = k.saturating_sub(a_size - 1);
348 let j_max: usize = (k + 1).min(b_size);
349
350 for j in j_min..j_max {
351 let ai: &[i64] = &a[8 * (k - j)..];
352 let bi: i64 = b[j];
353
354 dst[0] = dst[0].wrapping_add(ai[0].wrapping_mul(bi));
355 dst[1] = dst[1].wrapping_add(ai[1].wrapping_mul(bi));
356 dst[2] = dst[2].wrapping_add(ai[2].wrapping_mul(bi));
357 dst[3] = dst[3].wrapping_add(ai[3].wrapping_mul(bi));
358 dst[4] = dst[4].wrapping_add(ai[4].wrapping_mul(bi));
359 dst[5] = dst[5].wrapping_add(ai[5].wrapping_mul(bi));
360 dst[6] = dst[6].wrapping_add(ai[6].wrapping_mul(bi));
361 dst[7] = dst[7].wrapping_add(ai[7].wrapping_mul(bi));
362 }
363}
364
365#[inline(always)]
366pub(crate) fn as_arr_i64<const size: usize>(x: &[i64]) -> &[i64; size] {
367 debug_assert!(x.len() >= size, "x.len():{} < size:{}", x.len(), size);
368 unsafe { &*(x.as_ptr() as *const [i64; size]) }
369}
370
371#[inline(always)]
372pub(crate) fn as_arr_i64_mut<const size: usize>(x: &mut [i64]) -> &mut [i64; size] {
373 debug_assert!(x.len() >= size, "x.len():{} < size:{}", x.len(), size);
374 unsafe { &mut *(x.as_mut_ptr() as *mut [i64; size]) }
375}
376
377pub trait I64ConvolutionByConst2Coeffs {
378 fn i64_convolution_by_const_2coeffs(k: usize, dst: &mut [i64; 16], a: &[i64], a_size: usize, b: &[i64]);
379}
380
381#[inline(always)]
382pub fn i64_convolution_by_const_2coeffs_ref(k: usize, dst: &mut [i64; 16], a: &[i64], a_size: usize, b: &[i64]) {
383 i64_convolution_by_const_1coeff_ref(k, as_arr_i64_mut(&mut dst[..8]), a, a_size, b);
384 i64_convolution_by_const_1coeff_ref(k + 1, as_arr_i64_mut(&mut dst[8..]), a, a_size, b);
385}
386
387impl<BE: Backend> I64ConvolutionByConst<BE> for BE where Self: I64ConvolutionByConst1Coeff + I64ConvolutionByConst2Coeffs {}
388
389pub trait I64ConvolutionByConst<BE: Backend>
390where
391 BE: I64ConvolutionByConst1Coeff + I64ConvolutionByConst2Coeffs,
392{
393 fn i64_convolution_by_const(dst: &mut [i64], dst_size: usize, offset: usize, a: &[i64], a_size: usize, b: &[i64]) {
394 assert!(a_size > 0);
395
396 for k in (0..dst_size - 1).step_by(2) {
397 BE::i64_convolution_by_const_2coeffs(k + offset, as_arr_i64_mut(&mut dst[8 * k..]), a, a_size, b);
398 }
399
400 if !dst_size.is_multiple_of(2) {
401 let k: usize = dst_size - 1;
402 BE::i64_convolution_by_const_1coeff(k + offset, as_arr_i64_mut(&mut dst[8 * k..]), a, a_size, b);
403 }
404 }
405}