diff --git a/cgmanifest.json b/cgmanifest.json index 6034653b8c..226b83781e 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -450,7 +450,7 @@ { "component": { "git": { - "commitHash": "7922489c1e7e7baf20c4b1557743d6c7ea72647d", + "commitHash": "f01d8beeac92b54bf93df8053a2acb6a8a10f03c", "repositoryUrl": "https://github.com/microsoft/FeaturizersLibrary.git" }, "type": "git" diff --git a/cmake/external/FeaturizersLibrary b/cmake/external/FeaturizersLibrary index 7922489c1e..f01d8beeac 160000 --- a/cmake/external/FeaturizersLibrary +++ b/cmake/external/FeaturizersLibrary @@ -1 +1 @@ -Subproject commit 7922489c1e7e7baf20c4b1557743d6c7ea72647d +Subproject commit f01d8beeac92b54bf93df8053a2acb6a8a10f03c diff --git a/onnxruntime/core/graph/featurizers_ops/featurizers_defs.cc b/onnxruntime/core/graph/featurizers_ops/featurizers_defs.cc index 773a052a16..6c9046da7a 100644 --- a/onnxruntime/core/graph/featurizers_ops/featurizers_defs.cc +++ b/onnxruntime/core/graph/featurizers_ops/featurizers_defs.cc @@ -494,6 +494,7 @@ void RegisterForecastingPivotFeaturizerVer1(){ MS_FEATURIZERS_OPERATOR_SCHEMA(ForecastingPivotTransformer) .SinceVersion(1) .SetDomain(kMSFeaturizersDomain) + .Attr("num_pivot_columns", "The first num_pivot_columns input in Input1 are pivoted", AttributeProto::INT) .Input( 0, "State", @@ -504,32 +505,31 @@ void RegisterForecastingPivotFeaturizerVer1(){ "Inputs", "Variadic number of Input containing tensors of different size", "T", - ONNX_NAMESPACE::OpSchema::FormalParameterOption::Variadic) + ONNX_NAMESPACE::OpSchema::FormalParameterOption::Variadic, + false) .Output( 0, "Output", "No information is available", - "T") + "T", + ONNX_NAMESPACE::OpSchema::FormalParameterOption::Variadic, + false) .TypeConstraint( "T0", {"tensor(uint8)"}, "No information is available") .TypeConstraint( "T", - {"tensor(float)", "tensor(double)"}, + {"tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", + "tensor(float)", "tensor(double)", "tensor(bool)", "tensor(string)"}, "No information is available") .TypeAndShapeInferenceFunction( [](ONNX_NAMESPACE::InferenceContext& ctx) { - auto input_elem_type = ctx.getInputType(1)->tensor_type().elem_type(); - if (input_elem_type == ONNX_NAMESPACE::TensorProto_DataType_FLOAT) { - propagateElemTypeFromDtypeToOutput(ctx, ONNX_NAMESPACE::TensorProto_DataType_FLOAT, 0); - } else if (input_elem_type == ONNX_NAMESPACE::TensorProto_DataType_DOUBLE) { - propagateElemTypeFromDtypeToOutput(ctx, ONNX_NAMESPACE::TensorProto_DataType_DOUBLE, 0); - } + //The first num_pivot_columns inputs of Input(1) only support float & double if (hasInputShape(ctx, 1)) { const auto& input_shape = getInputShape(ctx, 1); - if (input_shape.dim_size() != 3) { - fail_shape_inference("Expecting Inputs to have 3 dimensions"); + if (input_shape.dim_size() < 2) { + fail_shape_inference("Expecting Inputs to have more than 2 dimensions"); } } ONNX_NAMESPACE::TensorShapeProto shape; diff --git a/onnxruntime/featurizers_ops/cpu/forecasting_pivot_transformer.cc b/onnxruntime/featurizers_ops/cpu/forecasting_pivot_transformer.cc index 514a059550..03daa40158 100644 --- a/onnxruntime/featurizers_ops/cpu/forecasting_pivot_transformer.cc +++ b/onnxruntime/featurizers_ops/cpu/forecasting_pivot_transformer.cc @@ -13,9 +13,27 @@ namespace NS = Microsoft::Featurizer; namespace onnxruntime { namespace featurizers { +template +struct CopyImputedColumnsImpl { + void operator()(const Tensor* input_tensor, Tensor* output_tensor_imputed, + const std::vector& row_idx_record, int64_t input_matrix_size, int num_output_rows) const { + const T* input_data(input_tensor->template Data()); + T* output_data_imputed = output_tensor_imputed->MutableData(); + + for (int imputed_output_row_idx = 0; imputed_output_row_idx < num_output_rows; imputed_output_row_idx++) { + output_data_imputed = std::copy(input_data + row_idx_record[imputed_output_row_idx] * input_matrix_size, + input_data + (row_idx_record[imputed_output_row_idx] + 1) * input_matrix_size, + output_data_imputed); + } + } +}; + template struct ForecastingPivotTransformerImpl { - void operator()(OpKernelContext* ctx) const { + void operator()(OpKernelContext* ctx, int64_t num_pivot_columns) const { + + ORT_ENFORCE(num_pivot_columns > 0, "num_pivot_columns > 0, otherwise there will be no input to pivot"); + using MatrixT = NS::RowMajMatrix::nullable_type>; using InputType = std::vector>; using OutputType = std::vector; @@ -33,21 +51,26 @@ struct ForecastingPivotTransformerImpl { //Get the output for whole rows is inevitable because there is conceptually no way to determine the shape of output for each row std::vector output; + std::vector row_idx_record; + int64_t row_idx = 0; std::function callback_fn; - callback_fn = [&output](OutputType const & value) -> void { + callback_fn = [&output, &row_idx_record, &row_idx](OutputType const & value) -> void { output.emplace_back(value); + row_idx_record.push_back(row_idx); }; // Transform const int input_node_0_count = ctx->NumVariadicInputs(0); const int input_node_1_count = ctx->NumVariadicInputs(1); + InputType input; - input.reserve(input_node_1_count); + input.reserve(num_pivot_columns); std::unordered_map> dataPtrMap; - for (int64_t row_idx = 0; row_idx < row_num; ++row_idx) { + std::vector horizon_output_helper; + for (row_idx = 0; row_idx < row_num; ++row_idx) { //Prepare Input and Output input.clear(); - for (int index = input_node_0_count; index < input_node_0_count + input_node_1_count; ++index) { + for (int index = input_node_0_count; index < input_node_0_count + num_pivot_columns; ++index) { if (row_idx == 0) { //Get the Input const auto* input_tensor(ctx->Input(index)); @@ -66,34 +89,102 @@ struct ForecastingPivotTransformerImpl { input.push_back(typename InputType::value_type(input_data, input_dim_1, input_dim_2)); //Increment data pointer input_data += input_dim_1 * input_dim_2; + std::get<0>(inputTuple) = input_data; } + + // Get the horizon vector from input, since num_pivot_columns > 0. So input is not null + const size_t matrix_cols_num = input[0].cols(); + for (size_t col_idx = 0; col_idx < matrix_cols_num; col_idx++) { + bool has_nan = false; + for (int input_matrix_id = 0; input_matrix_id < num_pivot_columns; input_matrix_id++) { + const size_t matrix_rows_num = input[input_matrix_id].rows(); + auto matrix = input[input_matrix_id]; + for (int row_id = 0; row_id < static_cast(matrix_rows_num); row_id++) { + if (std::isnan(matrix(row_id, col_idx))) { + has_nan = true; + break; + } + } + if (has_nan) + break; + } + if (!has_nan) { + horizon_output_helper.push_back(static_cast(matrix_cols_num - col_idx)); + } + } + //Execute transformer.execute(std::make_tuple(input.begin(), input.end()), callback_fn); } transformer.flush(callback_fn); - // Prepare the Output - TensorShape output_shape({static_cast(output.size()), static_cast(output[0].size())}); - Tensor* output_tensor(ctx->Output(0, output_shape)); - T* output_data = output_tensor->MutableData(); + // Prepare the number of output rows + ORT_ENFORCE(!output.empty(), "All rows dropped is an exception"); + int num_output_rows = static_cast(output.size()); + ORT_ENFORCE(static_cast(row_idx_record.size()) == num_output_rows, "row_idx_record.size() == num_output_rows"); - for (OutputType const & row : output) - output_data = std::copy(row.begin(), row.end(), output_data); + // Prepare the pivoted Output + int num_pivot_output_columns = 0; + if (!output.empty() && !output[0].empty()) { + num_pivot_output_columns = static_cast(output[0].size()); + + for (int pivot_output_tensor_idx = 0; pivot_output_tensor_idx < num_pivot_output_columns; pivot_output_tensor_idx++) { + TensorShape output_shape({static_cast(num_output_rows), 1}); + Tensor* output_tensor(ctx->Output(pivot_output_tensor_idx, output_shape)); + T* output_data = output_tensor->MutableData(); + + for (int pivot_output_row_idx = 0; pivot_output_row_idx < num_output_rows; pivot_output_row_idx++){ + *output_data++ = output[pivot_output_row_idx][pivot_output_tensor_idx]; + } + } + } + + // Prepare the non-pivot(imputed) Output + for (int imputed_output_count_idx = 0; imputed_output_count_idx < input_node_1_count - num_pivot_columns; imputed_output_count_idx++) { + + int tensor_id = input_node_0_count + static_cast(num_pivot_columns) + imputed_output_count_idx; + const auto* input_tensor(ctx->Input(tensor_id)); + + auto input_dims = input_tensor->Shape(); + int64_t input_matrix_size = 1; + for (size_t dim_idx = 1; dim_idx < input_dims.NumDimensions(); dim_idx++) + input_matrix_size *= input_dims[dim_idx]; + + TensorShape output_shape_imputed({static_cast(num_output_rows), input_matrix_size}); + Tensor* output_tensor_imputed(ctx->Output(imputed_output_count_idx + num_pivot_output_columns, output_shape_imputed)); + + const auto elem_type = input_tensor->GetElementType(); + + utils::MLTypeCallDispatcher t_disp(elem_type); + t_disp.Invoke(input_tensor, output_tensor_imputed, row_idx_record, input_matrix_size, num_output_rows); + } + + // Prepare the horizon Output(uint32) + TensorShape output_shape_horizon({static_cast(num_output_rows), 1}); + Tensor* output_tensor_horizon(ctx->Output(input_node_1_count + num_pivot_output_columns - static_cast(num_pivot_columns), output_shape_horizon)); + uint32_t* output_data_horizon = output_tensor_horizon->MutableData(); + + std::copy(horizon_output_helper.begin(), horizon_output_helper.end(), output_data_horizon); } }; class ForecastingPivotTransformer final : public OpKernel { public: - explicit ForecastingPivotTransformer(const OpKernelInfo& info) : OpKernel(info) { + explicit ForecastingPivotTransformer(const OpKernelInfo& info) : + OpKernel(info), _num_pivot_columns(info.GetAttrOrDefault("num_pivot_columns", static_cast(0))) { } Status Compute(OpKernelContext* ctx) const override { utils::MLTypeCallDispatcher t_disp(ctx->Input(1)->GetElementType()); - t_disp.Invoke(ctx); + t_disp.Invoke(ctx, _num_pivot_columns); return Status::OK(); } + private: + const int64_t _num_pivot_columns; }; ONNX_OPERATOR_KERNEL_EX( @@ -103,9 +194,18 @@ ONNX_OPERATOR_KERNEL_EX( kCpuExecutionProvider, KernelDefBuilder() .TypeConstraint("T0", DataTypeImpl::GetTensorType()) - .TypeConstraint("T", {DataTypeImpl::GetTensorType(), - DataTypeImpl::GetTensorType() - }), + .TypeConstraint("T", {DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType(), + DataTypeImpl::GetTensorType()}), ForecastingPivotTransformer); } // namespace featurizers diff --git a/onnxruntime/test/featurizers_ops/forecasting_pivot_transformer_test.cc b/onnxruntime/test/featurizers_ops/forecasting_pivot_transformer_test.cc index 903efcdf1f..01a993d426 100644 --- a/onnxruntime/test/featurizers_ops/forecasting_pivot_transformer_test.cc +++ b/onnxruntime/test/featurizers_ops/forecasting_pivot_transformer_test.cc @@ -29,50 +29,135 @@ std::vector GetStream() { } // namespace TEST(FeaturizersTests, ForecastingPivotTransformer_2_Inputs) { - auto stream = GetStream(); + auto stream = GetStream(); auto dim = static_cast(stream.size()); OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain); + test.AddAttribute("num_pivot_columns", static_cast(2)); test.AddInput("State", {dim}, stream); test.AddInput("Input_1", {2, 3, 4}, {1, 6, 3, 9, 2, 4, 5, 8, - NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7, 10, - 1, 6, 3, 9, - 2, 4, 5, 8, - NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7, 10}); - test.AddInput("Input_2", {2, 2, 4}, {2, NS::Traits::CreateNullValue(), 5, 6, - 2, NS::Traits::CreateNullValue(), 3, 4, - 2, NS::Traits::CreateNullValue(), 5, 6, - 2, NS::Traits::CreateNullValue(), 3, 4}); - test.AddOutput("Output", {4, 5}, {3, 5, 7, 5, 3, - 9, 8, 10, 6, 4, - 3, 5, 7, 5, 3, - 9, 8, 10, 6, 4}); + NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7, 10, + 1, 6, 9, 3, + 2, 4, 8, 5, + NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 10, 7}); + test.AddInput("Input_2", {2, 2, 4}, {2, NS::Traits::CreateNullValue(), 5, 6, + 2, NS::Traits::CreateNullValue(), 3, 4, + 2, NS::Traits::CreateNullValue(), 5, 6, + 2, NS::Traits::CreateNullValue(), 3, 4}); + test.AddOutput("Output_1", {4, 1}, {3, 9, 9, 3}); + test.AddOutput("Output_2", {4, 1}, {5, 8, 8, 5}); + test.AddOutput("Output_3", {4, 1}, {7, 10, 10, 7}); + test.AddOutput("Output_4", {4, 1}, {5, 6, 5, 6}); + test.AddOutput("Output_5", {4, 1}, {3, 4, 3, 4}); + //horizon output + test.AddOutput("Output_6", {4, 1}, {2, 1, 2, 1}); test.Run(); } -TEST(FeaturizersTests, ForecastingPivotTransformer_3_Inputs) { - auto stream = GetStream(); +TEST(FeaturizersTests, ForecastingPivotTransformer_4_Inputs) { + auto stream = GetStream(); auto dim = static_cast(stream.size()); OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain); + test.AddAttribute("num_pivot_columns", static_cast(2)); test.AddInput("State", {dim}, stream); test.AddInput("Input_1", {2, 3, 4}, {1, 6, 3, 9, 2, 4, 5, 8, - NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7, 10, + NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7, 10, 1, 6, 3, 9, 2, 4, 5, 8, - NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7, 10}); - test.AddInput("Input_2", {2, 2, 4}, {2, NS::Traits::CreateNullValue(), 5, 6, - 2, NS::Traits::CreateNullValue(), 3, 4, - 2, NS::Traits::CreateNullValue(), 5, 6, - 2, NS::Traits::CreateNullValue(), 3, 4}); - test.AddInput("Input_3", {2, 1, 4}, {0, 0, 0, 0, - 0, 0, 0, 0}); - test.AddOutput("Output", {4, 6}, {3, 5, 7, 5, 3, 0, - 9, 8, 10, 6, 4, 0, - 3, 5, 7, 5, 3, 0, - 9, 8, 10, 6, 4, 0}); + NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7, 10}); + test.AddInput("Input_2", {2, 2, 4}, {2, NS::Traits::CreateNullValue(), 5, 6, + 2, NS::Traits::CreateNullValue(), 3, 4, + 2, NS::Traits::CreateNullValue(), 5, 6, + 2, NS::Traits::CreateNullValue(), 3, 4}); + test.AddInput("Input_3", {2, 1, 4}, {"7", "7", "7", "7", + "9", "9", "9", "9"}); + test.AddInput("Input_4", {2, 4}, {-7, -7, -7, -7, + -9, -9, -9, -9}); + + //pivot output + test.AddOutput("Output_1", {4, 1}, {3, 9, 3, 9}); + test.AddOutput("Output_2", {4, 1}, {5, 8, 5, 8}); + test.AddOutput("Output_3", {4, 1}, {7, 10, 7, 10}); + test.AddOutput("Output_4", {4, 1}, {5, 6, 5, 6}); + test.AddOutput("Output_5", {4, 1}, {3, 4, 3, 4}); + + //non-pivot output + test.AddOutput("Output_6", {4, 4}, {"7", "7", "7", "7", + "7", "7", "7", "7", + "9", "9", "9", "9", + "9", "9", "9", "9"}); + test.AddOutput("Output_7", {4, 4}, {-7, -7, -7, -7, + -7, -7, -7, -7, + -9, -9, -9, -9, + -9, -9, -9, -9}); + //horizon output + test.AddOutput("Output_8", {4, 1}, {2, 1, 2, 1}); + test.Run(); +} + +TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_1) { + auto stream = GetStream(); + auto dim = static_cast(stream.size()); + OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain); + test.AddAttribute("num_pivot_columns", static_cast(1)); + test.AddInput("State", {dim}, stream); + test.AddInput("Input_1", {2, 1, 2}, {1, 6, 3, 9}); + test.AddOutput("Output_1", {4, 1}, {1, 6, 3, 9}); + + //horizon output + test.AddOutput("Output_2", {4, 1}, {2, 1, 2, 1}); + test.Run(); +} + +TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_2) { + auto stream = GetStream(); + auto dim = static_cast(stream.size()); + OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain); + test.AddAttribute("num_pivot_columns", static_cast(1)); + test.AddInput("State", {dim}, stream); + test.AddInput("Input_1", {2, 1, 2}, {1, NS::Traits::CreateNullValue(), + 3, 9}); + test.AddOutput("Output_1", {3, 1}, {1, 3, 9}); + + //horizon output + test.AddOutput("Output_2", {3, 1}, {2, 2, 1}); + test.Run(); +} + +TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_3) { + auto stream = GetStream(); + auto dim = static_cast(stream.size()); + OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain); + test.AddAttribute("num_pivot_columns", static_cast(1)); + test.AddInput("State", {dim}, stream); + test.AddInput("Input_1", {1, 3, 4}, {1, 4, 6, NS::Traits::CreateNullValue(), + 2, 5, NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), + 3, NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), 7}); + test.AddOutput("Output_1", {1, 1}, {1}); + test.AddOutput("Output_2", {1, 1}, {2}); + test.AddOutput("Output_3", {1, 1}, {3}); + //horizon output + test.AddOutput("Output_4", {1, 1}, {4}); + + test.Run(); +} + +TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_Horizon) { + auto stream = GetStream(); + auto dim = static_cast(stream.size()); + OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain); + test.AddAttribute("num_pivot_columns", static_cast(1)); + test.AddInput("State", {dim}, stream); + test.AddInput("Input_1", {3, 1, 6}, {1, NS::Traits::CreateNullValue(), 3, NS::Traits::CreateNullValue(), 5, NS::Traits::CreateNullValue(), + NS::Traits::CreateNullValue(), 2, 3, NS::Traits::CreateNullValue(), 5, 6, + 1, 2, 3, NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue(), NS::Traits::CreateNullValue()}); + test.AddOutput("Output_1", {10, 1}, {1, 3, 5, 2, 3, 5, 6, 1, 2, 3}); + + //horizon output + test.AddOutput("Output_2", {10, 1}, {6, 4, 2, 5, 4, 2, 1, 6, 5, 4}); test.Run(); }