[misc] Improve benchmark determinism and dataset API coverage (#33255)
This commit is contained in:
@@ -67,11 +67,13 @@ def compute_random_lens(full_len: int, range_ratio: float, num: int) -> List[int
|
||||
@lru_cache(maxsize=1)
|
||||
def get_available_tokens(tokenizer):
|
||||
"""Get valid token ids from the tokenizer vocabulary."""
|
||||
return [
|
||||
# Canonical order: vocab dict iteration order varies across tokenizers
|
||||
# versions, which would break --seed reproducibility.
|
||||
return sorted(
|
||||
token_id
|
||||
for token_id in tokenizer.get_vocab().values()
|
||||
if isinstance(token_id, int)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def gen_prompt(tokenizer, token_num):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import io
|
||||
import random
|
||||
import warnings
|
||||
from argparse import Namespace
|
||||
from dataclasses import dataclass
|
||||
@@ -30,6 +31,7 @@ class ImageDataset(BaseDataset):
|
||||
image_resolution: str
|
||||
backend: str
|
||||
random_image_count: bool
|
||||
seed: int
|
||||
|
||||
@classmethod
|
||||
def from_args(cls, args: Namespace) -> "ImageDataset":
|
||||
@@ -44,10 +46,15 @@ class ImageDataset(BaseDataset):
|
||||
image_resolution=args.image_resolution,
|
||||
backend=args.backend,
|
||||
random_image_count=args.random_image_count,
|
||||
seed=args.seed,
|
||||
)
|
||||
|
||||
def load(self, tokenizer=None, model_id=None) -> List[DatasetRow]:
|
||||
processor = get_processor(model_id)
|
||||
# Processor initialization may consume global RNG state. Reset it here so
|
||||
# --seed fixes the generated prompts, image sizes, and image contents.
|
||||
random.seed(self.seed)
|
||||
np.random.seed(self.seed)
|
||||
return sample_image_requests(
|
||||
num_requests=self.num_requests,
|
||||
image_count=self.image_count,
|
||||
@@ -148,7 +155,7 @@ def create_mm_data_row(
|
||||
prompt_str = f"<image>{text_prompt}"
|
||||
|
||||
# Calculate total tokens (text + vision)
|
||||
if type(processor).__name__ == "KimiK25Processor":
|
||||
if type(processor).__name__ in ("KimiK25Processor", "KimiK3Processor"):
|
||||
medias = [{"type": "image", "image": img} for img in images]
|
||||
prompt_len = processor(
|
||||
text=prompt_str,
|
||||
|
||||
@@ -1002,10 +1002,21 @@ def run_benchmark_internal(
|
||||
"token_capacity", 1000000000
|
||||
)
|
||||
|
||||
assert (
|
||||
max_running_requests_per_dp > 0
|
||||
), f"effective_max_running_requests_per_dp is not set, {max_running_requests_per_dp=}"
|
||||
skip_max_running_requests_threshold = max_running_requests_per_dp * dp_size
|
||||
# Router /get_server_info responses carry "router_manager"; worker
|
||||
# responses never do, so its presence confirms a router by design.
|
||||
if not internal_states and server_info.get("router_manager"):
|
||||
print(
|
||||
"WARNING: base_url points at a PD router; worker internal "
|
||||
"states are unavailable, so the max-running-requests and "
|
||||
"token-capacity skip guards are disabled."
|
||||
)
|
||||
skip_max_running_requests_threshold = float("inf")
|
||||
skip_token_capacity_threshold = float("inf")
|
||||
else:
|
||||
assert (
|
||||
max_running_requests_per_dp > 0
|
||||
), f"effective_max_running_requests_per_dp is not set, {max_running_requests_per_dp=}"
|
||||
skip_max_running_requests_threshold = max_running_requests_per_dp * dp_size
|
||||
|
||||
print(f"{max_running_requests_per_dp=}")
|
||||
print(f"{dp_size=}")
|
||||
|
||||
@@ -215,14 +215,78 @@ class ImageOpenAITestMixin(TestOpenAIMLLMServerBase):
|
||||
with ThreadPoolExecutor(4) as executor:
|
||||
list(executor.map(self.run_decode_with_image, image_ids))
|
||||
|
||||
def test_image_prefix_cache_reuse(self):
|
||||
"""Image prefix (radix) cache correctness across requests.
|
||||
|
||||
Repeating an identical image must reuse the multimodal prefix without
|
||||
changing the output, and a different image must NOT reuse the first
|
||||
image's KV. This guards against image-token pad_value / feature-hash
|
||||
regressions that would silently serve a cached *wrong* image's KV
|
||||
(a correctness bug invisible to single-request tests). Pure greedy
|
||||
request-level checks: no extra server flags, radix cache is on by
|
||||
default.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
def describe(url: str) -> str:
|
||||
response = client.chat.completions.create(
|
||||
model="default",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "image_url", "image_url": {"url": url}},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Describe this image in one sentence.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
temperature=0,
|
||||
max_tokens=32,
|
||||
**(self.get_vision_request_kwargs()),
|
||||
)
|
||||
assert response.usage.prompt_tokens > 0
|
||||
content = response.choices[0].message.content
|
||||
assert isinstance(content, str) and content
|
||||
return content
|
||||
|
||||
# miss -> compute, then hit -> reuse the identical image's prefix
|
||||
first = describe(IMAGE_MAN_IRONING_URL)
|
||||
repeat = describe(IMAGE_MAN_IRONING_URL)
|
||||
# a different image must be computed on its own, not reuse `first`'s KV
|
||||
other = describe(IMAGE_SGL_LOGO_URL)
|
||||
# the original image again, after a different one occupied the cache
|
||||
first_again = describe(IMAGE_MAN_IRONING_URL)
|
||||
|
||||
self.assertEqual(
|
||||
first,
|
||||
repeat,
|
||||
"Repeating an identical image changed the output; image prefix "
|
||||
"reuse broke greedy determinism.",
|
||||
)
|
||||
self.assertEqual(
|
||||
first,
|
||||
first_again,
|
||||
"The identical image after a different one changed the output; "
|
||||
"image KV was cross-contaminated across requests.",
|
||||
)
|
||||
self.assertNotEqual(
|
||||
first,
|
||||
other,
|
||||
"A different image produced an identical description; a wrong "
|
||||
"image's KV may have been reused from the prefix cache.",
|
||||
)
|
||||
|
||||
def verify_single_image_response(self, response):
|
||||
assert response.choices[0].message.role == "assistant"
|
||||
text = response.choices[0].message.content
|
||||
assert isinstance(text, str)
|
||||
|
||||
# `driver` is for gemma-3-it
|
||||
assert (
|
||||
"man" in text or "person" or "driver" in text
|
||||
assert any(
|
||||
keyword in text for keyword in ("man", "person", "driver")
|
||||
), f"text: {text}, should contain man, person or driver"
|
||||
assert (
|
||||
"cab" in text
|
||||
|
||||
Reference in New Issue
Block a user