1use tract_linalg::mmm::{
2 EagerPackedInput, MMMInputFormat, MMMInputValue, MatMatMul, PackedExoticFact,
3 PackedMatrixStorage,
4};
5use tract_linalg::pack::{PackedFormat, PackedI8K4, PackingWriter};
6
7use crate::internal::*;
8use ndarray::prelude::*;
9use num_integer::Integer;
10
11use crate::ops::cnn::pools::{ConcretePoolGeometry, PoolGeometry};
12use crate::ops::cnn::{GeometryBound, PoolSpec, ResolveTo};
13use crate::ops::nn::{BaseDataShape, DataFormat, DataShape};
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct Im2Col {
17 pub pool_spec: PoolSpec,
18 pub group: usize,
19 geometry: GeometryBound<SymbolicGeometry, ConcreteGeometry>,
20}
21
22#[derive(Debug, Clone, Hash, PartialEq, Eq)]
23struct SymbolicGeometry {
24 group: usize,
25 pool_spec: PoolSpec,
26 pool_geometry: PoolGeometry,
27 out_format: Box<dyn MMMInputFormat>,
29 k: usize,
30}
31
32#[derive(Debug, Clone, Hash, PartialEq, Eq)]
33struct ConcreteGeometry {
34 pool: ConcretePoolGeometry,
35 pub n: usize,
36 k: usize,
37 pub out_format: Box<dyn MMMInputFormat>,
38 pub ci_per_group: usize,
39 patcher: Patcher,
40 input_shape_with_n: DataShape,
41 packed_shape: TVec<usize>, }
43
44impl GeometryBound<SymbolicGeometry, ConcreteGeometry> {
45 pub fn out_format(&self) -> &dyn MMMInputFormat {
46 match self {
47 GeometryBound::Symbolic(s) => &*s.out_format,
48 GeometryBound::Concrete(s) => &*s.out_format,
49 }
50 }
51 pub fn k(&self) -> usize {
52 match self {
53 GeometryBound::Symbolic(s) => s.k,
54 GeometryBound::Concrete(s) => s.k,
55 }
56 }
57}
58
59impl ResolveTo<ConcreteGeometry> for SymbolicGeometry {
60 type Param = [usize];
61 fn resolve(&self, input_full_shape: &[usize]) -> TractResult<ConcreteGeometry> {
62 let pool = self.pool_geometry.to_concrete(input_full_shape)?.into_owned();
63 let patcher = match (pool.patch.rank(), pool.patch.padded) {
64 (1, false) => Patcher::Valid1d,
65 (1, true) => Patcher::Padded1d,
66 (2, false) => Patcher::Valid2d,
67 (2, true) => Patcher::Padded2d,
68 _ => Patcher::Generic,
69 };
70 let ci_per_group = pool.input_shape.c_dim() / self.group;
71 let n = pool.output_shape.hw_dims().iter().product();
72 let input_shape_with_n = match self.pool_spec.data_format {
73 DataFormat::HWC => DataFormat::NHWC.from_n_c_hw(
74 1,
75 *pool.input_shape.c(),
76 pool.input_shape.hw_dims(),
77 )?,
78 DataFormat::CHW => DataFormat::NCHW.from_n_c_hw(
79 1,
80 *pool.input_shape.c(),
81 pool.input_shape.hw_dims(),
82 )?,
83 _ => pool.input_shape.clone(),
84 };
85 let packed_shape = Im2Col::packed_shape(&pool.input_shape, self.group)?;
86 Ok(ConcreteGeometry {
87 pool,
88 n,
89 k: self.k,
90 ci_per_group,
91 out_format: self.out_format.clone(),
92 patcher,
93 input_shape_with_n,
94 packed_shape,
95 })
96 }
97}
98
99impl Im2Col {
100 pub fn new(
101 pool_spec: PoolSpec,
102 group: usize,
103 k: usize,
104 input_full_shape: &ShapeFact,
105 mmm: Box<dyn MatMatMul>,
106 packing: usize,
107 ) -> TractResult<Im2Col> {
108 let out_format = dyn_clone::clone_box(&*mmm.packings()[packing].1);
109 let pool_geometry = pool_spec.compute_geo(input_full_shape)?;
110 let geometry: GeometryBound<_, _> =
111 SymbolicGeometry { group, pool_spec: pool_spec.clone(), pool_geometry, out_format, k }
112 .into();
113 let geometry = geometry.optimize_if(input_full_shape.as_concrete())?;
114 Ok(Im2Col { pool_spec, group, geometry })
115 }
116
117 fn packed_shape<D: DimLike>(
119 input_shape: &BaseDataShape<D, TVec<D>>,
120 group: usize,
121 ) -> TractResult<TVec<D>> {
122 let mut output_shape: TVec<D> = tvec!();
123 output_shape.push(input_shape.n().cloned().unwrap_or_else(|| 1.into()));
124 output_shape.push(group.into());
125 Ok(output_shape)
126 }
127}
128
129impl Op for Im2Col {
130 fn name(&self) -> StaticName {
131 "Im2col".into()
132 }
133
134 fn info(&self) -> TractResult<Vec<String>> {
135 Ok(vec![format!("groups:{}", self.group)])
136 }
137
138 op_as_typed_op!();
139}
140
141impl EvalOp for Im2Col {
142 fn is_stateless(&self) -> bool {
143 true
144 }
145
146 fn eval(&self, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
147 let geometry = self.geometry.to_concrete(inputs[0].shape())?;
148 unsafe {
149 let mut input = inputs.remove(0).into_tensor();
150 let pad_value: Option<&Tensor> = if inputs.len() > 0 { Some(&inputs[0]) } else { None };
151 if !self.pool_spec.data_format.has_n() {
152 input.insert_axis(0)?;
153 }
154 let dt = input.datum_type();
155 let r = geometry.out_format.r();
156 let (single_panel_len, buf_align, zero_init) =
163 if let Some(pf) = geometry.out_format.downcast_ref::<PackedFormat>() {
164 (pf.single_panel_len(geometry.k), pf.alignment(), false)
165 } else if let Some(p4) = geometry.out_format.downcast_ref::<PackedI8K4>() {
166 (p4.single_panel_len(geometry.k), p4.alignment(), true)
167 } else {
168 bail!("Im2Col: unsupported packing format {:?}", geometry.out_format)
169 };
170 let panel_bytes = single_panel_len * dt.size_of();
171
172 let n_batches = *geometry.input_shape_with_n.n().unwrap_or(&1);
173 let n_groups = self.group;
174 let mut values: TVec<Box<dyn MMMInputValue>> =
175 TVec::with_capacity(n_batches * n_groups);
176
177 for i in 0..n_batches {
178 let input = input.view_at_prefix(&[i])?;
179 for g in 0..n_groups {
180 let n =
181 if geometry.pool.output_shape.shape.contains(&0) { 0 } else { geometry.n };
182 let mut data = Tensor::uninitialized_aligned_dt(
183 dt,
184 &[n.divceil(r) * single_panel_len],
185 buf_align,
186 )?;
187 if zero_init {
188 data.as_bytes_mut().fill(0);
189 } else if n % r != 0 {
190 data.as_bytes_mut()[(n / r) * panel_bytes..].fill(0);
191 }
192 if n > 0 {
193 dispatch_copy_by_size!(Patcher::patch(dt)(
194 &geometry.patcher,
195 &geometry,
196 &input,
197 &mut data.view_mut(),
198 g,
199 pad_value
200 ))?;
201 }
202 values.push(Box::new(EagerPackedInput {
203 fact: PackedExoticFact {
204 format: geometry.out_format.clone(),
205 k: geometry.k,
206 mn: n.to_dim(),
207 },
208 packed: data.into_blob()?.into(),
209 panel_bytes: if n > 0 { panel_bytes } else { 0 },
210 mn: n,
211 }));
212 }
213 }
214
215 let output = PackedMatrixStorage::new_batched(&geometry.packed_shape, values)
216 .into_tensor(input.datum_type());
217 Ok(tvec!(output.into_tvalue()))
218 }
219 }
220}
221
222impl TypedOp for Im2Col {
223 as_op!();
224
225 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
226 let input_shape = self.pool_spec.data_format.shape(inputs[0].shape.to_tvec())?;
227 let output_shape = self.pool_spec.output_shape(&inputs[0].shape)?;
228 let mn = output_shape.hw_dims().iter().product::<TDim>();
229 let pof = PackedExoticFact {
230 format: dyn_clone::clone_box(self.geometry.out_format()),
231 k: self.geometry.k(),
232 mn,
233 };
234 Ok(tvec!(
235 inputs[0]
236 .datum_type
237 .fact(&[input_shape.n().cloned().unwrap_or(1.into()), self.group.into()])
238 .with_exotic_fact(pof)
239 ))
240 }
241
242 fn declutter(
243 &self,
244 model: &TypedModel,
245 node: &TypedNode,
246 ) -> TractResult<Option<TypedModelPatch>> {
247 let input_fact = model.outlet_fact(node.inputs[0])?;
248 if node.inputs.len() == 2
249 && model.outlet_fact(node.inputs[1])?.konst.as_ref().and_then(|t| t.as_uniform())
250 == Some(Tensor::zero_scalar_dt(input_fact.datum_type)?)
251 {
252 Ok(Some(
253 TypedModelPatch::replace_single_op(model, node, &node.inputs[0..1], self.clone())?
254 .with_context("b0 is zero"),
255 ))
256 } else {
257 Ok(None)
258 }
259 }
260}
261
262#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
263enum Patcher {
264 Generic,
265 Valid1d,
266 Valid2d,
267 Padded1d,
268 Padded2d,
269}
270
271impl Patcher {
272 fn patch<'p, T: Copy + Datum + num_traits::Zero>(
273 &self,
274 geo: &'p ConcreteGeometry,
275 input: &TensorView,
276 pack: &'p mut TensorView,
277 g: usize,
278 pad_value: Option<&Tensor>,
279 ) -> TractResult<()> {
280 let ptr = unsafe { pack.as_slice_mut_unchecked::<T>().as_mut_ptr() };
284 if let Some(pf) = geo.out_format.downcast_ref::<PackedFormat>() {
285 let mut w = pf.write_with_k_outer(ptr, geo.k, geo.n);
286 self.run::<T, _>(geo, input, g, pad_value, &mut w)
287 } else if let Some(p4) = geo.out_format.downcast_ref::<PackedI8K4>() {
288 let mut w = p4.write_with_k_outer(ptr, geo.k, geo.n);
289 self.run::<T, _>(geo, input, g, pad_value, &mut w)
290 } else {
291 bail!("Im2Col: unsupported packing format {:?}", geo.out_format)
292 }
293 }
294
295 fn run<T: Copy + Datum + num_traits::Zero, W: PackingWriter<T>>(
296 &self,
297 geo: &ConcreteGeometry,
298 input: &TensorView,
299 g: usize,
300 pad_value: Option<&Tensor>,
301 writer: &mut W,
302 ) -> TractResult<()> {
303 match self {
304 Patcher::Valid1d => Self::valid_1d::<T, W>(geo, input, g, writer),
305 Patcher::Valid2d => Self::valid_2d::<T, W>(geo, input, g, writer),
306 Patcher::Padded1d => Self::padded_1d::<T, W>(
307 geo,
308 input,
309 g,
310 pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),
311 writer,
312 ),
313 Patcher::Padded2d => Self::padded_2d::<T, W>(
314 geo,
315 input,
316 g,
317 pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),
318 writer,
319 ),
320 _ => Self::generic::<T, W>(
321 geo,
322 input,
323 g,
324 pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),
325 writer,
326 ),
327 }
328 }
329
330 #[inline(never)]
331 fn generic<T: Copy + Datum, W: PackingWriter<T>>(
332 geometry: &ConcreteGeometry,
333 input: &TensorView,
334 g: usize,
335 pad_value: &Tensor,
336 writer: &mut W,
337 ) -> TractResult<()> {
338 unsafe {
339 let pad_value = *pad_value.to_scalar_unchecked();
340 let mut mega_matrix = Tensor::uninitialized::<T>(&[geometry.k, geometry.n])?;
341 let mut mega_matrix_view = mega_matrix.to_array_view_mut_unchecked::<T>();
342 let ptr = input.as_ptr_unchecked::<T>();
343 let ptr = ptr.add(geometry.input_shape_with_n.c_stride() * (g * geometry.ci_per_group));
344 for (spatial, mut col) in ndarray::indices(&*geometry.pool.patch.output_shape)
345 .into_iter()
346 .zip(mega_matrix_view.axis_iter_mut(Axis(1)))
347 {
348 let mut col = col.iter_mut();
349 for ci in 0..geometry.ci_per_group {
350 let ptr = ptr.add(geometry.input_shape_with_n.c_stride() * ci);
351 for v in geometry.pool.patch.at(spatial.slice()) {
352 *col.next().expect("geometry error in conv") =
353 v.map(|o| *ptr.offset(o)).unwrap_or(pad_value);
354 }
355 }
356 }
357 let mv = mega_matrix.as_slice_unchecked::<T>();
361 for kk in 0..geometry.k {
362 writer.write_slice(&mv[kk * geometry.n..(kk + 1) * geometry.n]);
363 }
364 Ok(())
365 }
366 }
367
368 #[inline(never)]
369 fn valid_1d<T: Copy + Datum, W: PackingWriter<T>>(
370 geometry: &ConcreteGeometry,
371 input: &TensorView,
372 g: usize,
373 writer: &mut W,
374 ) -> TractResult<()> {
375 unsafe {
376 let x_stride = *geometry.input_shape_with_n.h_stride() as isize
377 * geometry.pool.patch.spec.strides[0] as isize;
378 let c_stride = *geometry.input_shape_with_n.c_stride() as isize;
379 let iptr = input.as_ptr_unchecked::<T>();
380 let iptr = iptr.add(g * geometry.ci_per_group * geometry.input_shape_with_n.c_stride());
381 let output_x = *geometry.pool.patch.output_shape.get_unchecked(0);
382 let contiguous_x = x_stride == 1;
387 for ci in 0..geometry.ci_per_group {
388 let iptr = iptr.offset(ci as isize * c_stride);
389 for koffset in &geometry.pool.patch.standard_layout_data_field {
390 let iptr = iptr.offset(*koffset);
391 if contiguous_x {
392 let row = std::slice::from_raw_parts(iptr, output_x);
393 writer.write_slice(row);
394 } else {
395 let mut iptr_x = iptr;
397 for _ in 0..output_x {
398 writer.write(*iptr_x);
399 iptr_x = iptr_x.offset(x_stride);
400 }
401 }
402 }
403 }
404 Ok(())
405 }
406 }
407
408 #[inline(never)]
409 fn padded_1d<T: Copy + Datum, W: PackingWriter<T>>(
410 geometry: &ConcreteGeometry,
411 input: &TensorView,
412 g: usize,
413 pad_value: &Tensor,
414 writer: &mut W,
415 ) -> TractResult<()> {
416 unsafe {
417 let pad_value = *pad_value.to_scalar_unchecked();
418 let shape = &geometry.input_shape_with_n;
419 let x_stride = geometry.pool.patch.spec.strides[0] as isize;
420 let x_stride_ptr = x_stride * *shape.h_stride() as isize;
421 let c_stride_ptr = *shape.c_stride() as isize;
422 let input_width = shape.hw_dims()[0] as isize;
423 let kernel_len = geometry.pool.patch.standard_layout_data_field.len();
424 let iptr = input.as_ptr_unchecked::<T>();
425 let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());
426 let output_width = *geometry.pool.patch.output_shape.get_unchecked(0);
427 for ci in 0..geometry.ci_per_group {
428 let iptr = iptr.offset(ci as isize * c_stride_ptr);
429 for kitem in 0..kernel_len {
430 let dx = *geometry.pool.patch.data_field.as_ptr().add(kitem);
431 let valid_x_start =
432 Integer::div_ceil(&-dx, &x_stride).max(0).min(output_width as _);
433 let valid_x_end = Integer::div_ceil(&(input_width - dx), &x_stride)
434 .max(0)
435 .min(output_width as _);
436 let iptr = iptr.offset(
437 *geometry.pool.patch.standard_layout_data_field.get_unchecked(kitem),
438 );
439 Self::padded_invalid_x_loop(valid_x_start as usize, pad_value, &mut *writer);
440 Self::padded_valid_x_loop(
441 valid_x_start,
442 valid_x_end,
443 x_stride_ptr,
444 iptr,
445 &mut *writer,
446 );
447 Self::padded_invalid_x_loop(
448 output_width - valid_x_end as usize,
449 pad_value,
450 &mut *writer,
451 );
452 }
453 }
454 }
455 Ok(())
456 }
457
458 #[inline(never)]
459 fn padded_2d<T: Copy + Datum, W: PackingWriter<T>>(
460 geometry: &ConcreteGeometry,
461 input: &TensorView,
462 g: usize,
463 pad_value: &Tensor,
464 writer: &mut W,
465 ) -> TractResult<()> {
466 unsafe {
467 let pad_value = *pad_value.to_scalar_unchecked();
468 let y_stride = geometry.pool.patch.spec.strides[0] as isize;
469 let x_stride = geometry.pool.patch.spec.strides[1] as isize;
470 let shape = &geometry.input_shape_with_n;
471 let y_stride_ptr = y_stride * *shape.h_stride() as isize;
472 let x_stride_ptr = x_stride * *shape.w_stride() as isize;
473 let c_stride_ptr = *shape.c_stride() as isize;
474 let input_heigth = shape.hw_dims()[0] as isize;
475 let input_width = shape.hw_dims()[1] as isize;
476 let kernel_len = geometry.pool.patch.standard_layout_data_field.len();
477 let iptr = input.as_ptr_unchecked::<T>();
478 let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());
479 let output_width = *geometry.pool.patch.output_shape.get_unchecked(1);
480 for ci in 0..geometry.ci_per_group {
481 let iptr = iptr.offset(ci as isize * c_stride_ptr);
482 for kitem in 0..kernel_len {
483 let dy = *geometry.pool.patch.data_field.as_ptr().offset(kitem as isize * 2);
484 let dx =
485 *geometry.pool.patch.data_field.as_ptr().offset(1 + kitem as isize * 2);
486 let valid_x_start =
487 Integer::div_ceil(&-dx, &x_stride).max(0).min(output_width as _);
488 let valid_x_end = Integer::div_ceil(&(input_width - dx), &x_stride)
489 .max(0)
490 .min(output_width as _);
491
492 let iptr = iptr.offset(
493 *geometry.pool.patch.standard_layout_data_field.get_unchecked(kitem),
494 );
495 for yo in 0..*geometry.pool.patch.output_shape.get_unchecked(0) {
496 let y = yo as isize * y_stride + dy;
497 let iptr = iptr.offset(yo as isize * y_stride_ptr);
498 if y >= 0 && y < input_heigth {
499 Self::padded_invalid_x_loop(
500 valid_x_start as usize,
501 pad_value,
502 &mut *writer,
503 );
504 Self::padded_valid_x_loop(
505 valid_x_start,
506 valid_x_end,
507 x_stride_ptr,
508 iptr,
509 &mut *writer,
510 );
511 Self::padded_invalid_x_loop(
512 output_width - valid_x_end as usize,
513 pad_value,
514 &mut *writer,
515 );
516 } else {
517 Self::padded_invalid_x_loop(output_width, pad_value, &mut *writer);
518 }
519 }
520 }
521 }
522 }
523 Ok(())
524 }
525
526 #[inline(never)]
527 unsafe fn padded_invalid_x_loop<T: Copy + Datum, W: PackingWriter<T>>(
528 count: usize,
529 pad_value: T,
530 writer: &mut W,
531 ) {
532 for _ in 0..count {
533 writer.write(pad_value);
534 }
535 }
536
537 #[inline(never)]
538 unsafe fn padded_valid_x_loop<T: Copy + Datum, W: PackingWriter<T>>(
539 x_min: isize,
540 x_max: isize,
541 x_stride_ptr: isize,
542 iptr: *const T,
543 writer: &mut W,
544 ) {
545 if x_stride_ptr == 1 && x_max > x_min {
549 unsafe {
550 let row = std::slice::from_raw_parts(iptr.offset(x_min), (x_max - x_min) as usize);
551 writer.write_slice(row);
552 }
553 } else {
554 for x in x_min..x_max {
555 writer.write(unsafe { *iptr.offset(x * x_stride_ptr) });
556 }
557 }
558 }
559
560 #[inline(never)]
561 fn valid_2d<T: Copy + Datum, W: PackingWriter<T>>(
562 geometry: &ConcreteGeometry,
563 input: &TensorView,
564 g: usize,
565 writer: &mut W,
566 ) -> TractResult<()> {
567 unsafe {
568 let shape = &geometry.input_shape_with_n;
569 let y_stride = geometry.pool.patch.spec.strides[0] as isize;
570 let x_stride = geometry.pool.patch.spec.strides[1] as isize;
571 let y_stride_ptr = y_stride * *shape.h_stride() as isize;
572 let x_stride_ptr = x_stride * *shape.w_stride() as isize;
573 let c_stride_ptr = *shape.c_stride() as isize;
574 let iptr = input.as_ptr_unchecked::<T>();
575 let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());
576 let output_y = *geometry.pool.patch.output_shape.get_unchecked(0);
577 let output_x = *geometry.pool.patch.output_shape.get_unchecked(1);
578 let contiguous_x = x_stride_ptr == 1;
582 for ci in 0..geometry.ci_per_group {
583 let iptr = iptr.offset(ci as isize * c_stride_ptr);
584 for koffset in &geometry.pool.patch.standard_layout_data_field {
585 let iptr = iptr.offset(*koffset);
586 let mut iptr_y = iptr;
587 for _ in 0..output_y {
588 if contiguous_x {
589 let row = std::slice::from_raw_parts(iptr_y, output_x);
590 writer.write_slice(row);
591 } else {
592 let mut iptr_x = iptr_y;
594 for _ in 0..output_x {
595 writer.write(*iptr_x);
596 iptr_x = iptr_x.offset(x_stride_ptr);
597 }
598 }
599 iptr_y = iptr_y.offset(y_stride_ptr);
600 }
601 }
602 }
603 Ok(())
604 }
605 }
606}