mirror of
https://git.datalinker.icu/vllm-project/vllm.git
synced 2025-12-10 05:15:42 +08:00
Signed-off-by: vnadathur <glvikramn@gmail.com> Signed-off-by: WorldExplored <srreyansh.sethi@gmail.com> Signed-off-by: Srreyansh Sethi <srreyansh.sethi@gmail.com> Signed-off-by: Srreyansh Sethi <107075589+WorldExplored@users.noreply.github.com> Co-authored-by: WorldExplored <srreyansh.sethi@gmail.com> Co-authored-by: Srreyansh Sethi <107075589+worldexplored@users.noreply.github.com> Co-authored-by: vnadathur <236933696+vnadathur@users.noreply.github.com> Co-authored-by: Luka Govedič <ProExpertProg@users.noreply.github.com>
167 lines
4.9 KiB
Python
167 lines
4.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
|
|
import pytest
|
|
|
|
from vllm.config.utils import get_hash_factors, hash_factors, normalize_value
|
|
|
|
# Helpers
|
|
|
|
|
|
def endswith_fqname(obj, suffix: str) -> bool:
|
|
# normalize_value(type) returns fully-qualified name
|
|
# Compare suffix to avoid brittle import paths.
|
|
out = normalize_value(obj)
|
|
return isinstance(out, str) and out.endswith(suffix)
|
|
|
|
|
|
def expected_path(p_str: str = ".") -> str:
|
|
import pathlib
|
|
|
|
p = pathlib.Path(p_str)
|
|
return p.expanduser().resolve().as_posix()
|
|
|
|
|
|
# Minimal dataclass to test get_hash_factors.
|
|
# Avoid importing heavy vLLM configs.
|
|
@dataclass
|
|
class SimpleConfig:
|
|
a: object
|
|
b: object | None = None
|
|
|
|
|
|
class DummyLogprobsMode(Enum):
|
|
RAW_LOGITS = "raw_logits"
|
|
|
|
|
|
def test_hash_factors_deterministic():
|
|
"""Test that hash_factors produces consistent SHA-256 hashes"""
|
|
factors = {"a": 1, "b": "test"}
|
|
hash1 = hash_factors(factors)
|
|
hash2 = hash_factors(factors)
|
|
|
|
assert hash1 == hash2
|
|
# Dict key insertion order should not affect the hash.
|
|
factors_reordered = {"b": "test", "a": 1}
|
|
assert hash_factors(factors_reordered) == hash1
|
|
assert len(hash1) == 64
|
|
assert all(c in "0123456789abcdef" for c in hash1)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"inp, expected",
|
|
[
|
|
(None, None),
|
|
(True, True),
|
|
(1, 1),
|
|
(1.0, 1.0),
|
|
("x", "x"),
|
|
(b"ab", "6162"),
|
|
(bytearray(b"ab"), "6162"),
|
|
([1, 2], (1, 2)),
|
|
({"b": 2, "a": 1}, (("a", 1), ("b", 2))),
|
|
],
|
|
)
|
|
def test_normalize_value_matrix(inp, expected):
|
|
"""Parametric input→expected normalization table."""
|
|
assert normalize_value(inp) == expected
|
|
|
|
|
|
def test_normalize_value_enum():
|
|
# Enums normalize to (module.QualName, value).
|
|
# DummyLogprobsMode uses a string payload.
|
|
out = normalize_value(DummyLogprobsMode.RAW_LOGITS)
|
|
assert isinstance(out, tuple)
|
|
assert out[0].endswith("DummyLogprobsMode")
|
|
# Expect string payload 'raw_logits'.
|
|
assert out[1] == "raw_logits"
|
|
|
|
|
|
def test_normalize_value_set_order_insensitive():
|
|
# Sets are unordered; normalize_value sorts elements for determinism.
|
|
assert normalize_value({3, 1, 2}) == normalize_value({1, 2, 3})
|
|
|
|
|
|
def test_normalize_value_path_normalization():
|
|
from pathlib import Path # local import to avoid global dependency
|
|
|
|
# Paths expand/resolve to absolute strings.
|
|
# Stabilizes hashing across working dirs.
|
|
assert normalize_value(Path(".")) == expected_path(".")
|
|
|
|
|
|
def test_normalize_value_uuid_and_to_json():
|
|
# Objects may normalize via uuid() or to_json_string().
|
|
class HasUUID:
|
|
def uuid(self):
|
|
return "test-uuid"
|
|
|
|
class ToJson:
|
|
def to_json_string(self):
|
|
return '{"x":1}'
|
|
|
|
assert normalize_value(HasUUID()) == "test-uuid"
|
|
assert normalize_value(ToJson()) == '{"x":1}'
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"bad",
|
|
[
|
|
(lambda x: x),
|
|
(type("CallableInstance", (), {"__call__": lambda self: 0}))(),
|
|
(lambda: (lambda: 0))(), # nested function instance
|
|
],
|
|
)
|
|
def test_error_cases(bad):
|
|
"""Inputs expected to raise TypeError."""
|
|
# Reject functions/lambdas/callable instances
|
|
# to avoid under-hashing.
|
|
with pytest.raises(TypeError):
|
|
normalize_value(bad)
|
|
|
|
|
|
def test_enum_vs_int_disambiguation():
|
|
# int stays primitive
|
|
nf_int = normalize_value(1)
|
|
assert nf_int == 1
|
|
|
|
# enum becomes ("module.QualName", value)
|
|
nf_enum = normalize_value(DummyLogprobsMode.RAW_LOGITS)
|
|
assert isinstance(nf_enum, tuple) and len(nf_enum) == 2
|
|
enum_type, enum_val = nf_enum
|
|
assert enum_type.endswith(".DummyLogprobsMode")
|
|
assert enum_val == "raw_logits"
|
|
|
|
# Build factor dicts from configs with int vs enum
|
|
f_int = get_hash_factors(SimpleConfig(1), set())
|
|
f_enum = get_hash_factors(SimpleConfig(DummyLogprobsMode.RAW_LOGITS), set())
|
|
# The int case remains a primitive value
|
|
assert f_int["a"] == 1
|
|
# The enum case becomes a tagged tuple ("module.QualName", "raw_logits")
|
|
assert isinstance(f_enum["a"], tuple) and f_enum["a"][1] == "raw_logits"
|
|
# Factor dicts must differ so we don't collide primitives with Enums.
|
|
assert f_int != f_enum
|
|
# Hash digests must differ correspondingly
|
|
assert hash_factors(f_int) != hash_factors(f_enum)
|
|
|
|
# Hash functions produce stable hex strings
|
|
h_int = hash_factors(f_int)
|
|
h_enum = hash_factors(f_enum)
|
|
assert isinstance(h_int, str) and len(h_int) == 64
|
|
assert isinstance(h_enum, str) and len(h_enum) == 64
|
|
|
|
|
|
def test_classes_are_types():
|
|
"""Types normalize to FQNs; include real vLLM types."""
|
|
# Only classes allowed; functions/lambdas are rejected.
|
|
# Canonical form is the fully-qualified name.
|
|
assert isinstance(normalize_value(str), str)
|
|
|
|
class LocalDummy:
|
|
pass
|
|
|
|
assert endswith_fqname(LocalDummy, ".LocalDummy")
|