Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/guidellm/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
"EndlessTextCreator",
"InfoMixin",
"IntegerRangeSampler",
"camelize_str",
"InterProcessMessaging",
"InterProcessMessagingManagerQueue",
"InterProcessMessagingPipe",
Expand All @@ -107,14 +106,15 @@
"ThreadSafeSingletonMixin",
"TimeRunningStats",
"all_defined",
"camelize_str",
"check_load_processor",
"clean_text",
"filter_text",
"format_value_display",
"get_literal_vals",
"is_punctuation",
"recursive_key_update",
"load_text",
"recursive_key_update",
"safe_add",
"safe_divide",
"safe_format_timestamp",
Expand Down
2 changes: 1 addition & 1 deletion src/guidellm/utils/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def print_update_details(self, details: Any | None):
block = Padding(
Text.from_markup(str(details)),
(0, 0, 0, 2),
style=StatusStyles.get("debug"),
style=StatusStyles.get("debug", "dim"),
)
self.print(block)

Expand Down
111 changes: 62 additions & 49 deletions src/guidellm/utils/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@

import json
from collections.abc import Mapping
from typing import Annotated, Any, ClassVar, Generic, Literal, Optional, TypeVar
from typing import Annotated, Any, ClassVar, Generic, Literal, Optional, TypeVar, cast

try:
import msgpack
import msgpack # type: ignore[import-untyped] # Optional dependency
from msgpack import Packer, Unpacker

HAS_MSGPACK = True
Expand All @@ -24,16 +24,20 @@
HAS_MSGPACK = False

try:
from msgspec.msgpack import Decoder as MsgspecDecoder
from msgspec.msgpack import Encoder as MsgspecEncoder
from msgspec.msgpack import ( # type: ignore[import-not-found] # Optional dependency
Decoder as MsgspecDecoder,
)
from msgspec.msgpack import ( # type: ignore[import-not-found] # Optional dependency
Encoder as MsgspecEncoder,
)

HAS_MSGSPEC = True
except ImportError:
MsgspecDecoder = MsgspecEncoder = None
HAS_MSGSPEC = False

try:
import orjson
import orjson # type: ignore[import-not-found] # Optional dependency

