1use crate::{
4 error::{Error, Result},
5 utility::checked_element_count,
6};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct CausalConv1dConfig {
10 pub channels_in: usize,
11 pub channels_out: usize,
12 pub input_length: usize,
13 pub kernel_size: usize,
14 pub stride: usize,
15 pub dilation: usize,
16 pub groups: usize,
17 pub left_padding: usize,
18}
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub struct Conv1dConfig {
22 pub channels_in: usize,
23 pub channels_out: usize,
24 pub input_length: usize,
25 pub kernel_size: usize,
26 pub stride: usize,
27 pub dilation: usize,
28 pub groups: usize,
29 pub left_padding: usize,
30 pub right_padding: usize,
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct BatchedConv1dConfig {
35 pub batch: usize,
36 pub channels_in: usize,
37 pub channels_out: usize,
38 pub input_length: usize,
39 pub kernel_size: usize,
40 pub stride: usize,
41 pub dilation: usize,
42 pub groups: usize,
43 pub left_padding: usize,
44 pub right_padding: usize,
45 pub input_batch_stride: usize,
46 pub output_batch_stride: usize,
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub struct Conv1dIm2colConfig {
51 pub batch: usize,
52 pub channels_in: usize,
53 pub input_length: usize,
54 pub kernel_size: usize,
55 pub stride: usize,
56 pub dilation: usize,
57 pub groups: usize,
58 pub left_padding: usize,
59 pub right_padding: usize,
60 pub input_batch_stride: usize,
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum Conv1dActivation {
65 None,
66 Gelu,
67}
68
69impl CausalConv1dConfig {
70 pub fn output_length(self) -> Result<usize> {
71 validate_causal_conv1d_config(self)?;
72 conv1d_output_length(
73 self.input_length,
74 self.kernel_size,
75 self.stride,
76 self.dilation,
77 self.left_padding,
78 0,
79 )
80 }
81}
82
83impl Conv1dConfig {
84 pub fn output_length(self) -> Result<usize> {
85 validate_conv1d_config(self)?;
86 conv1d_output_length(
87 self.input_length,
88 self.kernel_size,
89 self.stride,
90 self.dilation,
91 self.left_padding,
92 self.right_padding,
93 )
94 }
95}
96
97impl BatchedConv1dConfig {
98 pub fn output_length(self) -> Result<usize> {
99 validate_batched_conv1d_config(self)?;
100 conv1d_output_length(
101 self.input_length,
102 self.kernel_size,
103 self.stride,
104 self.dilation,
105 self.left_padding,
106 self.right_padding,
107 )
108 }
109}
110
111impl Conv1dIm2colConfig {
112 pub fn output_length(self) -> Result<usize> {
113 validate_conv1d_im2col_config(self)?;
114 conv1d_output_length(
115 self.input_length,
116 self.kernel_size,
117 self.stride,
118 self.dilation,
119 self.left_padding,
120 self.right_padding,
121 )
122 }
123
124 pub fn output_values_per_batch(self) -> Result<usize> {
125 let output_length = self.output_length()?;
126 checked_element_count(
127 checked_element_count(self.channels_in, output_length)?,
128 self.kernel_size,
129 )
130 }
131}
132
133pub fn conv1d_causal(input: &[f32], weight: &[f32], config: CausalConv1dConfig) -> Vec<f32> {
134 let output_length = config.output_length().expect("output length");
135 let channels_in_per_group = config.channels_in / config.groups;
136 let channels_out_per_group = config.channels_out / config.groups;
137 let mut output = vec![0.0; config.channels_out * output_length];
138 for out_channel in 0..config.channels_out {
139 let group = out_channel / channels_out_per_group;
140 let input_channel_start = group * channels_in_per_group;
141 for out_pos in 0..output_length {
142 let mut sum = 0.0f32;
143 for group_input_channel in 0..channels_in_per_group {
144 let input_channel = input_channel_start + group_input_channel;
145 for kernel_index in 0..config.kernel_size {
146 let raw_input_pos = out_pos * config.stride + kernel_index * config.dilation;
147 if raw_input_pos < config.left_padding {
148 continue;
149 }
150 let input_pos = raw_input_pos - config.left_padding;
151 if input_pos >= config.input_length {
152 continue;
153 }
154 let input_value = input[input_channel * config.input_length + input_pos];
155 let weight_value = weight[(out_channel * channels_in_per_group
156 + group_input_channel)
157 * config.kernel_size
158 + kernel_index];
159 sum += input_value * weight_value;
160 }
161 }
162 output[out_channel * output_length + out_pos] = sum;
163 }
164 }
165 output
166}
167
168pub fn conv1d(input: &[f32], weight: &[f32], config: Conv1dConfig) -> Vec<f32> {
169 let output_length = config.output_length().expect("output length");
170 let channels_in_per_group = config.channels_in / config.groups;
171 let channels_out_per_group = config.channels_out / config.groups;
172 let mut output = vec![0.0; config.channels_out * output_length];
173 for out_channel in 0..config.channels_out {
174 let group = out_channel / channels_out_per_group;
175 let input_channel_start = group * channels_in_per_group;
176 for out_pos in 0..output_length {
177 let mut sum = 0.0f32;
178 for group_input_channel in 0..channels_in_per_group {
179 let input_channel = input_channel_start + group_input_channel;
180 for kernel_index in 0..config.kernel_size {
181 let raw_input_pos = out_pos * config.stride + kernel_index * config.dilation;
182 if raw_input_pos < config.left_padding {
183 continue;
184 }
185 let input_pos = raw_input_pos - config.left_padding;
186 if input_pos >= config.input_length {
187 continue;
188 }
189 let input_value = input[input_channel * config.input_length + input_pos];
190 let weight_value = weight[(out_channel * channels_in_per_group
191 + group_input_channel)
192 * config.kernel_size
193 + kernel_index];
194 sum += input_value * weight_value;
195 }
196 }
197 output[out_channel * output_length + out_pos] = sum;
198 }
199 }
200 output
201}
202
203pub fn conv1d_bias_activation(
204 input: &[f32],
205 weight: &[f32],
206 bias: &[f32],
207 config: Conv1dConfig,
208 activation: Conv1dActivation,
209) -> Vec<f32> {
210 let output_length = config.output_length().expect("output length");
211 let mut output = conv1d(input, weight, config);
212 for out_channel in 0..config.channels_out {
213 for out_pos in 0..output_length {
214 let value = &mut output[out_channel * output_length + out_pos];
215 *value += bias[out_channel];
216 *value = apply_activation(*value, activation);
217 }
218 }
219 output
220}
221
222pub fn conv1d_batched(input: &[f32], weight: &[f32], config: BatchedConv1dConfig) -> Vec<f32> {
223 let output_length = config.output_length().expect("output length");
224 let output_len = batched_reach(
225 config.batch,
226 config.output_batch_stride,
227 config.channels_out,
228 output_length,
229 );
230 let channels_in_per_group = config.channels_in / config.groups;
231 let channels_out_per_group = config.channels_out / config.groups;
232 let mut output = vec![0.0; output_len];
233 for batch in 0..config.batch {
234 let input_batch_base = batch * config.input_batch_stride;
235 let output_batch_base = batch * config.output_batch_stride;
236 for out_channel in 0..config.channels_out {
237 let group = out_channel / channels_out_per_group;
238 let input_channel_start = group * channels_in_per_group;
239 for out_pos in 0..output_length {
240 let mut sum = 0.0f32;
241 for group_input_channel in 0..channels_in_per_group {
242 let input_channel = input_channel_start + group_input_channel;
243 for kernel_index in 0..config.kernel_size {
244 let raw_input_pos =
245 out_pos * config.stride + kernel_index * config.dilation;
246 if raw_input_pos < config.left_padding {
247 continue;
248 }
249 let input_pos = raw_input_pos - config.left_padding;
250 if input_pos >= config.input_length {
251 continue;
252 }
253 let input_value = input
254 [input_batch_base + input_channel * config.input_length + input_pos];
255 let weight_value = weight[(out_channel * channels_in_per_group
256 + group_input_channel)
257 * config.kernel_size
258 + kernel_index];
259 sum += input_value * weight_value;
260 }
261 }
262 output[output_batch_base + out_channel * output_length + out_pos] = sum;
263 }
264 }
265 }
266 output
267}
268
269pub fn conv1d_batched_im2col(input: &[f32], config: Conv1dIm2colConfig) -> Vec<f32> {
270 let output_length = config.output_length().expect("output length");
271 let channels_in_per_group = config.channels_in / config.groups;
272 let output_values_per_batch = config.output_values_per_batch().expect("output values");
273 let mut output = vec![0.0; config.batch * output_values_per_batch];
274 for batch in 0..config.batch {
275 let input_batch_base = batch * config.input_batch_stride;
276 let output_batch_base = batch * output_values_per_batch;
277 for group in 0..config.groups {
278 for out_pos in 0..output_length {
279 for group_input_channel in 0..channels_in_per_group {
280 let input_channel = group * channels_in_per_group + group_input_channel;
281 for kernel_index in 0..config.kernel_size {
282 let output_offset = output_batch_base
283 + (((group * output_length + out_pos) * channels_in_per_group
284 + group_input_channel)
285 * config.kernel_size
286 + kernel_index);
287 let raw_input_pos =
288 out_pos * config.stride + kernel_index * config.dilation;
289 if raw_input_pos < config.left_padding {
290 continue;
291 }
292 let input_pos = raw_input_pos - config.left_padding;
293 if input_pos >= config.input_length {
294 continue;
295 }
296 output[output_offset] = input
297 [input_batch_base + input_channel * config.input_length + input_pos];
298 }
299 }
300 }
301 }
302 }
303 output
304}
305
306pub fn conv1d_batched_from_im2col(
307 columns: &[f32],
308 weight: &[f32],
309 config: BatchedConv1dConfig,
310) -> Vec<f32> {
311 let output_length = config.output_length().expect("output length");
312 let output_len = batched_reach(
313 config.batch,
314 config.output_batch_stride,
315 config.channels_out,
316 output_length,
317 );
318 let channels_in_per_group = config.channels_in / config.groups;
319 let channels_out_per_group = config.channels_out / config.groups;
320 let column_values_per_batch = config.channels_in * output_length * config.kernel_size;
321 let mut output = vec![0.0; output_len];
322 for batch in 0..config.batch {
323 let column_batch_base = batch * column_values_per_batch;
324 let output_batch_base = batch * config.output_batch_stride;
325 for out_channel in 0..config.channels_out {
326 let group = out_channel / channels_out_per_group;
327 for out_pos in 0..output_length {
328 let mut sum = 0.0f32;
329 for group_input_channel in 0..channels_in_per_group {
330 for kernel_index in 0..config.kernel_size {
331 let column_offset = column_batch_base
332 + (((group * output_length + out_pos) * channels_in_per_group
333 + group_input_channel)
334 * config.kernel_size
335 + kernel_index);
336 let weight_offset = (out_channel * channels_in_per_group
337 + group_input_channel)
338 * config.kernel_size
339 + kernel_index;
340 sum += columns[column_offset] * weight[weight_offset];
341 }
342 }
343 output[output_batch_base + out_channel * output_length + out_pos] = sum;
344 }
345 }
346 }
347 output
348}
349
350pub fn conv1d_causal_bias(
351 input: &[f32],
352 weight: &[f32],
353 bias: &[f32],
354 config: CausalConv1dConfig,
355) -> Vec<f32> {
356 let output_length = config.output_length().expect("output length");
357 let mut output = conv1d_causal(input, weight, config);
358 for out_channel in 0..config.channels_out {
359 for out_pos in 0..output_length {
360 output[out_channel * output_length + out_pos] += bias[out_channel];
361 }
362 }
363 output
364}
365
366pub fn conv1d_causal_bias_gelu(
367 input: &[f32],
368 weight: &[f32],
369 bias: &[f32],
370 config: CausalConv1dConfig,
371) -> Vec<f32> {
372 let mut output = conv1d_causal_bias(input, weight, bias, config);
373 for value in &mut output {
374 *value = gelu(*value);
375 }
376 output
377}
378
379pub fn conv1d_causal_bias_activation(
380 input: &[f32],
381 weight: &[f32],
382 bias: &[f32],
383 config: CausalConv1dConfig,
384 activation: Conv1dActivation,
385) -> Vec<f32> {
386 let output_length = config.output_length().expect("output length");
387 let mut output = conv1d_causal_bias(input, weight, bias, config);
388 for out_channel in 0..config.channels_out {
389 for out_pos in 0..output_length {
390 let value = &mut output[out_channel * output_length + out_pos];
391 *value = apply_activation(*value, activation);
392 }
393 }
394 output
395}
396
397fn batched_reach(batch: usize, batch_stride: usize, channels: usize, length: usize) -> usize {
398 (batch - 1) * batch_stride + channels * length
399}
400
401fn gelu(value: f32) -> f32 {
402 0.5 * value * (1.0 + (0.7978846 * (value + 0.044715 * value * value * value)).tanh())
403}
404
405fn apply_activation(value: f32, activation: Conv1dActivation) -> f32 {
406 match activation {
407 Conv1dActivation::None => value,
408 Conv1dActivation::Gelu => gelu(value),
409 }
410}
411
412fn validate_conv1d_config(config: Conv1dConfig) -> Result<()> {
413 validate_conv1d_shape(
414 config.channels_in,
415 config.channels_out,
416 config.input_length,
417 config.kernel_size,
418 config.stride,
419 config.dilation,
420 config.groups,
421 )
422}
423
424fn validate_batched_conv1d_config(config: BatchedConv1dConfig) -> Result<()> {
425 if config.batch == 0 || config.input_batch_stride == 0 || config.output_batch_stride == 0 {
426 return Err(Error::InvalidLength);
427 }
428 validate_conv1d_shape(
429 config.channels_in,
430 config.channels_out,
431 config.input_length,
432 config.kernel_size,
433 config.stride,
434 config.dilation,
435 config.groups,
436 )?;
437 let input_item_len = checked_element_count(config.channels_in, config.input_length)?;
438 let output_length = conv1d_output_length(
439 config.input_length,
440 config.kernel_size,
441 config.stride,
442 config.dilation,
443 config.left_padding,
444 config.right_padding,
445 )?;
446 let output_item_len = checked_element_count(config.channels_out, output_length)?;
447 if config.input_batch_stride < input_item_len || config.output_batch_stride < output_item_len {
448 return Err(Error::InvalidLength);
449 }
450 Ok(())
451}
452
453fn validate_conv1d_im2col_config(config: Conv1dIm2colConfig) -> Result<()> {
454 if config.batch == 0 || config.input_batch_stride == 0 {
455 return Err(Error::InvalidLength);
456 }
457 validate_conv1d_shape(
458 config.channels_in,
459 config.channels_in,
460 config.input_length,
461 config.kernel_size,
462 config.stride,
463 config.dilation,
464 config.groups,
465 )?;
466 let input_item_len = checked_element_count(config.channels_in, config.input_length)?;
467 if config.input_batch_stride < input_item_len {
468 return Err(Error::InvalidLength);
469 }
470 Ok(())
471}
472
473fn validate_causal_conv1d_config(config: CausalConv1dConfig) -> Result<()> {
474 validate_conv1d_shape(
475 config.channels_in,
476 config.channels_out,
477 config.input_length,
478 config.kernel_size,
479 config.stride,
480 config.dilation,
481 config.groups,
482 )
483}
484
485fn validate_conv1d_shape(
486 channels_in: usize,
487 channels_out: usize,
488 input_length: usize,
489 kernel_size: usize,
490 stride: usize,
491 dilation: usize,
492 groups: usize,
493) -> Result<()> {
494 if channels_in == 0
495 || channels_out == 0
496 || input_length == 0
497 || kernel_size == 0
498 || stride == 0
499 || dilation == 0
500 || groups == 0
501 {
502 return Err(Error::InvalidLength);
503 }
504 if !channels_in.is_multiple_of(groups) || !channels_out.is_multiple_of(groups) {
505 return Err(Error::InvalidLength);
506 }
507 effective_kernel_size(kernel_size, dilation)?;
508 Ok(())
509}
510
511fn conv1d_output_length(
512 input_length: usize,
513 kernel_size: usize,
514 stride: usize,
515 dilation: usize,
516 left_padding: usize,
517 right_padding: usize,
518) -> Result<usize> {
519 let padded = input_length
520 .checked_add(left_padding)
521 .and_then(|padded| padded.checked_add(right_padding))
522 .ok_or(Error::SizeOverflow)?;
523 let effective_kernel_size = effective_kernel_size(kernel_size, dilation)?;
524 if padded < effective_kernel_size {
525 return Ok(0);
526 }
527 Ok((padded - effective_kernel_size) / stride + 1)
528}
529
530fn effective_kernel_size(kernel_size: usize, dilation: usize) -> Result<usize> {
531 kernel_size
532 .checked_sub(1)
533 .ok_or(Error::SizeOverflow)?
534 .checked_mul(dilation)
535 .and_then(|size| size.checked_add(1))
536 .ok_or(Error::SizeOverflow)
537}
538
539pub fn causal_conv1d_update_silu(
547 conv_state: &[f32],
548 input: &[f32],
549 weight: &[f32],
550 bias: &[f32],
551 batch: usize,
552 channels: usize,
553 kernel_size: usize,
554) -> (Vec<f32>, Vec<f32>) {
555 let mut out = vec![0.0f32; batch * channels];
556 let mut next_state = conv_state.to_vec();
557 for batch_index in 0..batch {
558 for (channel, bias_value) in bias.iter().copied().enumerate().take(channels) {
559 let token_offset = batch_index * channels + channel;
560 let state_offset = (batch_index * channels + channel) * kernel_size;
561 let weight_offset = channel * kernel_size;
562 let mut dot = bias_value;
563 for kernel in 0..kernel_size {
564 let value = if kernel + 1 == kernel_size {
565 input[token_offset]
566 } else {
567 conv_state[state_offset + kernel + 1]
568 };
569 dot += value * weight[weight_offset + kernel];
570 }
571 out[token_offset] = dot / (1.0 + (-dot).exp());
572 for kernel in 0..kernel_size - 1 {
573 next_state[state_offset + kernel] = conv_state[state_offset + kernel + 1];
574 }
575 next_state[state_offset + kernel_size - 1] = input[token_offset];
576 }
577 }
578 (out, next_state)
579}
580
581pub fn causal_conv1d_prefill_silu(
588 input: &[f32],
589 weight: &[f32],
590 bias: &[f32],
591 batch: usize,
592 channels: usize,
593 kernel_size: usize,
594 input_length: usize,
595 output_length: usize,
596) -> (Vec<f32>, Vec<f32>) {
597 let mut out = vec![0.0f32; batch * channels * output_length];
598 let mut final_state = vec![0.0f32; batch * channels * kernel_size];
599 for batch_index in 0..batch {
600 for (channel, bias_value) in bias.iter().copied().enumerate().take(channels) {
601 let input_offset = (batch_index * channels + channel) * input_length;
602 let output_offset = (batch_index * channels + channel) * output_length;
603 let weight_offset = channel * kernel_size;
604 let state_offset = (batch_index * channels + channel) * kernel_size;
605
606 for time in 0..output_length {
607 let mut dot = bias_value;
608 for kernel in 0..kernel_size {
609 let input_time = time as isize + kernel as isize + 1 - kernel_size as isize;
610 let value = if input_time >= 0 && (input_time as usize) < input_length {
611 input[input_offset + input_time as usize]
612 } else {
613 0.0
614 };
615 dot += value * weight[weight_offset + kernel];
616 }
617 out[output_offset + time] = dot / (1.0 + (-dot).exp());
618 }
619
620 for kernel in 0..kernel_size {
621 let input_time = input_length as isize + kernel as isize - kernel_size as isize;
622 final_state[state_offset + kernel] =
623 if input_time >= 0 && (input_time as usize) < input_length {
624 input[input_offset + input_time as usize]
625 } else {
626 0.0
627 };
628 }
629 }
630 }
631 (out, final_state)
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637
638 #[test]
639 fn causal_conv1d_prefill_matches_repeated_update_from_zero_state() {
640 let batch = 2usize;
641 let channels = 3usize;
642 let kernel_size = 5usize;
643 let input_length = 4usize;
644 let input = (0..batch * channels * input_length)
645 .map(|index| (index as f32 % 11.0) * 0.125 - 0.5)
646 .collect::<Vec<_>>();
647 let weight = (0..channels * kernel_size)
648 .map(|index| (index as f32 % 7.0) * 0.25 - 0.75)
649 .collect::<Vec<_>>();
650 let bias = (0..channels)
651 .map(|index| (index as f32 % 5.0) * 0.125 - 0.25)
652 .collect::<Vec<_>>();
653
654 let (actual_out, actual_state) = causal_conv1d_prefill_silu(
655 &input,
656 &weight,
657 &bias,
658 batch,
659 channels,
660 kernel_size,
661 input_length,
662 input_length,
663 );
664 let mut expected_out = vec![0.0f32; batch * channels * input_length];
665 let mut state = vec![0.0f32; batch * channels * kernel_size];
666 for time in 0..input_length {
667 let token = (0..batch * channels)
668 .map(|index| {
669 let batch_index = index / channels;
670 let channel = index % channels;
671 input[(batch_index * channels + channel) * input_length + time]
672 })
673 .collect::<Vec<_>>();
674 let (step_out, next_state) = causal_conv1d_update_silu(
675 &state,
676 &token,
677 &weight,
678 &bias,
679 batch,
680 channels,
681 kernel_size,
682 );
683 for batch_index in 0..batch {
684 for channel in 0..channels {
685 expected_out[(batch_index * channels + channel) * input_length + time] =
686 step_out[batch_index * channels + channel];
687 }
688 }
689 state = next_state;
690 }
691
692 assert_eq!(actual_out, expected_out);
693 assert_eq!(actual_state, state);
694 }
695
696 #[test]
697 fn causal_conv1d_prefill_final_state_zero_pads_short_input() {
698 let input = [1.0f32, 2.0];
699 let weight = [1.0f32, 1.0, 1.0, 1.0];
700 let bias = [0.0f32];
701 let (_, state) = causal_conv1d_prefill_silu(&input, &weight, &bias, 1, 1, 4, 2, 2);
702 assert_eq!(state, vec![0.0, 0.0, 1.0, 2.0]);
703 }
704}