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 op_out_of_plan!();
143
144 fn eval(&self, _ctx: &EvalContext, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
145 let geometry = self.geometry.to_concrete(inputs[0].shape())?;
146 unsafe {
147 let mut input = inputs.remove(0).into_tensor();
148 let pad_value: Option<&Tensor> = if inputs.len() > 0 { Some(&inputs[0]) } else { None };
149 if !self.pool_spec.data_format.has_n() {
150 input.insert_axis(0)?;
151 }
152 let dt = input.datum_type();
153 let r = geometry.out_format.r();
154 let (single_panel_len, buf_align, zero_init) =
161 if let Some(pf) = geometry.out_format.downcast_ref::<PackedFormat>() {
162 (pf.single_panel_len(geometry.k), pf.alignment(), false)
163 } else if let Some(p4) = geometry.out_format.downcast_ref::<PackedI8K4>() {
164 (p4.single_panel_len(geometry.k), p4.alignment(), true)
165 } else {
166 bail!("Im2Col: unsupported packing format {:?}", geometry.out_format)
167 };
168 let panel_bytes = single_panel_len * dt.size_of();
169
170 let n_batches = *geometry.input_shape_with_n.n().unwrap_or(&1);
171 let n_groups = self.group;
172 let mut values: TVec<Box<dyn MMMInputValue>> =
173 TVec::with_capacity(n_batches * n_groups);
174
175 for i in 0..n_batches {
176 let input = input.view_at_prefix(&[i])?;
177 for g in 0..n_groups {
178 let n =
179 if geometry.pool.output_shape.shape.contains(&0) { 0 } else { geometry.n };
180 let mut data = Tensor::uninitialized_aligned_dt(
181 dt,
182 &[n.divceil(r) * single_panel_len],
183 buf_align,
184 )?;
185 if zero_init {
186 data.as_bytes_mut().fill(0);
187 } else if n % r != 0 {
188 data.as_bytes_mut()[(n / r) * panel_bytes..].fill(0);
189 }
190 if n > 0 {
191 dispatch_copy_by_size!(Patcher::patch(dt)(
192 &geometry.patcher,
193 &geometry,
194 &input,
195 &mut data.view_mut(),
196 g,
197 pad_value
198 ))?;
199 }
200 values.push(Box::new(EagerPackedInput {
201 fact: PackedExoticFact {
202 format: geometry.out_format.clone(),
203 k: geometry.k,
204 mn: n.to_dim(),
205 },
206 packed: data.into_blob()?.into(),
207 panel_bytes: if n > 0 { panel_bytes } else { 0 },
208 mn: n,
209 }));
210 }
211 }
212
213 let output = PackedMatrixStorage::new_batched(&geometry.packed_shape, values)
214 .into_tensor(input.datum_type());
215 Ok(tvec!(output.into_tvalue()))
216 }
217 }
218}
219
220impl TypedOp for Im2Col {
221 as_op!();
222
223 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
224 let input_shape = self.pool_spec.data_format.shape(inputs[0].shape.to_tvec())?;
225 let output_shape = self.pool_spec.output_shape(&inputs[0].shape)?;
226 let mn = output_shape.hw_dims().iter().product::<TDim>();
227 let pof = PackedExoticFact {
228 format: dyn_clone::clone_box(self.geometry.out_format()),
229 k: self.geometry.k(),
230 mn,
231 };
232 Ok(tvec!(
233 inputs[0]
234 .datum_type
235 .fact(&[input_shape.n().cloned().unwrap_or(1.into()), self.group.into()])
236 .with_exotic_fact(pof)
237 ))
238 }
239
240 fn declutter(
241 &self,
242 model: &TypedModel,
243 node: &TypedNode,
244 ) -> TractResult<Option<TypedModelPatch>> {
245 let input_fact = model.outlet_fact(node.inputs[0])?;
246 if node.inputs.len() == 2
247 && model.outlet_fact(node.inputs[1])?.konst.as_ref().and_then(|t| t.as_uniform())
248 == Some(Tensor::zero_scalar_dt(input_fact.datum_type)?)
249 {
250 Ok(Some(
251 TypedModelPatch::replace_single_op(model, node, &node.inputs[0..1], self.clone())?
252 .with_context("b0 is zero"),
253 ))
254 } else {
255 Ok(None)
256 }
257 }
258}
259
260#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
261enum Patcher {
262 Generic,
263 Valid1d,
264 Valid2d,
265 Padded1d,
266 Padded2d,
267}
268
269impl Patcher {
270 fn patch<'p, T: Copy + Datum + num_traits::Zero>(
271 &self,
272 geo: &'p ConcreteGeometry,
273 input: &TensorView,
274 pack: &'p mut TensorView,
275 g: usize,
276 pad_value: Option<&Tensor>,
277 ) -> TractResult<()> {
278 let ptr = unsafe { pack.as_slice_mut_unchecked::<T>().as_mut_ptr() };
282 if let Some(pf) = geo.out_format.downcast_ref::<PackedFormat>() {
283 let mut w = pf.write_with_k_outer(ptr, geo.k, geo.n);
284 self.run::<T, _>(geo, input, g, pad_value, &mut w)
285 } else if let Some(p4) = geo.out_format.downcast_ref::<PackedI8K4>() {
286 let mut w = p4.write_with_k_outer(ptr, geo.k, geo.n);
287 self.run::<T, _>(geo, input, g, pad_value, &mut w)
288 } else {
289 bail!("Im2Col: unsupported packing format {:?}", geo.out_format)
290 }
291 }
292
293 fn run<T: Copy + Datum + num_traits::Zero, W: PackingWriter<T>>(
294 &self,
295 geo: &ConcreteGeometry,
296 input: &TensorView,
297 g: usize,
298 pad_value: Option<&Tensor>,
299 writer: &mut W,
300 ) -> TractResult<()> {
301 match self {
302 Patcher::Valid1d => Self::valid_1d::<T, W>(geo, input, g, writer),
303 Patcher::Valid2d => Self::valid_2d::<T, W>(geo, input, g, writer),
304 Patcher::Padded1d => Self::padded_1d::<T, W>(
305 geo,
306 input,
307 g,
308 pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),
309 writer,
310 ),
311 Patcher::Padded2d => Self::padded_2d::<T, W>(
312 geo,
313 input,
314 g,
315 pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),
316 writer,
317 ),
318 _ => Self::generic::<T, W>(
319 geo,
320 input,
321 g,
322 pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),
323 writer,
324 ),
325 }
326 }
327
328 #[inline(never)]
329 fn generic<T: Copy + Datum, W: PackingWriter<T>>(
330 geometry: &ConcreteGeometry,
331 input: &TensorView,
332 g: usize,
333 pad_value: &Tensor,
334 writer: &mut W,
335 ) -> TractResult<()> {
336 unsafe {
337 let pad_value = *pad_value.to_scalar_unchecked();
338 let mut mega_matrix = Tensor::uninitialized::<T>(&[geometry.k, geometry.n])?;
339 let mut mega_matrix_view = mega_matrix.to_array_view_mut_unchecked::<T>();
340 let ptr = input.as_ptr_unchecked::<T>();
341 let ptr = ptr.add(geometry.input_shape_with_n.c_stride() * (g * geometry.ci_per_group));
342 for (spatial, mut col) in ndarray::indices(&*geometry.pool.patch.output_shape)
343 .into_iter()
344 .zip(mega_matrix_view.axis_iter_mut(Axis(1)))
345 {
346 let mut col = col.iter_mut();
347 for ci in 0..geometry.ci_per_group {
348 let ptr = ptr.add(geometry.input_shape_with_n.c_stride() * ci);
349 for v in geometry.pool.patch.at(spatial.slice()) {
350 *col.next().expect("geometry error in conv") =
351 v.map(|o| *ptr.offset(o)).unwrap_or(pad_value);
352 }
353 }
354 }
355 let mv = mega_matrix.as_slice_unchecked::<T>();
359 for kk in 0..geometry.k {
360 writer.write_slice(&mv[kk * geometry.n..(kk + 1) * geometry.n]);
361 }
362 Ok(())
363 }
364 }
365
366 #[inline(never)]
367 fn valid_1d<T: Copy + Datum, W: PackingWriter<T>>(
368 geometry: &ConcreteGeometry,
369 input: &TensorView,
370 g: usize,
371 writer: &mut W,
372 ) -> TractResult<()> {
373 unsafe {
374 let x_stride = *geometry.input_shape_with_n.h_stride() as isize
375 * geometry.pool.patch.spec.strides[0] as isize;
376 let c_stride = *geometry.input_shape_with_n.c_stride() as isize;
377 let iptr = input.as_ptr_unchecked::<T>();
378 let iptr = iptr.add(g * geometry.ci_per_group * geometry.input_shape_with_n.c_stride());
379 let output_x = *geometry.pool.patch.output_shape.get_unchecked(0);
380 let contiguous_x = x_stride == 1;
385 for ci in 0..geometry.ci_per_group {
386 let iptr = iptr.offset(ci as isize * c_stride);
387 for koffset in &geometry.pool.patch.standard_layout_data_field {
388 let iptr = iptr.offset(*koffset);
389 if contiguous_x {
390 let row = std::slice::from_raw_parts(iptr, output_x);
391 writer.write_slice(row);
392 } else {
393 let mut iptr_x = iptr;
395 for _ in 0..output_x {
396 writer.write(*iptr_x);
397 iptr_x = iptr_x.offset(x_stride);
398 }
399 }
400 }
401 }
402 Ok(())
403 }
404 }
405
406 #[inline(never)]
407 fn padded_1d<T: Copy + Datum, W: PackingWriter<T>>(
408 geometry: &ConcreteGeometry,
409 input: &TensorView,
410 g: usize,
411 pad_value: &Tensor,
412 writer: &mut W,
413 ) -> TractResult<()> {
414 unsafe {
415 let pad_value = *pad_value.to_scalar_unchecked();
416 let shape = &geometry.input_shape_with_n;
417 let x_stride = geometry.pool.patch.spec.strides[0] as isize;
418 let x_stride_ptr = x_stride * *shape.h_stride() as isize;
419 let c_stride_ptr = *shape.c_stride() as isize;
420 let input_width = shape.hw_dims()[0] as isize;
421 let kernel_len = geometry.pool.patch.standard_layout_data_field.len();
422 let iptr = input.as_ptr_unchecked::<T>();
423 let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());
424 let output_width = *geometry.pool.patch.output_shape.get_unchecked(0);
425 for ci in 0..geometry.ci_per_group {
426 let iptr = iptr.offset(ci as isize * c_stride_ptr);
427 for kitem in 0..kernel_len {
428 let dx = *geometry.pool.patch.data_field.as_ptr().add(kitem);
429 let valid_x_start =
430 Integer::div_ceil(&-dx, &x_stride).max(0).min(output_width as _);
431 let valid_x_end = Integer::div_ceil(&(input_width - dx), &x_stride)
432 .max(0)
433 .min(output_width as _);
434 let iptr = iptr.offset(
435 *geometry.pool.patch.standard_layout_data_field.get_unchecked(kitem),
436 );
437 Self::padded_invalid_x_loop(valid_x_start as usize, pad_value, &mut *writer);
438 Self::padded_valid_x_loop(
439 valid_x_start,
440 valid_x_end,
441 x_stride_ptr,
442 iptr,
443 &mut *writer,
444 );
445 Self::padded_invalid_x_loop(
446 output_width - valid_x_end as usize,
447 pad_value,
448 &mut *writer,
449 );
450 }
451 }
452 }
453 Ok(())
454 }
455
456 #[inline(never)]
457 fn padded_2d<T: Copy + Datum, W: PackingWriter<T>>(
458 geometry: &ConcreteGeometry,
459 input: &TensorView,
460 g: usize,
461 pad_value: &Tensor,
462 writer: &mut W,
463 ) -> TractResult<()> {
464 unsafe {
465 let pad_value = *pad_value.to_scalar_unchecked();
466 let y_stride = geometry.pool.patch.spec.strides[0] as isize;
467 let x_stride = geometry.pool.patch.spec.strides[1] as isize;
468 let shape = &geometry.input_shape_with_n;
469 let y_stride_ptr = y_stride * *shape.h_stride() as isize;
470 let x_stride_ptr = x_stride * *shape.w_stride() as isize;
471 let c_stride_ptr = *shape.c_stride() as isize;
472 let input_heigth = shape.hw_dims()[0] as isize;
473 let input_width = shape.hw_dims()[1] as isize;
474 let kernel_len = geometry.pool.patch.standard_layout_data_field.len();
475 let iptr = input.as_ptr_unchecked::<T>();
476 let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());
477 let output_width = *geometry.pool.patch.output_shape.get_unchecked(1);
478 for ci in 0..geometry.ci_per_group {
479 let iptr = iptr.offset(ci as isize * c_stride_ptr);
480 for kitem in 0..kernel_len {
481 let dy = *geometry.pool.patch.data_field.as_ptr().offset(kitem as isize * 2);
482 let dx =
483 *geometry.pool.patch.data_field.as_ptr().offset(1 + kitem as isize * 2);
484 let valid_x_start =
485 Integer::div_ceil(&-dx, &x_stride).max(0).min(output_width as _);
486 let valid_x_end = Integer::div_ceil(&(input_width - dx), &x_stride)
487 .max(0)
488 .min(output_width as _);
489
490 let iptr = iptr.offset(
491 *geometry.pool.patch.standard_layout_data_field.get_unchecked(kitem),
492 );
493 for yo in 0..*geometry.pool.patch.output_shape.get_unchecked(0) {
494 let y = yo as isize * y_stride + dy;
495 let iptr = iptr.offset(yo as isize * y_stride_ptr);
496 if y >= 0 && y < input_heigth {
497 Self::padded_invalid_x_loop(
498 valid_x_start as usize,
499 pad_value,
500 &mut *writer,
501 );
502 Self::padded_valid_x_loop(
503 valid_x_start,
504 valid_x_end,
505 x_stride_ptr,
506 iptr,
507 &mut *writer,
508 );
509 Self::padded_invalid_x_loop(
510 output_width - valid_x_end as usize,
511 pad_value,
512 &mut *writer,
513 );
514 } else {
515 Self::padded_invalid_x_loop(output_width, pad_value, &mut *writer);
516 }
517 }
518 }
519 }
520 }
521 Ok(())
522 }
523
524 #[inline(never)]
525 unsafe fn padded_invalid_x_loop<T: Copy + Datum, W: PackingWriter<T>>(
526 count: usize,
527 pad_value: T,
528 writer: &mut W,
529 ) {
530 for _ in 0..count {
531 writer.write(pad_value);
532 }
533 }
534
535 #[inline(never)]
536 unsafe fn padded_valid_x_loop<T: Copy + Datum, W: PackingWriter<T>>(
537 x_min: isize,
538 x_max: isize,
539 x_stride_ptr: isize,
540 iptr: *const T,
541 writer: &mut W,
542 ) {
543 if x_stride_ptr == 1 && x_max > x_min {
547 unsafe {
548 let row = std::slice::from_raw_parts(iptr.offset(x_min), (x_max - x_min) as usize);
549 writer.write_slice(row);
550 }
551 } else {
552 for x in x_min..x_max {
553 writer.write(unsafe { *iptr.offset(x * x_stride_ptr) });
554 }
555 }
556 }
557
558 #[inline(never)]
559 fn valid_2d<T: Copy + Datum, W: PackingWriter<T>>(
560 geometry: &ConcreteGeometry,
561 input: &TensorView,
562 g: usize,
563 writer: &mut W,
564 ) -> TractResult<()> {
565 unsafe {
566 let shape = &geometry.input_shape_with_n;
567 let y_stride = geometry.pool.patch.spec.strides[0] as isize;
568 let x_stride = geometry.pool.patch.spec.strides[1] as isize;
569 let y_stride_ptr = y_stride * *shape.h_stride() as isize;
570 let x_stride_ptr = x_stride * *shape.w_stride() as isize;
571 let c_stride_ptr = *shape.c_stride() as isize;
572 let iptr = input.as_ptr_unchecked::<T>();
573 let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());
574 let output_y = *geometry.pool.patch.output_shape.get_unchecked(0);
575 let output_x = *geometry.pool.patch.output_shape.get_unchecked(1);
576 let contiguous_x = x_stride_ptr == 1;
580 for ci in 0..geometry.ci_per_group {
581 let iptr = iptr.offset(ci as isize * c_stride_ptr);
582 for koffset in &geometry.pool.patch.standard_layout_data_field {
583 let iptr = iptr.offset(*koffset);
584 let mut iptr_y = iptr;
585 for _ in 0..output_y {
586 if contiguous_x {
587 let row = std::slice::from_raw_parts(iptr_y, output_x);
588 writer.write_slice(row);
589 } else {
590 let mut iptr_x = iptr_y;
592 for _ in 0..output_x {
593 writer.write(*iptr_x);
594 iptr_x = iptr_x.offset(x_stride_ptr);
595 }
596 }
597 iptr_y = iptr_y.offset(y_stride_ptr);
598 }
599 }
600 }
601 Ok(())
602 }
603 }
604}