diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md index 08178f2065..8179dc35ce 100644 --- a/docs/OperatorKernels.md +++ b/docs/OperatorKernels.md @@ -373,8 +373,8 @@ Do not modify directly.* |TfIdfVectorizer|*in* X:**T**
*out* Y:**T1**|9+|**T** = tensor(int32), tensor(int64), tensor(string)
**T1** = tensor(float)| |ThresholdedRelu|*in* X:**T**
*out* Y:**T**|10+|**T** = tensor(float)| |||[1, 9]|**T** = tensor(float)| -|Tile|*in* input:**T**
*in* repeats:**T1**
*out* output:**T**

or

*in* input:**T**
*in* tiles:**T**
*in* axis:**T**
*out* output:**T**|13+|**T** = tensor(bool), tensor(double), tensor(float), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)
**T1** = tensor(int64)| -|||[6, 12]|**T** = tensor(bool), tensor(double), tensor(float), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)
**T1** = tensor(int64)| +|Tile|*in* input:**T**
*in* repeats:**T1**
*out* output:**T**

or

*in* input:**T**
*in* tiles:**T**
*in* axis:**T**
*out* output:**T**|13+|**T** = tensor(bool), tensor(double), tensor(float), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)
**T1** = tensor(int64)| +|||[6, 12]|**T** = tensor(bool), tensor(double), tensor(float), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)
**T1** = tensor(int64)| |TopK|*in* X:**T**
*in* K:**tensor(int64)**
*out* Values:**T**
*out* Indices:**I**

or

