Skip to content

Commit 8dc4a2f

Browse files
pskiran1mc-nv
andcommitted
fix: Improve data type validation for classification (#8267)
Co-authored-by: Misha Chornyi <99709299+mc-nv@users.noreply.github.com>
1 parent 89888ce commit 8dc4a2f

3 files changed

Lines changed: 175 additions & 5 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions
7+
# are met:
8+
# * Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
# * Redistributions in binary form must reproduce the above copyright
11+
# notice, this list of conditions and the following disclaimer in the
12+
# documentation and/or other materials provided with the distribution.
13+
# * Neither the name of NVIDIA CORPORATION nor the names of its
14+
# contributors may be used to endorse or promote products derived
15+
# from this software without specific prior written permission.
16+
#
17+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
18+
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24+
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25+
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+
29+
import sys
30+
31+
sys.path.append("../common")
32+
33+
import os
34+
import unittest
35+
36+
import numpy as np
37+
import test_util as tu
38+
import tritonclient.grpc as grpcclient
39+
import tritonclient.http as httpclient
40+
from tritonclient.utils import InferenceServerException
41+
42+
43+
class ClassificationParameterTest(tu.TestResultCollector):
44+
def setUp(self):
45+
self.protocol = os.environ.get("CLIENT_TYPE", "http")
46+
if self.protocol == "http":
47+
self.client = httpclient.InferenceServerClient("localhost:8000")
48+
else:
49+
self.client = grpcclient.InferenceServerClient("localhost:8001")
50+
51+
def _prepare_io(self, input_data, dtype):
52+
if self.protocol == "http":
53+
inputs = [httpclient.InferInput("INPUT0", input_data.shape, dtype)]
54+
outputs = [httpclient.InferRequestedOutput(name="OUTPUT0", class_count=5)]
55+
else:
56+
inputs = [grpcclient.InferInput("INPUT0", input_data.shape, dtype)]
57+
outputs = [grpcclient.InferRequestedOutput(name="OUTPUT0", class_count=5)]
58+
inputs[0].set_data_from_numpy(input_data)
59+
return inputs, outputs
60+
61+
def test_classificattion(self):
62+
shape = (1, 8)
63+
dtype = "FP32"
64+
model_name = "identity_fp32"
65+
input_data = np.ones(shape, dtype=np.float32)
66+
67+
inputs, outputs = self._prepare_io(input_data, dtype)
68+
result = self.client.infer(
69+
model_name=model_name, inputs=inputs, outputs=outputs
70+
)
71+
output = result.get_output("OUTPUT0")
72+
if self.protocol == "http":
73+
output_dtype = output["datatype"]
74+
else:
75+
output_dtype = output.datatype
76+
77+
self.assertEqual(output_dtype, "BYTES")
78+
79+
# Validate shape matches to the class_count
80+
output_data = result.as_numpy("OUTPUT0")
81+
self.assertIsNotNone(output_data)
82+
self.assertEqual(output_data.shape, (1, 5))
83+
84+
for res_str_bytes in np.nditer(output_data, flags=["refs_ok"]):
85+
res_str = res_str_bytes.item().decode("utf-8")
86+
self.assertTrue(res_str.startswith("1.000000:"))
87+
88+
def test_classificattion_unsupported_data_type(self):
89+
shape = (1, 8)
90+
model_name = "identity_bytes"
91+
dtype = "BYTES"
92+
input_data = np.array([["test"] * shape[1]], dtype=object)
93+
94+
inputs, outputs = self._prepare_io(input_data, dtype)
95+
with self.assertRaises(InferenceServerException) as e:
96+
self.client.infer(model_name=model_name, inputs=inputs, outputs=outputs)
97+
98+
self.assertIn(
99+
"class result not available for output due to unsupported type 'BYTES'",
100+
str(e.exception),
101+
)
102+
103+
104+
if __name__ == "__main__":
105+
unittest.main()

qa/L0_parameters/test.sh

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
# Copyright 2023-2024, NVIDIA CORPORATION. All rights reserved.
2+
# Copyright 2023-2025, NVIDIA CORPORATION. All rights reserved.
33
#
44
# Redistribution and use in source and binary forms, with or without
55
# modification, are permitted provided that the following conditions
@@ -97,10 +97,65 @@ for i in "${all_tests[@]}"; do
9797
wait $SERVER_PID
9898
done
9999

100+
101+
# Test Classification Extension
102+
PYTHON_MODELS_DIR="${PYTHON_MODELS_DIR:-/opt/tritonserver/qa/python_models}"
103+
MODELDIR="models"
104+
TEST_RESULT_FILE="test_results.txt"
105+
TEST_SCRIPT_PY="./class_count_test.py"
106+
107+
rm -rf $MODELDIR
108+
mkdir -p "${MODELDIR}/identity_fp32/1"
109+
cp ${PYTHON_MODELS_DIR}/identity_fp32/config.pbtxt "${MODELDIR}/identity_fp32/"
110+
cp ${PYTHON_MODELS_DIR}/identity_fp32/model.py "${MODELDIR}/identity_fp32/1/"
111+
112+
mkdir -p "${MODELDIR}/identity_bytes/1"
113+
cp ${PYTHON_MODELS_DIR}/identity_fp32/config.pbtxt "${MODELDIR}/identity_bytes/"
114+
cp ${PYTHON_MODELS_DIR}/identity_fp32/model.py "${MODELDIR}/identity_bytes/1/"
115+
(cd "${MODELDIR}/identity_bytes" && \
116+
sed -i 's/identity_fp32/identity_bytes/' config.pbtxt && \
117+
sed -i 's/TYPE_FP32/TYPE_STRING/' config.pbtxt )
118+
119+
SERVER_ARGS="--model-repository=`pwd`/${MODELDIR} --log-verbose=1"
120+
for client_type in http grpc; do
121+
export CLIENT_TYPE=$client_type
122+
SERVER_LOG="./class_count_test_${client_type}_server.log"
123+
CLIENT_LOG="./class_count_test_${client_type}_client.log"
124+
rm -f $SERVER_LOG $CLIENT_LOG
125+
run_server
126+
if [ "$SERVER_PID" == "0" ]; then
127+
echo -e "\n***\n*** Failed to start $SERVER\n***"
128+
cat $SERVER_LOG
129+
exit 1
130+
fi
131+
132+
set +e
133+
python3 $TEST_SCRIPT_PY -v >>"$CLIENT_LOG" 2>&1
134+
if [ $? -ne 0 ]; then
135+
cat $CLIENT_LOG
136+
echo -e "\n***\n*** Test Failed - class_count_${client_type}_test_client\n***"
137+
RET=1
138+
else
139+
check_test_results $TEST_RESULT_FILE 2
140+
if [ $? -ne 0 ]; then
141+
cat $TEST_RESULT_FILE
142+
echo -e "\n***\n*** Test Result Verification Failed - class_count_${client_type}_test_client\n***"
143+
RET=1
144+
fi
145+
fi
146+
kill $SERVER_PID
147+
wait $SERVER_PID
148+
149+
if [ $? -ne 0 ]; then
150+
echo -e "\n***\n*** Test Server shut down non-gracefully\n***"
151+
RET=1
152+
fi
153+
set -e
154+
done
155+
100156
if [ $RET -eq 0 ]; then
101157
echo -e "\n***\n*** Test Passed\n***"
102158
else
103-
cat $CLIENT_LOG
104159
echo -e "\n***\n*** Test FAILED\n***"
105160
fi
106161

src/classification.cc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
1+
// Copyright (c) 2020-2025, NVIDIA CORPORATION. All rights reserved.
22
//
33
// Redistribution and use in source and binary forms, with or without
44
// modification, are permitted provided that the following conditions
@@ -77,8 +77,18 @@ TopkClassifications(
7777
const TRITONSERVER_DataType datatype, const uint32_t req_class_count,
7878
std::vector<std::string>* class_strs)
7979
{
80-
const size_t element_cnt =
81-
byte_size / TRITONSERVER_DataTypeByteSize(datatype);
80+
const uint32_t dtype_byte_size = TRITONSERVER_DataTypeByteSize(datatype);
81+
if (dtype_byte_size == 0) {
82+
return TRITONSERVER_ErrorNew(
83+
TRITONSERVER_ERROR_INVALID_ARG,
84+
std::string(
85+
std::string("class result not available for output due to "
86+
"unsupported type '") +
87+
std::string(TRITONSERVER_DataTypeString(datatype)) + "'")
88+
.c_str());
89+
}
90+
91+
const size_t element_cnt = byte_size / dtype_byte_size;
8292

8393
switch (datatype) {
8494
case TRITONSERVER_TYPE_UINT8:

0 commit comments

Comments
 (0)