use super::*;
#[cfg(feature = "rayon")]
const PARALLEL_THRESHOLD_BYTES: usize = 4 * 1024 * 1024;
#[cfg(feature = "rayon")]
const MIN_ELEMS_PER_TASK: usize = 64 * 1024;
pub fn select<E: Element + Pod + Default + Copy + Send + Sync>(
tensor: HostTensor,
dim: usize,
indices: HostTensor,
) -> HostTensor {
let tensor = tensor.to_contiguous();
let indices = indices.to_contiguous();
let tensor_shape = tensor.layout().shape();
let ndims = tensor_shape.num_dims();
assert!(
dim < ndims,
"dim {} out of bounds for {} dimensions",
dim,
ndims
);
assert_eq!(
indices.layout().num_dims(),
1,
"select: indices must be 1D, got {} dims",
indices.layout().num_dims()
);
let tensor_data: &[E] = tensor.storage();
let indices_data = read_indices(&indices);
let num_indices = indices_data.len();
let mut output_dims = tensor_shape.to_vec();
output_dims[dim] = num_indices;
let output_shape = Shape::from(output_dims);
let output_size = output_shape.num_elements();
let slice_size: usize = tensor_shape[dim + 1..].iter().product();
let select_dim_size = tensor_shape[dim];
if dim == 0 {
let result = select_rows::<E>(
tensor_data,
&indices_data,
select_dim_size,
slice_size,
output_size,
);
return HostTensor::new(
Bytes::from_elems(result),
Layout::contiguous(output_shape),
E::dtype(),
);
}
let outer_count: usize = tensor_shape[..dim].iter().product();
if output_size == 0 {
if slice_size == 0 && outer_count != 0 {
for &idx in indices_data.iter() {
checked_index(idx, select_dim_size);
}
}
return HostTensor::new(
Bytes::from_elems(Vec::<E>::new()),
Layout::contiguous(output_shape),
E::dtype(),
);
}
let source_offsets: Vec<usize> = indices_data
.iter()
.map(|&idx| checked_index(idx, select_dim_size) * slice_size)
.collect();
let source_row_size = select_dim_size * slice_size;
let output_row_size = num_indices * slice_size;
let mut result = vec![E::default(); output_size];
let copy_rows = |start_row: usize, rows: &mut [E]| {
for (row, dst_row) in rows.chunks_mut(output_row_size).enumerate() {
let src_start = (start_row + row) * source_row_size;
let src_row = &tensor_data[src_start..src_start + source_row_size];
if slice_size == 1 {
for (dst, &offset) in dst_row.iter_mut().zip(&source_offsets) {
*dst = src_row[offset];
}
} else {
for (dst, &offset) in dst_row.chunks_mut(slice_size).zip(&source_offsets) {
dst.copy_from_slice(&src_row[offset..offset + slice_size]);
}
}
}
};
#[cfg(feature = "rayon")]
if output_size.saturating_mul(size_of::<E>()) >= PARALLEL_THRESHOLD_BYTES {
let rows_per_chunk = (MIN_ELEMS_PER_TASK / output_row_size)
.max(1)
.min(outer_count);
result
.par_chunks_mut(rows_per_chunk * output_row_size)
.enumerate()
.for_each(|(chunk, rows)| copy_rows(chunk * rows_per_chunk, rows));
} else {
copy_rows(0, &mut result);
}
#[cfg(not(feature = "rayon"))]
copy_rows(0, &mut result);
HostTensor::new(
Bytes::from_elems(result),
Layout::contiguous(output_shape),
E::dtype(),
)
}
#[inline]
fn select_rows<E: Element + Pod + Default + Copy + Send + Sync>(
tensor_data: &[E],
indices_data: &[isize],
dim_size: usize,
row_size: usize,
output_size: usize,
) -> Vec<E> {
let mut result = Vec::with_capacity(output_size);
#[cfg(feature = "rayon")]
if output_size.saturating_mul(size_of::<E>()) >= PARALLEL_THRESHOLD_BYTES {
result.resize(output_size, E::default());
let rows_per_chunk = (MIN_ELEMS_PER_TASK / row_size)
.max(1)
.min(indices_data.len());
result
.par_chunks_mut(rows_per_chunk * row_size)
.enumerate()
.for_each(|(chunk, rows)| {
let start_row = chunk * rows_per_chunk;
for (i, dst_row) in rows.chunks_mut(row_size).enumerate() {
let src_row = checked_index(indices_data[start_row + i], dim_size);
let src_start = src_row * row_size;
dst_row.copy_from_slice(&tensor_data[src_start..src_start + row_size]);
}
});
return result;
}
for &idx in indices_data {
let src_row = checked_index(idx, dim_size);
let src_start = src_row * row_size;
result.extend_from_slice(&tensor_data[src_start..src_start + row_size]);
}
result
}
#[cfg(test)]
#[path = "select_tests.rs"]
mod tests;