*in* X:**T**
*out* Values:**T**
*out* Indices:**I**|11+|**I** = tensor(int64)
**T** = tensor(double), tensor(float), tensor(int32), tensor(int64)| |||10|**I** = tensor(int64)
**T** = tensor(double), tensor(float)| |||[1, 9]|**I** = tensor(int64)
**T** = tensor(double), tensor(float)| diff --git a/onnxruntime/core/providers/cpu/tensor/tile.cc b/onnxruntime/core/providers/cpu/tensor/tile.cc index 014274208c..57374014b0 100644 --- a/onnxruntime/core/providers/cpu/tensor/tile.cc +++ b/onnxruntime/core/providers/cpu/tensor/tile.cc @@ -34,6 +34,7 @@ ONNX_CPU_OPERATOR_VERSIONED_KERNEL( DataTypeImpl::GetTensorType(), DataTypeImpl::GetTensorType(), DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), DataTypeImpl::GetTensorType()}) .TypeConstraint("T1", DataTypeImpl::GetTensorType()), Tile); @@ -51,6 +52,7 @@ ONNX_CPU_OPERATOR_KERNEL( DataTypeImpl::GetTensorType(), DataTypeImpl::GetTensorType(), DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), DataTypeImpl::GetTensorType()}) .TypeConstraint("T1", DataTypeImpl::GetTensorType()), Tile); @@ -98,6 +100,45 @@ Status TileCoreForFixedSizeTypes(const Tensor& input_tensor, Tensor& output_tens return Status::OK(); } +Status TileCoreForStringType(const Tensor& input_tensor, Tensor& output_tensor, const int64_t* repeats, TensorAxisCounters& input_counters, const TensorPitches& output_pitches) { + const auto& input_shape = input_tensor.Shape().GetDims(); + const size_t dimension_count = input_shape.size(); + + const auto* input = input_tensor.Data(); + auto* output = output_tensor.MutableData(); + + // some helper variables that will be used along the way + size_t block_size = 0; + int64_t num_repeats = 0; + const std::string* copy = nullptr; + const int64_t innermost_dim = input_shape[dimension_count - 1]; + + while (input_counters) { + // Copy the input data over + block_size = SafeInt(innermost_dim); + output = std::copy(input, input + block_size, output); + input += block_size; + + // Tile data for the innermost axis + copy = output - block_size; + num_repeats = repeats[dimension_count - 1] - 1; + for (int64_t repeat = 0; repeat < num_repeats; ++repeat) { + output = std::copy(copy, copy + block_size, output); + } + + // Tile data for other axes + while (input_counters.Increment()) { + block_size = onnxruntime::narrow(output_pitches[input_counters.Axis()] * input_shape[input_counters.Axis()]); + copy = output - block_size; + num_repeats = repeats[input_counters.Axis()] - 1; + for (int64_t repeat = 0; repeat < num_repeats; ++repeat) { + output = std::copy(copy, copy + block_size, output); + } + } + } + return Status::OK(); +} + namespace TileOp { // Find the first non-1 repeat and check the input shape to the left of that dimension: // 1) If the dim values to the left are all 1s (or don't exist), then the tiling logic is essentially copying the input buffer @@ -170,10 +211,10 @@ Status Tile::Compute(OpKernelContext* ctx) const { // Repeat tensor has all 1s in it if (output_shape == input_shape) { - // TODO: Handle string copies when the kernel eventually supports string type. - // For now, it shouldn't throw in the enforce as the kernel doesn't claim string support - ORT_ENFORCE(!input_tensor.IsDataType(), "Tile doesn't support string type yet"); - memcpy(output_tensor.MutableDataRaw(), input_tensor.DataRaw(), input_tensor.SizeInBytes()); + if (input_tensor.IsDataType()) + std::copy(input_tensor.Data(), input_tensor.Data()+input_shape.Size(), output_tensor.MutableData()); + else + memcpy(output_tensor.MutableDataRaw(), input_tensor.DataRaw(), input_tensor.SizeInBytes()); return Status::OK(); } @@ -187,10 +228,7 @@ Status Tile::Compute(OpKernelContext* ctx) const { is_batched_memcpy, num_of_elements_per_batch, num_of_copies_per_batch, - num_of_batch_copies)) { - // TODO: Handle string copies when the kernel eventually supports string type. - // For now, it shouldn't throw in the enforce as the kernel doesn't claim string support - ORT_ENFORCE(!input_tensor.IsDataType(), "Tile doesn't support string type yet"); + num_of_batch_copies) && !input_tensor.IsDataType()) { int8_t* output_data_casted = reinterpret_cast(output_tensor.MutableDataRaw()); const int8_t* input_data_casted = reinterpret_cast(input_tensor.DataRaw()); @@ -239,6 +277,9 @@ Status Tile::Compute(OpKernelContext* ctx) const { static_assert(sizeof(float) == sizeof(int32_t), "Float and Int32 are of different sizes"); static_assert(sizeof(double) == sizeof(int64_t), "Double and Int64 are of different sizes"); + if (input_tensor.IsDataType()) + return TileCoreForStringType(input_tensor, output_tensor, repeats, input_counters, output_pitches); + if (input_tensor.IsDataType() || input_tensor.IsDataType() || input_tensor.IsDataType()) diff --git a/onnxruntime/test/providers/cpu/tensor/tile_op_test.cc b/onnxruntime/test/providers/cpu/tensor/tile_op_test.cc index 773a0cb3e2..70de326d73 100644 --- a/onnxruntime/test/providers/cpu/tensor/tile_op_test.cc +++ b/onnxruntime/test/providers/cpu/tensor/tile_op_test.cc @@ -16,6 +16,15 @@ std::vector InputData(size_t size) { return result; } +template <> +std::vector InputData(size_t size) { + std::vector result(size); + for (size_t i = 0; i < size; i++) { + result[i] = std::to_string(i); + } + return result; +} + template <> std::vector InputData(size_t size) { std::vector result(size); @@ -220,8 +229,11 @@ TEST(TensorOpTest, TileInt64Type) { RunTestWrapper(); } TEST(TensorOpTest, TileUint64Type) { RunTestWrapper(); } +TEST(TensorOpTest, TileStringType) { RunTestWrapper(); } + TEST(TensorOpTest, TileBoolType) { RunTestWrapperForBool(); } + #if defined(USE_CUDA) || defined(USE_ROCM) TEST(TensorOpTest, TileMLFloat16Type) { RunTestWrapper(); } #endif