[Bugfix][Frontend] Check EC requirements for each metadata item (#56070)
Signed-off-by: 子华 <huaxi.shx@alibaba-inc.com> Co-authored-by: Codex <noreply@openai.com>
shaohuaxi committed
Sep 10, 2026 at 12:23 UTC
ae71862c51f2e069998e0dd5dc30a03a3b475ab9
2 files changed
+56
-6
tests/entrypoints/scale_out/token_in_token_out/test_mm_serde.py
+45
@@ -5,6 +5,7 @@ Roundtrip tests for multimodal serde used by the
5
token_in_token_out generate endpoint.
6
"""
7
8
+import pytest
9
import torch
10
from pydantic import ValidationError
11
@@ -241,6 +242,50 @@ def test_metadata_only_generate_requires_ec_transfer_params():
242
assert set(item) == {"image_grid_thw"}
243
244
245
+@pytest.mark.parametrize("modality", ["image", "video"])
246
+@pytest.mark.parametrize(
247
+ ("has_metadata", "has_ec"),
248
+ [(True, False), (True, True), (False, False)],
249
+ ids=["metadata-without-ec", "metadata-with-ec", "cache-hit-without-ec"],
250
+)
251
+def test_mixed_items_require_ec_only_for_metadata_only(
252
+ modality: str, has_metadata: bool, has_ec: bool
253
+):
254
+ """A full item must not bypass the EC requirement for another item."""
255
+ engine_input, _, image_grid_thw = _image_engine_input()
256
+ rendered = extract_mm_features(engine_input)
257
+ assert rendered is not None
258
+ features = rendered.model_dump()
259
+ metadata = (
260
+ encode_mm_kwargs_item(
261
+ MultiModalKwargsItem({f"{modality}_grid_thw": image_grid_thw})
262
+ )
263
+ if has_metadata
264
+ else None
265
+ )
266
+ second_item = {
267
+ "mm_hashes": "second-item",
268
+ "mm_placeholders": {"offset": 2, "length": 1},
269
+ "kwargs_data": None,
270
+ "mm_metadata": metadata,
271
+ }
272
+ for key, value in second_item.items():
273
+ features[key].setdefault(modality, []).append(value)
274
+ payload = {
275
+ "token_ids": [1, 2, 3],
276
+ "sampling_params": {"max_tokens": 1},
277
+ "features": features,
278
+ }
279
+ if has_ec:
280
+ payload["ec_transfer_params"] = {"second-item": {"peer_host": "10.0.0.1"}}
281
+
282
+ if has_metadata and not has_ec:
283
+ with pytest.raises(ValidationError, match="ec_transfer_params"):
284
+ GenerateRequest.model_validate(payload)
285
+ else:
286
+ GenerateRequest.model_validate(payload)
287
+
288
+
289
def test_kwargs_and_metadata_generate_does_not_require_ec():
290
engine_input, pixel_values, image_grid_thw = _image_engine_input()
291
rendered = extract_mm_features(engine_input)
vllm/entrypoints/scale_out/token_in_token_out/protocol.py
+11
-6
@@ -192,14 +192,19 @@ class GenerateRequest(BaseModel):
192
return self
193
if not _has_serialized_mm_items(features.mm_metadata):
194
return self
195
- if _has_serialized_mm_items(features.kwargs_data):
196
- return self
195
if self.ec_transfer_params:
196
return self
199
- raise ValueError(
200
- "features.mm_metadata without features.kwargs_data requires "
201
- "ec_transfer_params so embeddings can be loaded by the EC connector"
202
- )
197
+ kwargs_data = features.kwargs_data or {}
198
+ for modality, metadata_items in (features.mm_metadata or {}).items():
199
+ kwargs_items = kwargs_data.get(modality, [None] * len(metadata_items))
200
+ if any(
201
+ metadata is not None and kwargs is None
202
+ for metadata, kwargs in zip(metadata_items, kwargs_items, strict=True)
203
+ ):
204
+ raise ValueError(
205
+ "metadata-only multimodal items require ec_transfer_params"
206
+ )
207
+ return self
208
209
sampling_params: SamplingParams
210
"""The sampling parameters for the model."""