HAS_ORJSON = True
except ImportError:
Expand Down Expand Up @@ -116,7 +120,7 @@ def encode_message(
"""
serialized = serializer.serialize(obj) if serializer else obj

return encoder.encode(serialized) if encoder else serialized
return cast("MsgT", encoder.encode(serialized) if encoder else serialized)

@classmethod
def decode_message(
Expand All @@ -137,7 +141,9 @@ def decode_message(
"""
serialized = encoder.decode(message) if encoder else message

return serializer.deserialize(serialized) if serializer else serialized
return cast(
"ObjT", serializer.deserialize(serialized) if serializer else serialized
)

def __init__(
self,
Expand Down Expand Up @@ -296,6 +302,15 @@ def _get_available_encoder_decoder(
return None, None, None


PayloadType = Literal[
"pydantic",
"python",
"collection_tuple",
"collection_sequence",
"collection_mapping",
]


class Serializer:
"""
Object serialization with specialized Pydantic model support.
Expand Down Expand Up @@ -474,6 +489,7 @@ def to_sequence(self, obj: Any) -> str | Any:
:param obj: Object to serialize to sequence format
:return: Serialized sequence string or bytes
"""
payload_type: PayloadType
if isinstance(obj, BaseModel):
payload_type = "pydantic"
payload = self.to_sequence_pydantic(obj)
Expand Down Expand Up @@ -515,7 +531,9 @@ def to_sequence(self, obj: Any) -> str | Any:
payload_type = "python"
payload = self.to_sequence_python(obj)

return self.pack_next_sequence(payload_type, payload, None)
return self.pack_next_sequence(
payload_type, payload if payload is not None else "", None
)

def from_sequence(self, data: str | Any) -> Any: # noqa: C901, PLR0912
"""
Expand All @@ -529,6 +547,7 @@ def from_sequence(self, data: str | Any) -> Any: # noqa: C901, PLR0912
:raises ValueError: If sequence format is invalid or contains multiple
packed sequences
"""
payload: str | bytes | None
type_, payload, remaining = self.unpack_next_sequence(data)
if remaining is not None:
raise ValueError("Data contains multiple packed sequences; expected one.")
Expand All @@ -540,16 +559,16 @@ def from_sequence(self, data: str | Any) -> Any: # noqa: C901, PLR0912
return self.from_sequence_python(payload)

if type_ in {"collection_sequence", "collection_tuple"}:
items = []
c_items = []
while payload:
type_, item_payload, payload = self.unpack_next_sequence(payload)
if type_ == "pydantic":
items.append(self.from_sequence_pydantic(item_payload))
c_items.append(self.from_sequence_pydantic(item_payload))
elif type_ == "python":
items.append(self.from_sequence_python(item_payload))
c_items.append(self.from_sequence_python(item_payload))
else:
raise ValueError("Invalid type in collection sequence")
return items
return c_items

if type_ != "collection_mapping":
raise ValueError(f"Invalid type for mapping sequence: {type_}")
Expand Down Expand Up @@ -604,6 +623,7 @@ def from_sequence_pydantic(self, data: str | bytes) -> BaseModel:
:param data: Sequence data containing class metadata and JSON
:return: Reconstructed Pydantic model instance
"""
json_data: str | bytes | bytearray
if isinstance(data, bytes):
class_name_end = data.index(b"|")
class_name = data[:class_name_end].decode()
Expand Down Expand Up @@ -647,13 +667,7 @@ def from_sequence_python(self, data: str | bytes) -> Any:

def pack_next_sequence( # noqa: C901, PLR0912
self,
type_: Literal[
"pydantic",
"python",
"collection_tuple",
"collection_sequence",
"collection_mapping",
],
type_: PayloadType,
payload: str | bytes,
current: str | bytes | None,
) -> str | bytes:
Expand All @@ -672,9 +686,11 @@ def pack_next_sequence( # noqa: C901, PLR0912
raise ValueError("Payload and current must be of the same type")

payload_len = len(payload)

payload_len_output: str | bytes
payload_type: str | bytes
delimiter: str | bytes
if isinstance(payload, bytes):
payload_len = payload_len.to_bytes(
payload_len_output = payload_len.to_bytes(
length=(payload_len.bit_length() + 7) // 8 if payload_len > 0 else 1,
byteorder="big",
)
Expand All @@ -692,7 +708,7 @@ def pack_next_sequence( # noqa: C901, PLR0912
raise ValueError(f"Unknown type for packing: {type_}")
delimiter = b"|"
else:
payload_len = str(payload_len)
payload_len_output = str(payload_len)
if type_ == "pydantic":
payload_type = "P"
elif type_ == "python":
Expand All @@ -707,20 +723,16 @@ def pack_next_sequence( # noqa: C901, PLR0912
raise ValueError(f"Unknown type for packing: {type_}")
delimiter = "|"

next_sequence = payload_type + delimiter + payload_len + delimiter + payload

return current + next_sequence if current else next_sequence
# Type ignores because types are enforced at runtime
next_sequence = (
payload_type + delimiter + payload_len_output + delimiter + payload # type: ignore[operator]
)
return current + next_sequence if current else next_sequence # type: ignore[operator]

def unpack_next_sequence( # noqa: C901, PLR0912
self, data: str | bytes
) -> tuple[
Literal[
"pydantic",
"python",
"collection_tuple",
"collection_sequence",
"collection_mapping",
],
PayloadType,
str | bytes,
str | bytes | None,
]:
Expand All @@ -731,57 +743,58 @@ def unpack_next_sequence( # noqa: C901, PLR0912
:return: Tuple of (type, payload, remaining_data)
:raises ValueError: If sequence format is invalid or unknown type character
"""
type_: PayloadType
if isinstance(data, bytes):
if len(data) < len(b"T|N") or data[1:2] != b"|":
raise ValueError("Invalid packed data format")

type_char = data[0:1]
if type_char == b"P":
type_char_b = data[0:1]
if type_char_b == b"P":
type_ = "pydantic"
elif type_char == b"p":
elif type_char_b == b"p":
type_ = "python"
elif type_char == b"T":
elif type_char_b == b"T":
type_ = "collection_tuple"
elif type_char == b"S":
elif type_char_b == b"S":
type_ = "collection_sequence"
elif type_char == b"M":
elif type_char_b == b"M":
type_ = "collection_mapping"
else:
raise ValueError("Unknown type character in packed data")

len_end = data.index(b"|", 2)
payload_len = int.from_bytes(data[2:len_end], "big")
payload = data[len_end + 1 : len_end + 1 + payload_len]
remaining = (
payload_b = data[len_end + 1 : len_end + 1 + payload_len]
remaining_b = (
data[len_end + 1 + payload_len :]
if len_end + 1 + payload_len < len(data)
else None
)

return type_, payload, remaining
return type_, payload_b, remaining_b

if len(data) < len("T|N") or data[1] != "|":
raise ValueError("Invalid packed data format")

type_char = data[0]
if type_char == "P":
type_char_s = data[0]
if type_char_s == "P":
type_ = "pydantic"
elif type_char == "p":
elif type_char_s == "p":
type_ = "python"
elif type_char == "S":
elif type_char_s == "S":
type_ = "collection_sequence"
elif type_char == "M":
elif type_char_s == "M":
type_ = "collection_mapping"
else:
raise ValueError("Unknown type character in packed data")

len_end = data.index("|", 2)
payload_len = int(data[2:len_end])
payload = data[len_end + 1 : len_end + 1 + payload_len]
remaining = (
payload_s = data[len_end + 1 : len_end + 1 + payload_len]
remaining_s = (
data[len_end + 1 + payload_len :]
if len_end + 1 + payload_len < len(data)
else None
)

return type_, payload, remaining
return type_, payload_s, remaining_s
15 changes: 8 additions & 7 deletions src/guidellm/utils/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,20 @@ def safe_add(
if not values:
return default

values = list(values)
values_list = list(values)

if signs is None:
signs = [1] * len(values)
signs = [1] * len(values_list)

if len(signs) != len(values):
if len(signs) != len(values_list):
raise ValueError("Length of signs must match length of values")

result = values[0] if values[0] is not None else default
result = values_list[0] if values_list[0] is not None else default

for ind in range(1, len(values)):
val = values[ind] if values[ind] is not None else default
result += signs[ind] * val
for ind in range(1, len(values_list)):
value = values_list[ind]
checked_value = value if value is not None else default
result += signs[ind] * checked_value

return result

Expand Down
Loading
Loading