Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions onnxruntime/core/providers/cpu/ml/label_encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ std::vector<T> GetAttribute(const OpKernelInfo& info, const std::string& name, c
} else {
ORT_ENFORCE(result.IsOK(), "LabelEncoder is missing attribute ", tensor_name, " or ", name);
}
ORT_ENFORCE(!utils::HasExternalData(attr_tensor_proto),
"Tensor attribute ", tensor_name, " with external data is not supported.");
SafeInt<int64_t> element_count(1);
for (auto dim : attr_tensor_proto.dims()) {
element_count *= dim;
Expand All @@ -135,6 +137,8 @@ T GetDefault(const OpKernelInfo& info, const std::string& attr_name, const T& ba
ONNX_NAMESPACE::TensorProto attr_tensor_proto;
auto result = info.GetAttr("default_tensor", &attr_tensor_proto);
if (result.IsOK() && utils::HasDataType(attr_tensor_proto)) {
ORT_ENFORCE(!utils::HasExternalData(attr_tensor_proto),
"Tensor attribute default_tensor with external data is not supported.");
T default_value;
result = utils::UnpackTensor<T>(attr_tensor_proto, std::filesystem::path(), &default_value, 1);
ORT_ENFORCE(result.IsOK(), "LabelEncoder could not unpack default tensor ", attr_name);
Expand Down
3 changes: 3 additions & 0 deletions onnxruntime/core/providers/cpu/ml/tree_ensemble_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ Status GetAnyVectorAttrsOrDefault(const OpKernelInfo& info, const std::string& n
ONNX_NAMESPACE::TensorProto proto;
auto result = info.GetAttr(name, &proto);

ORT_RETURN_IF(utils::HasExternalData(proto),
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
"Tensor attribute ", name, " with external data is not supported.");

SafeInt<int64_t> n_elements(1);
for (auto dim : proto.dims()) {
n_elements *= dim;
Expand Down
174 changes: 174 additions & 0 deletions onnxruntime/test/providers/cpu/ml/label_encoder_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "gtest/gtest.h"
#include "core/framework/tensorprotoutils.h"
#include "test/providers/provider_test_utils.h"
#include <fstream>

Comment thread
yuslepukhin marked this conversation as resolved.
namespace onnxruntime {
namespace test {
Expand Down Expand Up @@ -756,5 +757,178 @@ TEST(LabelEncoder, EmptyInputOpset4) {
test.Run();
}

// External data in tensor attributes is not supported. The kernel must reject such attributes
// during construction. These tests verify the rejection.
// In no-exceptions builds, ORT_ENFORCE calls abort() so these tests cannot run.
#if !defined(ORT_NO_EXCEPTIONS)
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated

// RAII helper that creates a dummy binary file on construction and removes it on destruction.
struct ScopedExternalDataFile {
std::string path;
ScopedExternalDataFile(const std::string& filename, size_t num_bytes) : path(filename) {
std::ofstream ofs(path, std::ios::binary);
std::vector<char> data(num_bytes, 0);
ofs.write(data.data(), static_cast<std::streamsize>(num_bytes));
}
~ScopedExternalDataFile() { std::remove(path.c_str()); }
ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(ScopedExternalDataFile);
};

// Helper: create a TensorProto that references external data in the given file.
static ONNX_NAMESPACE::TensorProto MakeExternalInt64TensorProto(const std::string& name,
const std::string& filename,
int64_t num_elements) {
ONNX_NAMESPACE::TensorProto proto;
proto.set_name(name);
proto.set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64);
proto.add_dims(num_elements);
proto.set_data_location(ONNX_NAMESPACE::TensorProto_DataLocation_EXTERNAL);
auto* loc = proto.add_external_data();
loc->set_key("location");
loc->set_value(filename);
auto* offset = proto.add_external_data();
offset->set_key("offset");
offset->set_value("0");
auto* length = proto.add_external_data();
length->set_key("length");
length->set_value(std::to_string(num_elements * static_cast<int64_t>(sizeof(int64_t))));
return proto;
}

TEST(LabelEncoder, RejectsExternalDataInKeysTensorOpset4) {
ScopedExternalDataFile ext_file("label_encoder_test_ext_keys.bin", 16); // 2 x int64

OpTester test("LabelEncoder", 4, onnxruntime::kMLDomain);
test.AddAttribute("keys_tensor", MakeExternalInt64TensorProto("keys_tensor", ext_file.path, 2));

Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
// Normal values_tensor
ONNX_NAMESPACE::TensorProto values_proto;
values_proto.set_name("values_tensor");
values_proto.set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64);
values_proto.add_dims(2);
values_proto.add_int64_data(10);
values_proto.add_int64_data(20);
test.AddAttribute("values_tensor", values_proto);

ONNX_NAMESPACE::TensorProto default_proto;
default_proto.set_name("default_tensor");
default_proto.set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64);
default_proto.add_dims(1);
default_proto.add_int64_data(0);
test.AddAttribute("default_tensor", default_proto);

test.AddInput<int64_t>("X", {1, 2}, {1, 2});
test.AddOutput<int64_t>("Y", {1, 2}, {10, 20});

// CUDA EP uses a different code path that doesn't hit this issue, exclude it.
test.Run(OpTester::ExpectResult::kExpectFailure, "external data is not supported",
{kCudaExecutionProvider});
}

TEST(LabelEncoder, RejectsExternalDataInDefaultTensorOpset4) {
ScopedExternalDataFile ext_file("label_encoder_test_ext_default.bin", 8); // 1 x int64

OpTester test("LabelEncoder", 4, onnxruntime::kMLDomain);

test.AddAttribute("keys_int64s", std::vector<int64_t>{1, 2});
test.AddAttribute("values_int64s", std::vector<int64_t>{10, 20});

test.AddAttribute("default_tensor", MakeExternalInt64TensorProto("default_tensor", ext_file.path, 1));

test.AddInput<int64_t>("X", {1, 2}, {1, 3});
test.AddOutput<int64_t>("Y", {1, 2}, {10, 0});

// CUDA EP uses a different code path that doesn't hit this issue, exclude it.
test.Run(OpTester::ExpectResult::kExpectFailure, "external data is not supported",
{kCudaExecutionProvider});
}

TEST(LabelEncoder, RejectsExternalDataInValuesTensorOpset4) {
ScopedExternalDataFile ext_file("label_encoder_test_ext_values.bin", 16); // 2 x int64

OpTester test("LabelEncoder", 4, onnxruntime::kMLDomain);

// Normal keys_tensor
ONNX_NAMESPACE::TensorProto keys_proto;
keys_proto.set_name("keys_tensor");
keys_proto.set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64);
keys_proto.add_dims(2);
keys_proto.add_int64_data(1);
keys_proto.add_int64_data(2);
test.AddAttribute("keys_tensor", keys_proto);

test.AddAttribute("values_tensor", MakeExternalInt64TensorProto("values_tensor", ext_file.path, 2));

ONNX_NAMESPACE::TensorProto default_proto;
default_proto.set_name("default_tensor");
default_proto.set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64);
default_proto.add_dims(1);
default_proto.add_int64_data(0);
test.AddAttribute("default_tensor", default_proto);

test.AddInput<int64_t>("X", {1, 2}, {1, 2});
test.AddOutput<int64_t>("Y", {1, 2}, {10, 20});

// CUDA EP uses a different code path that doesn't hit this issue, exclude it.
test.Run(OpTester::ExpectResult::kExpectFailure, "external data is not supported",
{kCudaExecutionProvider});
}
Comment thread
yuslepukhin marked this conversation as resolved.

#endif // !defined(ORT_NO_EXCEPTIONS)
Comment thread
yuslepukhin marked this conversation as resolved.

// Duplicate keys: emplace() keeps the first occurrence. Verify this behavior.
TEST(LabelEncoder, DuplicateKeysFirstWinsOpset4) {
std::vector<std::int64_t> dims{1, 3};

std::vector<int64_t> input{1, 2, 3};
// key 1 maps to 10 (first), not 99 (second duplicate)
std::vector<int64_t> output{10, 20, 42};
std::vector<int64_t> key_data{1, 2, 1}; // duplicate key 1
std::vector<int64_t> value_data{10, 20, 99};

OpTester test("LabelEncoder", 4, onnxruntime::kMLDomain);

test.AddAttribute("keys_int64s", key_data);
test.AddAttribute("values_int64s", value_data);

ONNX_NAMESPACE::TensorProto default_proto;
default_proto.set_name("default_tensor");
default_proto.set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64);
default_proto.add_dims(1);
default_proto.add_int64_data(42);
test.AddAttribute("default_tensor", default_proto);

test.AddInput<int64_t>("X", dims, input);
test.AddOutput<int64_t>("Y", dims, output);

test.Run();
}

// Singleton 1D default_tensor (dims=[1]) — the ONNX spec requires this shape
TEST(LabelEncoder, SingletonDefaultTensorOpset4) {
std::vector<std::int64_t> dims{1, 3};

std::vector<int64_t> input{1, 2, 99};
std::vector<int64_t> output{10, 20, -7};

OpTester test("LabelEncoder", 4, onnxruntime::kMLDomain);

test.AddAttribute("keys_int64s", std::vector<int64_t>{1, 2});
test.AddAttribute("values_int64s", std::vector<int64_t>{10, 20});

// 1D singleton default_tensor with dims=[1]
ONNX_NAMESPACE::TensorProto default_proto;
default_proto.set_name("default_tensor");
default_proto.set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64);
default_proto.add_dims(1);
default_proto.add_int64_data(-7);
test.AddAttribute("default_tensor", default_proto);

test.AddInput<int64_t>("X", dims, input);
test.AddOutput<int64_t>("Y", dims, output);

test.Run();
}

} // namespace test
} // namespace onnxruntime
Loading
Loading