1use super::{BasicOps, DType, Int, Tensor, TensorPrimitive, backend::Backend};
2use super::ops::PadMode;
3use super::module::{
4 avg_pool1d, avg_pool2d,
5 max_pool1d, max_pool2d, max_pool1d_with_indices, max_pool2d_with_indices,
6};
7
8pub(super) fn volume_planes<B: Backend, K: BasicOps<B>>(input: Tensor<B, 5, K>) -> Tensor<B, 4, K> {
9 let [batch, channels, depth, height, width] = input.dims();
10 let planes = batch.checked_mul(depth).expect("pooling plane count overflow");
11 input.permute([0, 2, 1, 3, 4]).reshape([planes, channels, height, width])
12}
13
14pub(super) fn plane_depth_lines<B: Backend, K: BasicOps<B>>(
15 planes: Tensor<B, 4, K>,
16 batch: usize,
17 depth: usize,
18) -> Tensor<B, 3, K> {
19 let [_, channels, height, width] = planes.dims();
20 let lines = batch.checked_mul(channels)
21 .and_then(|count| count.checked_mul(height))
22 .and_then(|count| count.checked_mul(width))
23 .expect("pooling depth line count overflow");
24 planes.reshape([batch, depth, channels, height, width])
25 .permute([0, 2, 3, 4, 1]).reshape([lines, 1, depth])
26}
27
28pub(super) fn depth_lines_volume<B: Backend, K: BasicOps<B>>(
29 lines: Tensor<B, 3, K>,
30 batch: usize,
31 channels: usize,
32 height: usize,
33 width: usize,
34) -> Tensor<B, 5, K> {
35 let [_, _, depth] = lines.dims();
36 lines.reshape([batch, channels, height, width, depth]).permute([0, 1, 4, 2, 3])
37}
38
39pub(crate) fn max_pool3d_composed<B: Backend>(
45 input: Tensor<B, 5>,
46 kernel_size: [usize; 3],
47 stride: [usize; 3],
48 padding: [usize; 3],
49 dilation: [usize; 3],
50 ceil_mode: bool,
51) -> Tensor<B, 5> {
52 let [batch, channels, depth, _, _] = input.dims();
53 let planes = max_pool2d(
54 volume_planes(input),
55 [kernel_size[1], kernel_size[2]],
56 [stride[1], stride[2]],
57 [padding[1], padding[2]],
58 [dilation[1], dilation[2]],
59 ceil_mode,
60 );
61 let [_, _, height, width] = planes.dims();
62 let lines = max_pool1d(
63 plane_depth_lines(planes, batch, depth),
64 kernel_size[0], stride[0], padding[0], dilation[0], ceil_mode,
65 );
66 depth_lines_volume(lines, batch, channels, height, width)
67}
68
69pub(crate) fn max_pool3d_with_indices_composed<B: Backend>(
76 input: Tensor<B, 5>,
77 kernel_size: [usize; 3],
78 stride: [usize; 3],
79 padding: [usize; 3],
80 dilation: [usize; 3],
81 ceil_mode: bool,
82) -> (Tensor<B, 5>, Tensor<B, 5, Int>) {
83 let [batch, channels, depth, input_height, input_width] = input.dims();
84 assert!(depth > 0 && input_height > 0 && input_width > 0,
85 "volume pooling indices require non-empty spatial axes");
86 let area = input_height.checked_mul(input_width).expect("pooling plane area overflow");
87 let volume = depth.checked_mul(area).expect("pooling volume size overflow");
88 assert!(volume <= i64::MAX as usize, "volume pooling indices exceed I64");
89 let (planes, plane_indices) = max_pool2d_with_indices(
90 volume_planes(input),
91 [kernel_size[1], kernel_size[2]],
92 [stride[1], stride[2]],
93 [padding[1], padding[2]],
94 [dilation[1], dilation[2]],
95 ceil_mode,
96 );
97 let [_, _, height, width] = planes.dims();
98 let (lines, depth_indices) = max_pool1d_with_indices(
99 plane_depth_lines(planes, batch, depth),
100 kernel_size[0], stride[0], padding[0], dilation[0], ceil_mode,
101 );
102 let depth_indices = depth_indices.cast(DType::I64);
103 let safe_depth_indices = depth_indices.clone().clamp(0, depth as i64 - 1);
104 let spatial_indices = plane_depth_lines(plane_indices.cast(DType::I64), batch, depth)
105 .gather(2, safe_depth_indices.clone());
106 let invalid = depth_indices.clone().lower_elem(0)
107 .bool_or(depth_indices.clone().greater_equal_elem(depth as i64))
108 .bool_or(spatial_indices.clone().lower_elem(0))
109 .bool_or(spatial_indices.clone().greater_equal_elem(area as i64));
110 let indices = (safe_depth_indices.mul_scalar(area as i64)
111 + spatial_indices.clamp(0, area as i64 - 1)).mask_fill(invalid, -1);
112 (
113 depth_lines_volume(lines, batch, channels, height, width),
114 depth_lines_volume(indices, batch, channels, height, width),
115 )
116}
117
118pub fn max_pool3d<B: Backend>(input: Tensor<B, 5>, kernel_size: [usize; 3], stride: [usize; 3],
120 padding: [usize; 3], dilation: [usize; 3], ceil_mode: bool) -> Tensor<B, 5> {
121 Tensor::new(TensorPrimitive::Float(B::max_pool3d(input.primitive.tensor(),
122 kernel_size, stride, padding, dilation, ceil_mode)))
123}
124
125pub fn max_pool3d_with_indices<B: Backend>(input: Tensor<B, 5>, kernel_size: [usize; 3],
128 stride: [usize; 3], padding: [usize; 3], dilation: [usize; 3], ceil_mode: bool)
129 -> (Tensor<B, 5>, Tensor<B, 5, Int>) {
130 let [_, _, depth, height, width] = input.dims();
131 assert!(depth > 0 && height > 0 && width > 0, "volume pooling indices require non-empty spatial axes");
132 let volume = depth.checked_mul(height).and_then(|size| size.checked_mul(width))
133 .expect("pooling volume size overflow");
134 assert!(volume <= i64::MAX as usize, "volume pooling positions exceed I64");
135 let output = B::max_pool3d_with_indices(input.primitive.tensor(), kernel_size, stride,
136 padding, dilation, ceil_mode);
137 (Tensor::new(TensorPrimitive::Float(output.output)), Tensor::new(output.indices))
138}
139
140pub fn avg_pool3d<B: Backend>(
146 input: Tensor<B, 5>,
147 kernel_size: [usize; 3],
148 stride: [usize; 3],
149 padding: [usize; 3],
150 count_include_pad: bool,
151 ceil_mode: bool,
152) -> Tensor<B, 5> {
153 Tensor::new(TensorPrimitive::Float(B::avg_pool3d(input.primitive.tensor(), kernel_size,
154 stride, padding, count_include_pad, ceil_mode)))
155}
156
157pub fn adaptive_avg_pool3d<B: Backend>(
163 input: Tensor<B, 5>,
164 output_size: [usize; 3],
165) -> Tensor<B, 5> {
166 Tensor::new(TensorPrimitive::Float(B::adaptive_avg_pool3d(input.primitive.tensor(), output_size)))
167}
168
169fn average_excluding_explicit_padding<B: Backend, const D: usize, const N: usize>(
170 input: Tensor<B, D>,
171 padding: [(usize, usize); N],
172 pool: impl Fn(Tensor<B, D>) -> Tensor<B, D>,
173) -> Tensor<B, D> {
174 let storage = input.dtype();
175 let compute = if storage == DType::F64 { DType::F64 } else { DType::F32 };
176 let mut visible_shape = input.dims();
177 visible_shape[0] = 1;
178 visible_shape[1] = 1;
179 let visible = Tensor::<B, D>::ones(visible_shape, (&input.device(), compute))
180 .pad(padding, PadMode::Constant(0.0));
181 let values = pool(input.cast(compute).pad(padding, PadMode::Constant(0.0)));
182 let coverage = pool(visible);
183 (values / coverage).cast(storage)
184}
185
186pub fn avg_pool1d_padded<B: Backend>(
192 input: Tensor<B, 3>,
193 kernel_size: usize,
194 stride: usize,
195 padding: [(usize, usize); 1],
196 count_include_pad: bool,
197 ceil_mode: bool,
198) -> Tensor<B, 3> {
199 let [(left, right)] = padding;
200 if left == right {
201 return avg_pool1d(input, kernel_size, stride, left, count_include_pad, ceil_mode);
202 }
203 if count_include_pad {
204 return avg_pool1d(input.pad(padding, PadMode::Constant(0.0)),
205 kernel_size, stride, 0, true, ceil_mode);
206 }
207 average_excluding_explicit_padding(input, padding,
208 |input| avg_pool1d(input, kernel_size, stride, 0, true, ceil_mode))
209}
210
211pub fn avg_pool2d_padded<B: Backend>(
216 input: Tensor<B, 4>,
217 kernel_size: [usize; 2],
218 stride: [usize; 2],
219 padding: [(usize, usize); 2],
220 count_include_pad: bool,
221 ceil_mode: bool,
222) -> Tensor<B, 4> {
223 if padding.iter().all(|(start, end)| start == end) {
224 return avg_pool2d(input, kernel_size, stride, padding.map(|(start, _)| start),
225 count_include_pad, ceil_mode);
226 }
227 if count_include_pad {
228 return avg_pool2d(input.pad(padding, PadMode::Constant(0.0)),
229 kernel_size, stride, [0; 2], true, ceil_mode);
230 }
231 average_excluding_explicit_padding(input, padding,
232 |input| avg_pool2d(input, kernel_size, stride, [0; 2], true, ceil_mode))
233}
234
235pub fn avg_pool3d_padded<B: Backend>(
239 input: Tensor<B, 5>,
240 kernel_size: [usize; 3],
241 stride: [usize; 3],
242 padding: [(usize, usize); 3],
243 count_include_pad: bool,
244 ceil_mode: bool,
245) -> Tensor<B, 5> {
246 if padding.iter().all(|(start, end)| start == end) {
247 return avg_pool3d(input, kernel_size, stride, padding.map(|(start, _)| start),
248 count_include_pad, ceil_mode);
249 }
250 if count_include_pad {
251 return avg_pool3d(input.pad(padding, PadMode::Constant(0.0)),
252 kernel_size, stride, [0; 3], true, ceil_mode);
253 }
254 average_excluding_explicit_padding(input, padding,
255 |input| avg_pool3d(input, kernel_size, stride, [0; 3], true, ceil_mode))
256}
257
258pub fn max_pool1d_padded<B: Backend>(
260 input: Tensor<B, 3>,
261 kernel_size: usize,
262 stride: usize,
263 padding: [(usize, usize); 1],
264 dilation: usize,
265 ceil_mode: bool,
266) -> Tensor<B, 3> {
267 let [(left, right)] = padding;
268 let (input, padding) = if left == right {
269 (input, left)
270 } else {
271 (input.pad(padding, PadMode::Constant(f32::NEG_INFINITY)), 0)
272 };
273 max_pool1d(input, kernel_size, stride, padding, dilation, ceil_mode)
274}
275
276pub fn max_pool2d_padded<B: Backend>(
278 input: Tensor<B, 4>,
279 kernel_size: [usize; 2],
280 stride: [usize; 2],
281 padding: [(usize, usize); 2],
282 dilation: [usize; 2],
283 ceil_mode: bool,
284) -> Tensor<B, 4> {
285 let (input, padding) = if padding.iter().all(|(start, end)| start == end) {
286 (input, padding.map(|(start, _)| start))
287 } else {
288 (input.pad(padding, PadMode::Constant(f32::NEG_INFINITY)), [0; 2])
289 };
290 max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
291}
292
293pub fn max_pool3d_padded<B: Backend>(
295 input: Tensor<B, 5>,
296 kernel_size: [usize; 3],
297 stride: [usize; 3],
298 padding: [(usize, usize); 3],
299 dilation: [usize; 3],
300 ceil_mode: bool,
301) -> Tensor<B, 5> {
302 let (input, padding) = if padding.iter().all(|(start, end)| start == end) {
303 (input, padding.map(|(start, _)| start))
304 } else {
305 (input.pad(padding, PadMode::Constant(f32::NEG_INFINITY)), [0; 3])
306 };
307 max_pool3d(input, kernel_size, stride, padding, dilation, ceil_mode)
308}
309
310fn unpad_pool_indices<B: Backend, const D: usize, const N: usize>(
311 indices: Tensor<B, D, Int>,
312 input_size: [usize; N],
313 padding: [(usize, usize); N],
314) -> Tensor<B, D, Int> {
315 let padded_size: [usize; N] = core::array::from_fn(|axis| {
316 input_size[axis].checked_add(padding[axis].0)
317 .and_then(|size| size.checked_add(padding[axis].1))
318 .expect("padded pooling index extent overflow")
319 });
320 let padded_volume = padded_size.iter().try_fold(1usize,
321 |size, axis| size.checked_mul(*axis)).expect("padded pooling index volume overflow");
322 let input_volume = input_size.iter().try_fold(1usize,
323 |size, axis| size.checked_mul(*axis)).expect("pooling input index volume overflow");
324 assert!(padded_volume > 0 && padded_volume <= i64::MAX as usize
325 && input_volume <= i64::MAX as usize, "pooling indices cannot be represented in I64");
326 let indices = indices.cast(DType::I64);
327 if input_volume == 0 {
328 return indices.zeros_like().sub_scalar(1);
329 }
330 let mut invalid = indices.clone().lower_elem(0)
331 .bool_or(indices.clone().greater_equal_elem(padded_volume as i64));
332 let mut remaining = indices.clone().clamp(0, padded_volume as i64 - 1);
333 let mut unpadded = indices.zeros_like();
334 let mut input_stride = 1usize;
335 for axis in (0..N).rev() {
336 let coordinate = remaining.clone().remainder_scalar(padded_size[axis] as i64)
337 .sub_scalar(padding[axis].0 as i64);
338 remaining = remaining.div_scalar(padded_size[axis] as i64);
339 invalid = invalid.bool_or(coordinate.clone().lower_elem(0))
340 .bool_or(coordinate.clone().greater_equal_elem(input_size[axis] as i64));
341 unpadded = unpadded + coordinate.clamp(0, input_size[axis].saturating_sub(1) as i64)
342 .mul_scalar(input_stride as i64);
343 input_stride = input_stride.checked_mul(input_size[axis])
344 .expect("pooling input index stride overflow");
345 }
346 unpadded.mask_fill(invalid, -1)
347}
348
349pub fn max_pool1d_with_indices_padded<B: Backend>(
355 input: Tensor<B, 3>,
356 kernel_size: usize,
357 stride: usize,
358 padding: [(usize, usize); 1],
359 dilation: usize,
360 ceil_mode: bool,
361) -> (Tensor<B, 3>, Tensor<B, 3, Int>) {
362 let [(left, right)] = padding;
363 if left == right {
364 return max_pool1d_with_indices(input, kernel_size, stride, left, dilation, ceil_mode);
365 }
366 let [_, _, length] = input.dims();
367 let (values, indices) = max_pool1d_with_indices(
368 input.pad(padding, PadMode::Constant(f32::NEG_INFINITY)),
369 kernel_size, stride, 0, dilation, ceil_mode,
370 );
371 (values, unpad_pool_indices(indices, [length], padding))
372}
373
374pub fn max_pool2d_with_indices_padded<B: Backend>(
378 input: Tensor<B, 4>,
379 kernel_size: [usize; 2],
380 stride: [usize; 2],
381 padding: [(usize, usize); 2],
382 dilation: [usize; 2],
383 ceil_mode: bool,
384) -> (Tensor<B, 4>, Tensor<B, 4, Int>) {
385 if padding.iter().all(|(start, end)| start == end) {
386 return max_pool2d_with_indices(input, kernel_size, stride,
387 padding.map(|(start, _)| start), dilation, ceil_mode);
388 }
389 let [_, _, height, width] = input.dims();
390 let (values, indices) = max_pool2d_with_indices(
391 input.pad(padding, PadMode::Constant(f32::NEG_INFINITY)),
392 kernel_size, stride, [0; 2], dilation, ceil_mode,
393 );
394 (values, unpad_pool_indices(indices, [height, width], padding))
395}
396
397pub fn max_pool3d_with_indices_padded<B: Backend>(
402 input: Tensor<B, 5>,
403 kernel_size: [usize; 3],
404 stride: [usize; 3],
405 padding: [(usize, usize); 3],
406 dilation: [usize; 3],
407 ceil_mode: bool,
408) -> (Tensor<B, 5>, Tensor<B, 5, Int>) {
409 if padding.iter().all(|(start, end)| start == end) {
410 return max_pool3d_with_indices(input, kernel_size, stride,
411 padding.map(|(start, _)| start), dilation, ceil_mode);
412 }
413 let [_, _, depth, height, width] = input.dims();
414 let (values, indices) = max_pool3d_with_indices(
415 input.pad(padding, PadMode::Constant(f32::NEG_INFINITY)),
416 kernel_size, stride, [0; 3], dilation, ceil_mode,
417 );
418 (values, unpad_pool_indices(indices, [depth, height, width], padding))
419}