1use crate::ops::{add, matmul, multiply, reshape, slice, transpose};
4use crate::{
5 Array, Category, DType, Error, Result, SliceArg, concatenate, promote_types, stack, zeros,
6};
7
8pub fn conv1d(
27 input: &Array,
28 weight: &Array,
29 stride: usize,
30 padding: usize,
31 dilation: usize,
32 groups: usize,
33) -> Result<Array> {
34 let invalid = |msg: String| Err(Error::InvalidArgument(format!("conv1d: {msg}")));
35 if input.ndim() != 3 || weight.ndim() != 3 {
36 return invalid(format!(
37 "input must be [N, L, C_in] and weight [C_out, K, C_in / groups], got {:?} and {:?}",
38 input.shape(),
39 weight.shape()
40 ));
41 }
42 let (l, c_in) = (input.shape()[1], input.shape()[2]);
43 let (c_out, k, c_group) = (weight.shape()[0], weight.shape()[1], weight.shape()[2]);
44 if stride == 0 || dilation == 0 || groups == 0 {
45 return invalid("stride, dilation and groups must be positive".into());
46 }
47 if c_in % groups != 0 || c_out % groups != 0 || c_group * groups != c_in {
48 return invalid(format!(
49 "{c_in} input and {c_out} output channels in {groups} groups need weights \
50 [C_out, K, {}], got {:?}",
51 c_in / groups.max(1),
52 weight.shape()
53 ));
54 }
55 let span = dilation * (k.max(1) - 1) + 1;
56 if k == 0 || l + 2 * padding < span {
57 return invalid(format!(
58 "a kernel spanning {span} positions does not fit {l} positions padded by {padding}"
59 ));
60 }
61 let dtype = promote_types(input.dtype(), weight.dtype());
62 if dtype.category() != Category::Float {
63 return Err(Error::UnsupportedDType {
64 op: "conv1d",
65 dtype,
66 });
67 }
68 if groups == c_in && c_out == c_in {
69 let op = crate::fast::FastOp::DepthwiseConv1d {
71 stride,
72 padding,
73 dilation,
74 };
75 let inputs = vec![input.astype(dtype)?, weight.astype(dtype)?];
76 return Ok(crate::fast::record_or_fallback(op, inputs)?.swap_remove(0));
77 }
78 general(input, weight, (stride, padding, dilation, groups))
79}
80
81pub(crate) fn general(
85 input: &Array,
86 weight: &Array,
87 (stride, padding, dilation, groups): (usize, usize, usize, usize),
88) -> Result<Array> {
89 let (n, l, c_in) = (input.shape()[0], input.shape()[1], input.shape()[2]);
90 let (c_out, k, c_group) = (weight.shape()[0], weight.shape()[1], weight.shape()[2]);
91 let span = dilation * (k - 1) + 1;
92 let dtype = promote_types(input.dtype(), weight.dtype());
93 let acc = if dtype == DType::F64 {
94 DType::F64
95 } else {
96 DType::F32
97 };
98 let l_out = (l + 2 * padding - span) / stride + 1;
99 let mut x = input.astype(acc)?;
100 if padding > 0 {
101 let pad = zeros(&[n, padding, c_in], acc)?;
102 x = concatenate(&[pad.clone(), x, pad], 1)?;
103 }
104 let tap = |j: usize| -> Result<Array> {
106 let start = (j * dilation) as isize;
107 let stop = start + ((l_out - 1) * stride + 1) as isize;
108 slice(
109 &x,
110 &[
111 SliceArg::full(),
112 SliceArg::new(Some(start), Some(stop), stride as isize),
113 SliceArg::full(),
114 ],
115 )
116 };
117 let w = weight.astype(acc)?;
118 let out = if groups == c_in && c_out == c_in {
119 let mut sum: Option<Array> = None;
121 for j in 0..k {
122 let wj = reshape(
123 &slice(
124 &w,
125 &[
126 SliceArg::full(),
127 SliceArg::new(Some(j as isize), Some(j as isize + 1), 1),
128 SliceArg::full(),
129 ],
130 )?,
131 &[c_in as isize],
132 )?;
133 let term = multiply(&tap(j)?, &wj)?;
134 sum = Some(match sum {
135 Some(s) => add(&s, &term)?,
136 None => term,
137 });
138 }
139 sum.expect("at least one tap")
140 } else {
141 let cols = stack(&(0..k).map(tap).collect::<Result<Vec<_>>>()?, 2)?;
143 let per_out = c_out / groups;
144 let mut outs = Vec::with_capacity(groups);
145 for g in 0..groups {
146 let cg = slice(
147 &cols,
148 &[
149 SliceArg::full(),
150 SliceArg::full(),
151 SliceArg::full(),
152 SliceArg::new(
153 Some((g * c_group) as isize),
154 Some(((g + 1) * c_group) as isize),
155 1,
156 ),
157 ],
158 )?;
159 let cg = reshape(&cg, &[(n * l_out) as isize, (k * c_group) as isize])?;
160 let wg = slice(
161 &w,
162 &[
163 SliceArg::new(
164 Some((g * per_out) as isize),
165 Some(((g + 1) * per_out) as isize),
166 1,
167 ),
168 SliceArg::full(),
169 SliceArg::full(),
170 ],
171 )?;
172 let wg = reshape(&wg, &[per_out as isize, (k * c_group) as isize])?;
173 outs.push(matmul(&cg, &transpose(&wg, &[1, 0])?)?);
174 }
175 let y = match groups {
176 1 => outs.pop().expect("one group"),
177 _ => concatenate(&outs, 1)?,
178 };
179 reshape(&y, &[n as isize, l_out as isize, c_out as isize])?
180 };
181 out.astype(dtype)
182}