Validate and inline external data in node tensor attributes during session initialization#29250
Validate and inline external data in node tensor attributes during session initialization#29250yuslepukhin wants to merge 9 commits into
Conversation
…semble Tensor attributes (keys_tensor, values_tensor, default_tensor, and *_as_tensor) in LabelEncoder and TreeEnsemble kernels did not validate against external data location. Add HasExternalData() checks before calling UnpackTensor to reject such attributes during kernel construction. Add unit tests covering external data rejection for: - LabelEncoder opset 4 (keys, values, default tensors) - TreeEnsembleRegressor opset 3 - TreeEnsemble opset 5 Also add tests for duplicate key behavior (first-wins) and scalar default_tensor.
There was a problem hiding this comment.
Pull request overview
This PR hardens ONNX Runtime’s CPU ML operator handling by rejecting tensor attributes that reference ONNX external data, preventing kernels from attempting to load attribute data from external locations during kernel construction.
Changes:
- Add kernel-side validation to reject
TensorProtoattributes that use external data forLabelEncoderandTreeEnsemble*. - Add CPU unit tests asserting construction-time failure when external-data tensor attributes are provided for
LabelEncoder,TreeEnsemble, andTreeEnsembleRegressor. - Standardize failure messaging to include “external data is not supported” for test matching.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| onnxruntime/core/providers/cpu/ml/label_encoder.h | Enforces rejection of external-data tensor attributes for keys_tensor/values_tensor and default_tensor. |
| onnxruntime/core/providers/cpu/ml/tree_ensemble_helper.cc | Adds a guard in tensor attribute unpacking to reject external data. |
| onnxruntime/test/providers/cpu/ml/label_encoder_test.cc | Adds negative tests for external-data tensor attributes on LabelEncoder (opset 4), excluding CUDA EP. |
| onnxruntime/test/providers/cpu/ml/tree_ensembler_test.cc | Adds a negative test ensuring TreeEnsemble rejects external-data tensor attributes. |
| onnxruntime/test/providers/cpu/ml/treeregressor_test.cc | Adds a negative test ensuring TreeEnsembleRegressor rejects external-data tensor attributes. |
- Move HasExternalData check before dims().empty() early-return in GetAnyVectorAttrsOrDefault so scalar external-data tensors are also rejected. - Remove conflicting nodes_values attribute from the TreeEnsembleRegressor test so the external-data rejection is the sole failure mode.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
onnxruntime/core/providers/cpu/ml/label_encoder.h:146
- The external-data rejection for default_tensor is only applied when
utils::HasDataType(attr_tensor_proto)is true. A malformeddefault_tensorTensorProto could setdata_location=EXTERNAL(and external_data entries) but omitdata_type, and this would bypass the intended rejection and fall back to the non-tensor default attributes. If the goal is to reject any tensor attribute that uses external data, the check should run whenever GetAttr succeeds, regardless of data_type presence.
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);
return default_value;
} else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, float> || std::is_same_v<T, int64_t>) {
- Create real temp files (RAII-guarded) so ONNX checker passes file-existence validation before reaching kernel construction. - Fix ScalarDefaultTensorOpset4: ONNX spec requires default_tensor to be a singleton 1D tensor (dims=[1]), not 0D. - Rename _multiply_update_array -> MultiplyUpdateArray and _multiply_update_array_string -> MultiplyUpdateArrayString to follow naming conventions.
- _multiply_update_childnode -> MultiplyUpdateChildnode - _multiply_arrays_values -> MultiplyArraysValues
|
I'm curiuous. I thought after loading the model, there was no external tensor. I did not exepect to validate that for every kernel (I'll see many PRs about that then). So do we allow external tensor in kernels in case they want to load the data themselves? |
I re-worked this. We now validate external references and inline the tensor attributes. |
- Replace fixed filenames with CreateTestFile + ScopedFileDeleter to avoid collisions when tests run in parallel via CTest. - Add explicit #include <cstdio> for std::remove. - Remove unused <fstream> include.
- Add HasExternalData check to CUDA EP non-plugin LabelEncoder path (GetAttrOrTensor and TryGetScalarTensorAttribute) so error messages are consistent across all EPs. - Remove CUDA EP exclusion from LabelEncoder tests since all EPs now reject external data uniformly. - Fix TreeEnsemble opset 5 test: nodes_splits dims must match the number of elements in nodes_featureids and other node attributes (changed from 3 to 1).
Move external data validation from per-kernel checks to a centralized graph-level pass in ConvertInitializersIntoOrtValues(). This: - Validates file paths using ValidateExternalDataPath (same as initializers) - Rejects in-memory external data references for node attributes - Reads validated external data from disk and inlines it as raw_data - Removes per-kernel HasExternalData guards (CPU, CUDA) since data is now inlined before kernels construct - Uses InlinedHashSet for validated paths - Uses utils::HasTensor/HasTensors utilities Tests updated to verify in-memory reference rejection and valid external data inlining. Uses proper kTensorProtoNativeEndianMemoryAddressTag constant.
The ONNX checker (checker::check_node) runs during Graph::Resolve() and validates external data file paths before our inlining code runs. It rejects the in-memory tag as 'not regular file'. Update expected error substrings and add comments explaining this.
- Add node/attribute context to ValidateExternalDataPath and UnpackInitializerData error messages in graph.cc - Update test comments to accurately describe ONNX checker behavior (fail_check aborts in no-exceptions builds, not ORT_ENFORCE/ORT_RETURN_IF) - Add ORT_ENFORCE checks on fwrite/fclose in CreateExternalDataFile - Add inline comments next to expected error messages explaining they originate from the ONNX checker during Graph::Resolve()
This pull request introduces improved support and validation for external data in tensor attributes, particularly ensuring that external data in node attributes is properly validated and inlined, and that in-memory references are correctly rejected. Additionally, it introduces new tests to cover these scenarios, and refactors some utility functions in the test code for clarity and consistency.
External Data Handling and Validation:
Graph::ConvertInitializersIntoOrtValues()ingraph.ccto:InlinedHashSetfor tracking validated external data paths for efficiency.Testing:
label_encoder_test.ccto verify:tree_ensembler_test.ccto ensure that in-memory external data references in node attributes are rejected, preventing invalid attribute usage.Test Utility Refactoring:
tree_ensembler_test.ccandtreeregressor_test.cc:MultiplyUpdateArray,MultiplyArraysValues,MultiplyUpdateArrayString). [1] [2] [3] [4] [5] [6] [7] [8] [9]Test File Includes: