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