1use crate::internal::*;
2use crate::ops::einsum::block_quant_aware_input_shape;
3use crate::ops::matmul::pack::OptSimpleMatMulPack;
4use ndarray::*;
5use tract_linalg::block_quant::BlockQuantStorage;
6use tract_linalg::mmm::{MMMInputValue, PackedMatrixStorage};
7
8#[derive(Debug, Clone, Hash, PartialEq, Eq)]
9pub struct Gather {
10 pub axis: usize,
11 pub output_type: Option<DatumType>,
12}
13
14impl Op for Gather {
15 fn name(&self) -> StaticName {
16 "Gather".into()
17 }
18
19 op_as_typed_op!();
20}
21
22impl Gather {
23 pub fn new(axis: usize) -> Gather {
24 Gather { axis, output_type: None }
25 }
26
27 pub fn compute_output_shape<D: DimLike>(
28 &self,
29 input_shape: &[D],
30 indices_shape: &[D],
31 ) -> TractResult<TVec<D>> {
32 ensure!(input_shape.len() > self.axis);
33 let mut output_shape: TVec<D> = input_shape[..self.axis].into();
34 output_shape.extend(indices_shape.iter().cloned());
35 output_shape.extend(input_shape[self.axis + 1..].iter().cloned());
36 Ok(output_shape)
37 }
38
39 fn eval_t<T: Datum>(&self, data: TValue, indices: &TValue) -> TractResult<Tensor> {
40 let data_plain = data.try_as_plain()?;
41 let data_view = unsafe { data_plain.to_array_view_unchecked::<T>() };
42 let indices = indices.to_plain_array_view::<i64>()?;
43 let output_shape = &*self.compute_output_shape(data.shape(), indices.shape())?;
44 let mut output = unsafe { Tensor::uninitialized::<T>(output_shape)? };
45 let mut output_plain = output.try_as_plain_mut()?;
46 let mut output_view = output_plain.to_array_view_mut::<T>()?;
47
48 let data_shape = data.shape();
49 let data_axis = self.axis;
50
51 let block_len = data_shape[data_axis + 1..].iter().product::<usize>();
52
53 let outer_len = data_shape[..data_axis].iter().product::<usize>();
56 let can_block_copy = data_shape[..data_axis] == output_shape[..data_axis]
57 && data_view.is_standard_layout()
58 && output_view.is_standard_layout();
59
60 if can_block_copy {
61 let axis_len = data_shape[data_axis];
62 let input_slice = data_view.as_slice().unwrap();
63 let output_slice = &mut output_view.as_slice_mut().unwrap();
64 let mut out_offset = 0;
65 for outer in 0..outer_len {
66 let input_base = outer * axis_len * block_len;
67 for index in indices.iter() {
68 let resolved_index =
69 if *index < 0 { index + axis_len as i64 } else { *index } as usize;
70 let input_offset = input_base + resolved_index * block_len;
71 output_slice[out_offset..out_offset + block_len]
72 .clone_from_slice(&input_slice[input_offset..input_offset + block_len]);
73 out_offset += block_len;
74 }
75 }
76 } else {
77 let ic_len = self.axis + 1 + output_shape.len() - (self.axis + indices.ndim());
78 let mut icoords = vec![0; ic_len];
79 let axis = self.axis;
80 for coords in tract_ndarray::indices(output_shape) {
81 let ocoords = coords.as_array_view();
82 let ocoords = ocoords.as_slice().unwrap();
83
84 let kcoords = &ocoords[self.axis..][..indices.ndim()];
85 let k = indices[kcoords];
86 let k = if k < 0 { k + data_view.shape()[self.axis] as i64 } else { k } as usize;
87 icoords[0..axis].copy_from_slice(&ocoords[..self.axis]);
88 icoords[self.axis] = k;
89 icoords[self.axis + 1..].clone_from_slice(&ocoords[self.axis + indices.ndim()..]);
90 output_view[ocoords] =
91 data_view.get(&*icoords).cloned().context("Invalid gather")?;
92 }
93 }
94 unsafe { output.set_datum_type(data.datum_type()) };
97 Ok(output)
98 }
99
100 fn eval_bq<F: Datum>(
101 &self,
102 data: &BlockQuantStorage,
103 m: usize,
104 k: usize,
105 indices: &TValue,
106 ) -> TractResult<Tensor> {
107 ensure!(self.axis == 0);
108 let data_shape = &[m, k];
109 let output_shape = &*self.compute_output_shape(data_shape, indices.shape())?;
110 let mut output = unsafe { Tensor::uninitialized::<F>(output_shape)? };
111 let indices_plain = indices.try_as_plain()?;
112 let indices_slice = indices_plain.as_slice::<i64>()?;
113 let vector_len = k;
114 let blob = data.value();
115
116 let block_len = data.format().block_len();
117 let block_bytes = data.format().block_bytes();
118 if F::datum_type() == f16::datum_type() {
119 let mut output_plain = output.try_as_plain_mut()?;
120 let output_slice = output_plain.as_slice_mut::<f16>()?;
121 for (pos, ix) in indices_slice.iter().enumerate() {
122 let slice = &mut output_slice[pos * vector_len..][..vector_len];
123 for i in (0..vector_len).step_by(block_len) {
124 let offset = k * *ix as usize + i;
125 let block_id = offset / block_len;
126 data.format().dequant_block_f16(
127 &blob[block_id * block_bytes..][..block_bytes],
128 &mut slice[i..i + block_len],
129 );
130 }
131 }
132 } else {
133 let mut output_plain = output.try_as_plain_mut()?;
134 let output_slice = output_plain.as_slice_mut::<f32>()?;
135 for (pos, ix) in indices_slice.iter().enumerate() {
136 let slice = &mut output_slice[pos * vector_len..][..vector_len];
137 for i in (0..vector_len).step_by(block_len) {
138 let offset = k * *ix as usize + i;
139 let block_id = offset / block_len;
140 data.format().dequant_block_f32(
141 &blob[block_id * block_bytes..][..block_bytes],
142 &mut slice[i..i + block_len],
143 );
144 }
145 }
146 }
147 Ok(output)
148 }
149
150 fn eval_input_store<F: Datum>(
151 &self,
152 data: &dyn MMMInputValue,
153 indices: &TValue,
154 ) -> TractResult<Tensor> {
155 ensure!(self.axis == 0);
156 let data_shape = &[data.mn(), data.k()];
157 let output_shape = &*self.compute_output_shape(data_shape, indices.shape())?;
158 let mut output = unsafe { Tensor::uninitialized::<F>(output_shape)? };
159 let indices_plain = indices.try_as_plain()?;
160 let indices_slice = indices_plain.as_slice::<i64>()?;
161 let vector_len = data_shape[1];
162 if F::datum_type() == f16::datum_type() {
163 let mut output_plain = output.try_as_plain_mut()?;
164 let output_slice = output_plain.as_slice_mut::<f16>()?;
165 for (pos, m) in indices_slice.iter().enumerate() {
166 let slice = &mut output_slice[pos * vector_len..][..vector_len];
167 data.extract_at_mn_f16(*m as usize, slice)?;
168 }
169 } else {
170 let mut output_plain = output.try_as_plain_mut()?;
171 let output_slice = output_plain.as_slice_mut::<f32>()?;
172 for (pos, m) in indices_slice.iter().enumerate() {
173 let slice = &mut output_slice[pos * vector_len..][..vector_len];
174 data.extract_at_mn_f32(*m as usize, slice)?;
175 }
176 }
177 Ok(output)
178 }
179}
180
181impl TypedOp for Gather {
182 as_op!();
183
184 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
185 if let Some(dt) = self.output_type {
186 ensure!(
187 inputs[0].is_exotic() || inputs[0].datum_type == dt,
188 "Inconsistent datum_type in Gather: attribute is {:?}, but inputs[0] is {:?}",
189 dt,
190 inputs[0].datum_type
191 );
192 } else {
193 ensure!(
194 inputs[0].is_plain(),
195 "Gather applied to compressed data requires an explicit datum_type attribute for its output"
196 );
197 }
198 ensure!(inputs[1].datum_type == i64::datum_type());
199 if inputs[0].is_exotic() {
200 let data_shape = block_quant_aware_input_shape(inputs[0])?;
201 Ok(tvec!(
202 self.output_type
203 .unwrap()
204 .fact(&*self.compute_output_shape(&data_shape, &inputs[1].shape)?)
205 ))
206 } else {
207 Ok(tvec!(
208 inputs[0]
209 .datum_type
210 .fact(&*self.compute_output_shape(&inputs[0].shape, &inputs[1].shape)?)
211 ))
212 }
213 }
214
215 fn axes_mapping(
216 &self,
217 inputs: &[&TypedFact],
218 _outputs: &[&TypedFact],
219 ) -> TractResult<AxesMapping> {
220 if !inputs[0].is_plain() {
228 return AxesMapping::disconnected(
229 inputs,
230 &[&inputs[0].datum_type.fact(&[0i64.to_dim()])],
231 );
232 }
233 let data_rank = inputs[0].rank();
234 let indices_rank = inputs[1].rank();
235 let mut axes: TVec<crate::axes::Axis> = tvec!();
236 let mut alphabet = 'a'..;
237 for k in 0..self.axis {
238 axes.push(
239 crate::axes::Axis::new(alphabet.next().unwrap(), 2, 1).input(0, k).output(0, k),
240 );
241 }
242 axes.push(crate::axes::Axis::new(alphabet.next().unwrap(), 2, 1).input(0, self.axis));
243 for k in self.axis + 1..data_rank {
244 let out_pos = k - 1 + indices_rank;
245 axes.push(
246 crate::axes::Axis::new(alphabet.next().unwrap(), 2, 1)
247 .input(0, k)
248 .output(0, out_pos),
249 );
250 }
251 for k in 0..indices_rank {
252 let out_pos = self.axis + k;
253 axes.push(
254 crate::axes::Axis::new(alphabet.next().unwrap(), 2, 1)
255 .input(1, k)
256 .output(0, out_pos),
257 );
258 }
259 AxesMapping::new(2, 1, axes)
260 }
261
262 fn declutter(
263 &self,
264 model: &TypedModel,
265 node: &TypedNode,
266 ) -> TractResult<Option<TypedModelPatch>> {
267 let (input_fact, indices_fact) = args_2!(model.node_input_facts(node.id)?);
268 if let Some(indices) = indices_fact.konst.as_ref()
269 && indices.rank() == 1
270 && indices.len() == 1
271 && input_fact.is_plain()
272 && input_fact.datum_type.is_number()
273 {
274 let mut patch = TypedModelPatch::default();
275 let mut wire = patch.tap_model(model, node.inputs[0])?;
276 let index = indices.cast_to_scalar::<i64>()?;
277 let index = if index < 0 {
278 let data_fact = model.outlet_fact(node.inputs[0])?;
279 data_fact.shape[self.axis].clone() + index.to_dim()
280 } else {
281 index.to_dim()
282 };
283 wire = patch.wire_node(
284 format!("{}.slice", node.name),
285 crate::ops::array::Slice { axis: self.axis, start: index.clone(), end: index + 1 },
286 &[wire],
287 )?[0];
288 patch.shunt_outside(model, node.id.into(), wire)?;
289 return Ok(Some(patch));
290 }
291 if input_fact.konst.is_some() {
292 if let Some(sibling) = model
294 .outlet_successors(node.inputs[0])
295 .iter()
296 .find(|o| o.node != node.id && model.node(o.node).op_is::<OptSimpleMatMulPack>())
297 {
298 let mut patch = TypedModelPatch::default();
299 let mut taps = patch.taps(model, &node.inputs)?;
300 taps[0] = patch.tap_model(model, sibling.node.into())?;
301 let wire = patch.wire_node(&node.name, self.clone(), &taps)?[0];
302 patch.shunt_outside(model, node.id.into(), wire)?;
303 return Ok(Some(patch));
304 }
305 }
306 Ok(None)
307 }
308}
309
310impl EvalOp for Gather {
311 fn is_stateless(&self) -> bool {
312 true
313 }
314
315 fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
316 let (data, indices) = args_2!(inputs);
317 let result = if let Some(bqs) = data.storage_as::<BlockQuantStorage>() {
318 let dt = self.output_type.unwrap();
319 let m = data.shape()[data.rank() - 2];
320 let k = *data.shape().last().unwrap();
321 dispatch_floatlike!(Self::eval_bq(dt)(self, bqs, m, k, &indices))?
322 } else if let Some(storage) = data.storage_as::<PackedMatrixStorage>()
323 && storage.batch_shape().is_empty()
324 {
325 let dt = self.output_type.unwrap();
326 let data_val = storage.value();
327 dispatch_floatlike!(Self::eval_input_store(dt)(self, data_val, &indices))?
328 } else {
329 dispatch_datum!(Self::eval_t(data.datum_type())(self, data, &indices))?
330 };
331 Ok(tvec!(result.into_tvalue()))
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn test_should_gather_scalar_index() {
341 let data = Tensor::from(arr1(&[1i64, 2, 3]));
342 let gatherer = Gather::new(0);
343 for idx in 2..3 {
344 let index = Tensor::from(arr0(idx));
345 let outputs =
346 gatherer.eval(tvec![data.clone().into_tvalue(), index.into_tvalue()]).unwrap();
347 let output = &outputs[0];
348 assert_eq!(output.shape().len(), 0);
349 assert_eq!(*output.try_as_plain().unwrap().to_scalar::<i64>().unwrap(), idx + 1);
350 }
351 }
352}