|
1 | 1 | #!/usr/bin/python |
2 | | -# Copyright 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# Copyright 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
3 | 3 | # |
4 | 4 | # Redistribution and use in source and binary forms, with or without |
5 | 5 | # modification, are permitted provided that the following conditions |
|
29 | 29 |
|
30 | 30 | sys.path.append("../common") |
31 | 31 |
|
| 32 | +import base64 |
32 | 33 | import gzip |
33 | 34 | import io |
34 | 35 | import json |
|
41 | 42 | # Constants for size calculations |
42 | 43 | # Each FP32 value is 4 bytes, so we need to divide target byte sizes by 4 to get element counts |
43 | 44 | BYTES_PER_FP32 = 4 |
| 45 | +BYTES_PER_INT64 = ( |
| 46 | + 8 # For the type size explosion test, we use int64 which is 8 bytes per element |
| 47 | +) |
44 | 48 | MB = 2**20 # 1 MB = 1,048,576 bytes |
45 | 49 | GB = 2**30 # 1 GB = 1,073,741,824 bytes |
46 | 50 | DEFAULT_LIMIT_BYTES = 64 * MB # 64MB default limit |
|
58 | 62 |
|
59 | 63 | class InferSizeLimitTest(tu.TestResultCollector): |
60 | 64 | def _get_infer_url(self, model_name): |
61 | | - return "http://localhost:8000/v2/models/{}/infer".format(model_name) |
| 65 | + return f"http://localhost:8000/v2/models/{model_name}/infer" |
| 66 | + |
| 67 | + def test_json_dtype_size_expansion_exceeds_limit_error(self): |
| 68 | + """ |
| 69 | + Test that when the client sends a JSON input of byte[], that when it |
| 70 | + expands to dtype[], it exceeds the maximum allowed input size and |
| 71 | + returns an appropriate error message. The test sends a large base64 |
| 72 | + encoded string as input, which simulates a byte[] input that would |
| 73 | + expand to a much larger dtype[] input on the server side when |
| 74 | + `sizeof(dtype) > 1`. |
| 75 | + The test checks that the error message indicates that the input size |
| 76 | + exceeds the limit. |
| 77 | + This is important to prevent clients from sending inputs that could |
| 78 | + cause excessive memory usage on the server. |
| 79 | + """ |
| 80 | + model = "onnx_zero_1_float32" |
| 81 | + |
| 82 | + # Provided data is 64MB of int8, but the model expects FP32, |
| 83 | + # which would expand to 256MB when interpreted as FP32. |
| 84 | + bytes_input = np.ones(DEFAULT_LIMIT_BYTES, dtype=np.int8) |
| 85 | + input_bytes = bytes_input.tobytes() |
| 86 | + data_str = base64.b64encode(input_bytes).decode("utf-8") |
| 87 | + headers = { |
| 88 | + "Content-Type": "application/json", |
| 89 | + "Inference-Header-Content-Length": f"{len(input_bytes)}", |
| 90 | + } |
| 91 | + shape_size = ( |
| 92 | + DEFAULT_LIMIT_ELEMENTS // BYTES_PER_INT64 |
| 93 | + ) # Calculate shape size based on int64 element count to match the byte size |
| 94 | + |
| 95 | + payload = { |
| 96 | + "inputs": [ |
| 97 | + { |
| 98 | + "name": "INPUT0", |
| 99 | + "datatype": "INT64", |
| 100 | + "shape": [1, shape_size], |
| 101 | + "data": data_str, |
| 102 | + } |
| 103 | + ] |
| 104 | + } |
| 105 | + |
| 106 | + response = requests.post( |
| 107 | + f"http://localhost:8000/v2/models/{model}/generate", |
| 108 | + headers=headers, |
| 109 | + json=payload, |
| 110 | + ) |
| 111 | + |
| 112 | + self.assertEqual( |
| 113 | + 400, |
| 114 | + response.status_code, |
| 115 | + f"Expected error code for type/size mismatch, got: {response.status_code}", |
| 116 | + ) |
| 117 | + error_msg = response.content.decode() |
| 118 | + print( |
| 119 | + f"Error message: {error_msg}", flush=True |
| 120 | + ) # Print the error message for debugging |
| 121 | + self.assertIn( |
| 122 | + "Request JSON size of ", |
| 123 | + error_msg, |
| 124 | + ) |
| 125 | + self.assertIn( |
| 126 | + " bytes exceeds the maximum allowed input size of ", |
| 127 | + error_msg, |
| 128 | + ) |
| 129 | + self.assertIn( |
| 130 | + "Use --http-max-input-size to increase the limit.", |
| 131 | + error_msg, |
| 132 | + ) |
| 133 | + |
| 134 | + # Test multiple inputs with one that causes size explosion. |
| 135 | + payload = { |
| 136 | + "inputs": [ |
| 137 | + { |
| 138 | + "name": "INPUT0", |
| 139 | + "datatype": "INT64", |
| 140 | + "shape": [1, shape_size // 2], |
| 141 | + "data": data_str[: len(data_str) // 2], |
| 142 | + }, |
| 143 | + { |
| 144 | + "name": "INPUT1", |
| 145 | + "datatype": "INT64", |
| 146 | + "shape": [1, shape_size // 2], |
| 147 | + "data": data_str[len(data_str) // 2 :], |
| 148 | + }, |
| 149 | + ] |
| 150 | + } |
| 151 | + |
| 152 | + response = requests.post( |
| 153 | + f"http://localhost:8000/v2/models/{model}/generate", |
| 154 | + headers=headers, |
| 155 | + json=payload, |
| 156 | + ) |
| 157 | + |
| 158 | + self.assertEqual( |
| 159 | + 400, |
| 160 | + response.status_code, |
| 161 | + f"Expected error code for type/size mismatch, got: {response.status_code}", |
| 162 | + ) |
| 163 | + error_msg = response.content.decode() |
| 164 | + print( |
| 165 | + f"Error message: {error_msg}", flush=True |
| 166 | + ) # Print the error message for debugging |
| 167 | + self.assertIn( |
| 168 | + "request JSON size of ", |
| 169 | + error_msg, |
| 170 | + ) |
| 171 | + self.assertIn( |
| 172 | + " bytes exceeds the maximum allowed input size of ", |
| 173 | + error_msg, |
| 174 | + ) |
| 175 | + self.assertIn( |
| 176 | + "Use --http-max-input-size to increase the limit.", |
| 177 | + error_msg, |
| 178 | + ) |
62 | 179 |
|
63 | 180 | def test_default_limit_raw_binary(self): |
64 | 181 | """Test raw binary inputs with default limit""" |
@@ -165,9 +282,16 @@ def test_default_limit_json(self): |
165 | 282 | # Verify error message contains size limit info |
166 | 283 | error_msg = response.content.decode() |
167 | 284 | self.assertIn( |
168 | | - "exceeds the maximum allowed value", |
| 285 | + "Request JSON size of ", |
| 286 | + error_msg, |
| 287 | + ) |
| 288 | + self.assertIn( |
| 289 | + " bytes exceeds the maximum allowed input size of ", |
| 290 | + error_msg, |
| 291 | + ) |
| 292 | + self.assertIn( |
| 293 | + "Use --http-max-input-size to increase the limit.", |
169 | 294 | error_msg, |
170 | | - "Expected error message about exceeding max input size", |
171 | 295 | ) |
172 | 296 |
|
173 | 297 | # Test case 2: Input just under the 64MB limit (should succeed) |
@@ -320,9 +444,16 @@ def test_large_input_json(self): |
320 | 444 | # Verify error message contains size limit info |
321 | 445 | error_msg = response.content.decode() |
322 | 446 | self.assertIn( |
323 | | - "exceeds the maximum allowed value", |
| 447 | + "request JSON size of ", |
| 448 | + error_msg, |
| 449 | + ) |
| 450 | + self.assertIn( |
| 451 | + " bytes exceeds the maximum allowed input size of ", |
| 452 | + error_msg, |
| 453 | + ) |
| 454 | + self.assertIn( |
| 455 | + "Use --http-max-input-size to increase the limit.", |
324 | 456 | error_msg, |
325 | | - "Expected error message about exceeding max input size", |
326 | 457 | ) |
327 | 458 |
|
328 | 459 | # Test case 2: Input just under the 128MB configured limit (should succeed) |
@@ -405,15 +536,15 @@ def test_large_string_in_json(self): |
405 | 536 | # Verify error message |
406 | 537 | error_msg = response.content.decode() |
407 | 538 | self.assertIn( |
408 | | - "Request JSON size", |
| 539 | + "Request JSON size of ", |
409 | 540 | error_msg, |
410 | 541 | ) |
411 | 542 | self.assertIn( |
412 | | - "exceeds the maximum allowed value", |
| 543 | + " bytes exceeds the maximum allowed input size of ", |
413 | 544 | error_msg, |
414 | 545 | ) |
415 | 546 | self.assertIn( |
416 | | - "Use --http-max-input-size to increase the limit", |
| 547 | + "Use --http-max-input-size to increase the limit.", |
417 | 548 | error_msg, |
418 | 549 | ) |
419 | 550 |
|
|
0 commit comments