diff --git a/qa/L0_pytorch_python_runtime/test.sh b/qa/L0_pytorch_python_runtime/test.sh index 0fbd8f00bc..253445ac7b 100755 --- a/qa/L0_pytorch_python_runtime/test.sh +++ b/qa/L0_pytorch_python_runtime/test.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -122,6 +122,24 @@ else fi done + # Regression guard: core auto-complete must not fill in + # default_model_filename for a model using the Python-based runtime. + # If it does, 'neuralnet' is selected as TorchScript and torch.jit.load() + # fails on its weights file with a "constants.pkl not found" error. + grep "Loading 'neuralnet' as TorchScript" $SERVER_LOG + if [ $? -eq 0 ]; then + echo -e "\n***\n*** 'neuralnet' took the TorchScript load path: core autofilled default_model_filename for a runtime \"model.py\" model. \n***" + RET=1 + fi + + if ! NEURALNET_DEFAULT_FILENAME=$(curl -sf localhost:8000/v2/models/neuralnet/config | python3 -c "import json,sys; c=json.load(sys.stdin); assert c.get('name')=='neuralnet', c; print(c.get('default_model_filename', ''))"); then + echo -e "\n***\n*** Failed to retrieve or parse the configuration for 'neuralnet'. \n***" + RET=1 + elif [ "$NEURALNET_DEFAULT_FILENAME" != "" ]; then + echo -e "\n***\n*** Expected empty default_model_filename for 'neuralnet', got \"$NEURALNET_DEFAULT_FILENAME\". \n***" + RET=1 + fi + # Infer TorchScript model CLIENT_LOG="./infer.torchscript.log" python $IMAGE_CLIENT -m "resnet50_libtorch" -s INCEPTION -c 1 -b 2 "$IMAGE_DIR/vulture.jpeg" > $CLIENT_LOG 2>&1 diff --git a/qa/L0_pytorch_python_runtime/unit_test.py b/qa/L0_pytorch_python_runtime/unit_test.py index 5b69f23a8a..397ee15ac0 100755 --- a/qa/L0_pytorch_python_runtime/unit_test.py +++ b/qa/L0_pytorch_python_runtime/unit_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -26,7 +26,10 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import os +import shutil import sys +import tempfile import unittest import torch @@ -34,7 +37,11 @@ # satisfy Python runtime import requirements sys.modules["triton_python_backend_utils"] = unittest.mock.MagicMock() # import modules from Python runtime to be tested -from py_runtime import _gather_torch_tensors, _scatter_torch_tensors +from py_runtime import ( # noqa: E402 + _gather_torch_tensors, + _get_model_path, + _scatter_torch_tensors, +) class PyTorchPythonBackendRuntimeUnittest(unittest.TestCase): @@ -150,5 +157,41 @@ def test_scatter_torch_tensors(self): self.assertTrue(torch.equal(tensor, expected_tensor)) +class PyTorchPythonRuntimeModelPathTest(unittest.TestCase): + """For a Python-class model, 'model.py' must be selected over a + sibling weights file 'model.pt' unless the user explicitly requests the + weights file via default_model_filename.""" + + def setUp(self): + self._model_dir = tempfile.mkdtemp() + pb_utils = sys.modules["triton_python_backend_utils"] + pb_utils.get_model_dir.return_value = self._model_dir + + def tearDown(self): + shutil.rmtree(self._model_dir) + + def _touch(self, filename): + path = os.path.join(self._model_dir, filename) + open(path, "w").close() + return path + + def test_model_py_preferred_over_sibling_weights(self): + expected = self._touch("model.py") + self._touch("model.pt") + config = {"default_model_filename": ""} + self.assertEqual(_get_model_path(config), expected) + + def test_explicit_default_model_filename_wins(self): + self._touch("model.py") + expected = self._touch("model.pt") + config = {"default_model_filename": "model.pt"} + self.assertEqual(_get_model_path(config), expected) + + def test_torchscript_only_model(self): + expected = self._touch("model.pt") + config = {"default_model_filename": ""} + self.assertEqual(_get_model_path(config), expected) + + if __name__ == "__main__": unittest.main()