Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion examples/nemo_megatron_gpt_multinode/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def cast_output(data, required_dtype):
elif not isinstance(data, np.ndarray):
data = np.array(data)

data_is_str = required_dtype in (object, np.object_, bytes, np.bytes_)
data_is_str = required_dtype in (object, bytes, np.bytes_)
if data_is_str:
data = np.char.encode(data, "utf-8")

Expand Down
12 changes: 5 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ classifiers = [
"Topic :: Scientific/Engineering",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -35,9 +33,9 @@ authors = []
maintainers = []
keywords = []
license = { text = "Apache 2.0" }
requires-python = ">=3.8,<4"
requires-python = ">=3.10,<4"
dependencies = [
"numpy >= 1.21, <2.0.0",
"numpy >= 2.2.6",
"protobuf >= 3.7",
"pyzmq >= 23.0",
"sh >= 1.14",
Expand All @@ -62,7 +60,7 @@ test = [
"pytest-timeout ~= 2.2",
"pytest-asyncio ~= 0.23.5",
"pytype!=2021.11.18,!=2022.2.17",
"pre-commit >= 3.5", # Python 3.8 maximum for 24.02
"pre-commit >= 3.5",
"tox >= 4.13",
"tqdm >= 4.64.1",
"psutil ~= 5.9",
Expand All @@ -86,7 +84,7 @@ dev = [
"nvidia-pytriton[test]",
"nvidia-pytriton[doc]",
"build >= 0.8, <1.0.0", # to support --plat-name for multiarch build
"ipython >= 8.12", # Python 3.8 maximum for 24.02
"ipython >= 8.12",
"packaging ~= 24.0",
"pudb >= 2024.1",
"pip >= 24.0", # to support editable installation
Expand Down Expand Up @@ -135,7 +133,7 @@ plat-name = "linux_x86_64"
preview = true
line-length = 120
indent-width = 4
target-version = "py38"
target-version = "py310"

[tool.ruff.lint]
select = [
Expand Down
2 changes: 1 addition & 1 deletion pytriton/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ def _create_request(self, inputs: _IOType):
f"Numpy array for {input_name!r} input with dtype=object should contain encoded strings \
\\(e.g. into utf-8\\). Element type: {type(input_data.reshape(-1)[0])}"
)
if input_data.dtype.type == np.str_:
if input_data.dtype.kind == "U":
raise RuntimeError(
"Unicode inputs are not supported. "
f"Encode numpy array for {input_name!r} input (ex. with np.char.encode(array, 'utf-8'))."
Expand Down
2 changes: 1 addition & 1 deletion pytriton/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def infer_fun(**inputs):

def value_to_key(value):
if isinstance(value, np.ndarray):
if value.dtype == np.object_ or value.dtype.type == np.bytes_:
if value.dtype == object or value.dtype.type == np.bytes_:
return _serialize_byte_tensor(value)
else:
return value.tobytes()
Expand Down
2 changes: 1 addition & 1 deletion pytriton/model_config/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@


def _convert_dtype_to_triton_dtype(dtype: Union[Type[np.dtype], Type[object]]) -> str:
if dtype in [np.object_, object, bytes, np.bytes_]:
if dtype in [object, bytes, np.bytes_]:
dtype = "TYPE_STRING"
Comment on lines 52 to 54

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat np.object_ as TYPE_STRING in dtype conversion

If a user still supplies np.object_ in a TensorSpec (valid in NumPy 2.x), this branch no longer recognizes it as a string type and falls through to client_utils.np_to_triton_dtype, which doesn't accept object dtypes. That turns a previously supported config into a runtime error when generating the model config. Consider normalizing np.object_ to object or adding it back to the string-type list to preserve compatibility with existing configs.

Useful? React with 👍 / 👎.

else:
# pytype: disable=attribute-error
Expand Down
8 changes: 4 additions & 4 deletions pytriton/proxy/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
def _serialize_byte_tensor(tensor) -> bytes:
"""Serializes a bytes tensor into a flat numpy array of length prepended bytes.

The numpy array should use dtype of np.object_. For np.bytes_,
The numpy array should use dtype of object. For np.bytes_,
numpy will remove trailing zeros at the end of byte sequence and because
of this it should be avoided.

Expand All @@ -76,14 +76,14 @@ def _serialize_byte_tensor(tensor) -> bytes:
# If the input is a tensor of string/bytes objects, then must flatten those
# into a 1-dimensional array containing the 4-byte byte size followed by the
# actual element bytes. All elements are concatenated together in "C" order.
assert (tensor.dtype == np.object_) or (tensor.dtype.type == np.bytes_)
assert (tensor.dtype == object) or (tensor.dtype.type == np.bytes_)
flattened_ls = []
total_len = 0
for obj in np.nditer(tensor, flags=["refs_ok"], order="C"):
# If directly passing bytes to BYTES type,
# don't convert it to str as Python will encode the
# bytes which may distort the meaning
if tensor.dtype == np.object_ and not isinstance(obj.item(), bytes):
if tensor.dtype == object and not isinstance(obj.item(), bytes):
s = str(obj.item()).encode("utf-8")
else:
s = obj.item()
Expand Down Expand Up @@ -201,7 +201,7 @@ def calc_serialized_size_of_numpy_with_struct_header(tensor: np.ndarray) -> List
items_sizes = []
order = "C" if tensor.flags.c_contiguous else "F"
for obj in np.nditer(tensor, flags=["refs_ok"], order=order):
if tensor.dtype == np.object_ and not isinstance(obj.item(), bytes):
if tensor.dtype == object and not isinstance(obj.item(), bytes):
s = str(obj.item()).encode("utf-8")
else:
s = obj.item()
Expand Down
2 changes: 1 addition & 1 deletion pytriton/proxy/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def _validate_output_dtype_and_shape(model_config, model_outputs, name, value):
f"Returned output `{name}` is not defined in model config for model `{model_config.model_name}`."
)

allowed_object_types = [bytes, object, np.bytes_, np.object_]
allowed_object_types = [bytes, object, np.bytes_]
if (value.dtype.kind not in "OSU" and not np.issubdtype(value.dtype, output_config.dtype)) or (
value.dtype.kind in "OSU" and output_config.dtype not in allowed_object_types
Comment on lines +148 to 150

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep np.object_ in allowed output dtypes

Models configured with output_config.dtype = np.object_ (common in NumPy 1.x/2.x for string-like outputs) will now be rejected even if the actual output is an object array, because np.object_ was removed from allowed_object_types. This is a regression from the previous behavior and will raise ValueError for otherwise valid outputs. Adding np.object_ back (or normalizing it to object) would avoid breaking existing model configs.

Useful? React with 👍 / 👎.

):
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,12 @@ def sample1(**inputs):
( # extract 1st item (scalar) from 1D array of strings (objects)
{"a": np.array([1, 2, 3]), "b": np.array(["val1", "val1"], dtype=object)},
["b"],
{"a": np.array([1, 2, 3]), "b": np.object_("val1")},
{"a": np.array([1, 2, 3]), "b": np.asarray("val1", dtype=object)[()]},
),
( # extract 1st item (scalar) from 3D array of strings (objects) with shape (batch_size, 1, 1)
{"a": np.array([1, 2, 3]), "b": np.array([[["val1"]], [["val1"]]], dtype=object)},
["b"],
{"a": np.array([1, 2, 3]), "b": np.object_("val1")},
{"a": np.array([1, 2, 3]), "b": np.asarray("val1", dtype=object)[()]},
),
( # do not raise error when key is missing in inputs
{"a": np.array([1, 2, 3]), "b": np.array([1, 1, 1])},
Expand Down
145 changes: 145 additions & 0 deletions tests/unit/test_numpy2_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests to verify numpy 2.x compatibility of dtype handling."""

import numpy as np
import pytest


class TestNumpyDtypeCompatibility:
"""Test that dtype comparisons work correctly with numpy 2.x."""

def test_object_dtype_comparison(self):
"""Verify dtype == object works for object arrays."""
arr = np.array(["hello", "world"], dtype=object)
assert arr.dtype == object
assert arr.dtype.kind == "O"

def test_bytes_dtype_comparison(self):
"""Verify np.bytes_ still works in numpy 2.x."""
arr = np.array([b"hello", b"world"], dtype=np.bytes_)
assert arr.dtype.type == np.bytes_
assert arr.dtype.kind == "S"

def test_unicode_dtype_kind(self):
"""Verify dtype.kind == 'U' detects unicode string arrays."""
arr = np.array(["hello", "world"]) # default is unicode
assert arr.dtype.kind == "U"

arr_explicit = np.array(["hello", "world"], dtype="U10")
assert arr_explicit.dtype.kind == "U"

arr_str = np.array(["hello", "world"], dtype=str)
assert arr_str.dtype.kind == "U"

def test_non_unicode_arrays(self):
"""Verify non-unicode arrays are not detected as unicode."""
arr_bytes = np.array([b"hello", b"world"])
assert arr_bytes.dtype.kind != "U"

arr_object = np.array(["hello", "world"], dtype=object)
assert arr_object.dtype.kind != "U"

arr_int = np.array([1, 2, 3])
assert arr_int.dtype.kind != "U"

def test_object_scalar_creation(self):
"""Verify object scalar creation works with np.asarray."""
scalar = np.asarray("val1", dtype=object)[()]
assert scalar == "val1"
assert isinstance(scalar, str)

def test_allowed_object_types_list(self):
"""Verify the allowed_object_types list works correctly."""
allowed_object_types = [bytes, object, np.bytes_]

# object dtype should match
assert object in allowed_object_types

# bytes type should match
assert bytes in allowed_object_types

# np.bytes_ should match
assert np.bytes_ in allowed_object_types


class TestSerializationCompatibility:
"""Test serialization functions work with numpy 2.x."""

def test_serialize_deserialize_numeric_arrays(self):
"""Test round-trip serialization of numeric arrays."""
from pytriton.proxy.data import (
deserialize_numpy_with_struct_header,
serialize_numpy_with_struct_header,
)

test_arrays = [
np.array([1, 2, 3], dtype=np.int32),
np.array([1.5, 2.5, 3.5], dtype=np.float64),
np.array([[1, 2], [3, 4]], dtype=np.int64),
]

for arr in test_arrays:
frames = serialize_numpy_with_struct_header(arr)
result = deserialize_numpy_with_struct_header(frames)
assert np.array_equal(arr, result)
assert arr.dtype == result.dtype

def test_serialize_deserialize_bytes_object_array(self):
"""Test round-trip serialization of bytes in object array."""
from pytriton.proxy.data import (
deserialize_numpy_with_struct_header,
serialize_numpy_with_struct_header,
)

arr = np.array([b"hello", b"world"], dtype=object)
frames = serialize_numpy_with_struct_header(arr)
result = deserialize_numpy_with_struct_header(frames)

assert result.dtype == object
assert all(a == b for a, b in zip(arr.flat, result.flat))


class TestDecoratorCompatibility:
"""Test decorator functions work with numpy 2.x."""

def test_value_to_key_object_dtype(self):
"""Test the value_to_key logic from decorators.py."""

def value_to_key(value):
if isinstance(value, np.ndarray):
if value.dtype == object or value.dtype.type == np.bytes_:
return "bytes_path"
else:
return "tobytes_path"
return value

arr_obj = np.array(["test"], dtype=object)
arr_bytes = np.array([b"test"], dtype=np.bytes_)
arr_int = np.array([1, 2, 3])

assert value_to_key(arr_obj) == "bytes_path"
assert value_to_key(arr_bytes) == "bytes_path"
assert value_to_key(arr_int) == "tobytes_path"


class TestGeneratorCompatibility:
"""Test model config generator works with numpy 2.x."""

def test_dtype_to_triton_dtype_conversion(self):
"""Test dtype conversion for string types."""
string_types = [object, bytes, np.bytes_]

for dtype in string_types:
assert dtype in [object, bytes, np.bytes_]
4 changes: 2 additions & 2 deletions tests/unit/test_proxy_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def test_validate_output_data_throws_exception_when_value_is_not_supported_data_

def test_validate_output_data_throws_exception_when_value_is_list_of_strings():
name = "output1"
value = np.array(["abcd", "efgg"], dtype=np.object_)
value = np.array(["abcd", "efgg"], dtype=object)

with pytest.raises(
ValueError,
Expand All @@ -170,7 +170,7 @@ def test_validate_output_data_throws_exception_when_value_is_list_of_strings():

def test_validate_output_data_throws_exception_when_value_is_list_of_ints_defined_as_object():
name = "output1"
value = np.array([123, 456], dtype=np.object_)
value = np.array([123, 456], dtype=object)

with pytest.raises(
ValueError,
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
[tox]
envlist = py38, py39, py310, py311, codeblocks, pytype, pre-commit
envlist = py310, py311, py312, py313, codeblocks, pytype, pre-commit
isolated_build = True

[testenv]
Expand Down