diff --git a/docs/features/custom_pipeline.md b/docs/features/custom_pipeline.md index 78d680f26..887dfa769 100644 --- a/docs/features/custom_pipeline.md +++ b/docs/features/custom_pipeline.md @@ -101,7 +101,7 @@ outputs = omni.generate( ) # Access custom trajectory data -output = outputs[0].request_output +output = outputs[0] print(f"Trajectory timesteps shape: {output.metrics['trajectory_timesteps'].shape}") print(f"Trajectory latents shape: {output.latents.shape}") ``` diff --git a/docs/getting_started/quickstart.md b/docs/getting_started/quickstart.md index f9673eac7..45ff311cb 100644 --- a/docs/getting_started/quickstart.md +++ b/docs/getting_started/quickstart.md @@ -47,7 +47,7 @@ if __name__ == "__main__": omni = Omni(model="Tongyi-MAI/Z-Image-Turbo") prompt = "a cup of coffee on the table" outputs = omni.generate(prompt) - images = outputs[0].request_output.images + images = outputs[0].images images[0].save("coffee.png") ``` @@ -75,8 +75,7 @@ if __name__ == "__main__": ] omni_outputs = omni.generate(prompts) for i_prompt, prompt_output in enumerate(omni_outputs): - this_request_output = prompt_output.request_output - this_images = this_request_output.images + this_images = prompt_output.images for i_image, image in enumerate(this_images): image.save(f"p{i_prompt}-img{i_image}.jpg") print("saved to", f"p{i_prompt}-img{i_image}.jpg") diff --git a/docs/user_guide/examples/offline_inference/glm_image.md b/docs/user_guide/examples/offline_inference/glm_image.md index 09d927ff7..267819cc5 100644 --- a/docs/user_guide/examples/offline_inference/glm_image.md +++ b/docs/user_guide/examples/offline_inference/glm_image.md @@ -43,7 +43,7 @@ if __name__ == "__main__": ), ], ) - outputs[0].request_output.images[0].save("output.png") + outputs[0].images[0].save("output.png") ``` ## Image-to-Image (Image Editing) @@ -72,7 +72,7 @@ if __name__ == "__main__": ), ], ) - outputs[0].request_output.images[0].save("output.png") + outputs[0].images[0].save("output.png") ``` ## Generation Parameters diff --git a/docs/user_guide/examples/offline_inference/text_to_image.md b/docs/user_guide/examples/offline_inference/text_to_image.md index f631cd962..24cab3a3c 100644 --- a/docs/user_guide/examples/offline_inference/text_to_image.md +++ b/docs/user_guide/examples/offline_inference/text_to_image.md @@ -55,7 +55,7 @@ if __name__ == "__main__": omni = Omni(model="Qwen/Qwen-Image") prompt = "a cup of coffee on the table" outputs = omni.generate(prompt) - images = outputs[0].request_output.images + images = outputs[0].images images[0].save("coffee.png") ``` @@ -179,7 +179,7 @@ if __name__ == "__main__": ] outputs = omni.generate(prompts) for i, output in enumerate(outputs): - output.request_output.images[0].save(f"{i}.jpg") + output.images[0].save(f"{i}.jpg") ``` !!! info @@ -207,7 +207,7 @@ if __name__ == "__main__": } ]) for i, output in enumerate(outputs): - output.request_output.images[0].save(f"{i}.jpg") + output.images[0].save(f"{i}.jpg") ``` You can also pass a negative prompt via the CLI argument `--negative-prompt`: diff --git a/examples/offline_inference/image_to_video/README.md b/examples/offline_inference/image_to_video/README.md index c8f7fe617..050448571 100644 --- a/examples/offline_inference/image_to_video/README.md +++ b/examples/offline_inference/image_to_video/README.md @@ -96,7 +96,7 @@ if __name__ == "__main__": ), ) from diffusers.utils import export_to_video - frames = outputs[0].request_output.images + frames = outputs[0].images export_to_video(frames, "quick_test_i2v.mp4", fps=16) ``` diff --git a/examples/offline_inference/minicpmo/end2end.py b/examples/offline_inference/minicpmo/end2end.py index 857f1f00f..8e536735d 100644 --- a/examples/offline_inference/minicpmo/end2end.py +++ b/examples/offline_inference/minicpmo/end2end.py @@ -335,7 +335,7 @@ def main(args): print(f"use_tts (tts_bos): {use_tts}") for stage_outputs in omni_generator: - output = stage_outputs.request_output + output = stage_outputs if stage_outputs.final_output_type == "text": request_id = output.request_id text_output = output.outputs[0].text diff --git a/examples/offline_inference/text_to_image/README.md b/examples/offline_inference/text_to_image/README.md index b71c998b3..cfe05255d 100644 --- a/examples/offline_inference/text_to_image/README.md +++ b/examples/offline_inference/text_to_image/README.md @@ -74,7 +74,7 @@ if __name__ == "__main__": omni = Omni(model="Qwen/Qwen-Image") prompt = "a cup of coffee on the table" outputs = omni.generate(prompt) - images = outputs[0].request_output.images + images = outputs[0].images images[0].save("coffee.png") ``` @@ -265,7 +265,7 @@ if __name__ == "__main__": ] outputs = omni.generate(prompts) for i, output in enumerate(outputs): - output.request_output.images[0].save(f"{i}.jpg") + output.images[0].save(f"{i}.jpg") ``` !!! info @@ -301,7 +301,7 @@ if __name__ == "__main__": } ]) for i, output in enumerate(outputs): - output.request_output.images[0].save(f"{i}.jpg") + output.images[0].save(f"{i}.jpg") ``` You can also pass a negative prompt via the CLI argument `--negative-prompt`: diff --git a/tests/diffusion/distributed/test_flux2_vae_patch_parallel.py b/tests/diffusion/distributed/test_flux2_vae_patch_parallel.py index e861bd61a..8de3a42f8 100644 --- a/tests/diffusion/distributed/test_flux2_vae_patch_parallel.py +++ b/tests/diffusion/distributed/test_flux2_vae_patch_parallel.py @@ -53,14 +53,10 @@ def _diff_metrics(a: Image.Image, b: Image.Image) -> tuple[float, float]: def _extract_single_image(outputs) -> Image.Image: first_output = outputs[0] assert first_output.final_output_type == "image" - if not hasattr(first_output, "request_output") or not first_output.request_output: - raise ValueError("No request_output found in OmniRequestOutput") + if not isinstance(first_output, OmniRequestOutput): + raise ValueError(f"Expected an OmniRequestOutput, got {type(first_output).__name__}") - req_out = first_output.request_output - if not isinstance(req_out, OmniRequestOutput) or not hasattr(req_out, "images"): - raise ValueError("Invalid request_output structure or missing 'images' key") - - images = req_out.images + images = first_output.images if images is None or len(images) != 1: raise ValueError(f"Expected 1 image, got {0 if images is None else len(images)}") return images[0] diff --git a/tests/diffusion/distributed/test_vae_decode_parallelism.py b/tests/diffusion/distributed/test_vae_decode_parallelism.py index c3996fc23..28ded4639 100644 --- a/tests/diffusion/distributed/test_vae_decode_parallelism.py +++ b/tests/diffusion/distributed/test_vae_decode_parallelism.py @@ -72,8 +72,7 @@ def _to_float_array(data: Any) -> np.ndarray: def _extract_output_array(output: Any) -> np.ndarray: - req_out = output.request_output - payload = req_out.images[0] + payload = output.images[0] return _to_float_array(payload) diff --git a/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py b/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py index 80d99895f..1ba382703 100644 --- a/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py +++ b/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py @@ -142,12 +142,6 @@ def _generate_joint_output(omni, config: QualityTestConfig): first = outputs[0] worker_peak_memory_mb = float(getattr(first, "peak_memory_mb", 0.0) or 0.0) - if hasattr(first, "request_output") and isinstance(first.request_output, list): - first = first.request_output[0] - worker_peak_memory_mb = max( - worker_peak_memory_mb, - float(getattr(first, "peak_memory_mb", 0.0) or 0.0), - ) if not hasattr(first, "images") or not first.images: raise ValueError("Could not extract video frames from H3 output") frames = first.images[0] diff --git a/tests/docs/test_example_output_accessors.py b/tests/docs/test_example_output_accessors.py new file mode 100644 index 000000000..e7a618890 --- /dev/null +++ b/tests/docs/test_example_output_accessors.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Guard the public accessor that examples and docs use to read omni outputs. + +``OmniRequestOutput`` used to hold a nested ``RequestOutput`` in a +``request_output`` field. Since #5146 it *inherits* ``RequestOutput``, so the +generation content lives on the object itself and the nested field is gone. + +Call sites that kept the old accessor only fail once a real model runs, which +means a GPU nightly job is the first thing to notice. These checks pin both +halves of the contract on CPU instead: the field is really gone, and no doc, +example, test or tool still reaches for it. +""" + +import re +from pathlib import Path + +import pytest + +from vllm_omni.outputs import OmniRequestOutput + +pytestmark = [pytest.mark.core_model, pytest.mark.cpu] + + +ROOT_DIR = Path(__file__).parents[2] +SCANNED_DIRS = ("docs", "examples", "tests", "tools") +SCANNED_SUFFIXES = (".py", ".md") + +# ``.request_outputs`` (plural, an OutputProcessor batch) is a different and +# still-valid attribute, so require a non-identifier character after the name. +NESTED_ACCESSOR = re.compile(r"\.request_output(?![\w])") + + +def _iter_scanned_files(): + for directory in SCANNED_DIRS: + for path in sorted((ROOT_DIR / directory).rglob("*")): + if path.suffix in SCANNED_SUFFIXES and path.resolve() != Path(__file__).resolve(): + yield path + + +def test_omni_request_output_has_no_nested_request_output(): + assert not hasattr(OmniRequestOutput(request_id="req-accessor"), "request_output") + + +def test_no_source_reads_the_nested_request_output(): + offenders = [] + for path in _iter_scanned_files(): + for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): + if NESTED_ACCESSOR.search(line): + offenders.append(f"{path.relative_to(ROOT_DIR)}:{lineno}: {line.strip()}") + + assert not offenders, ( + "OmniRequestOutput no longer nests a RequestOutput; read the content off the " + "output itself (e.g. `outputs[0].images`) instead of `.request_output`:\n" + "\n".join(offenders) + ) diff --git a/tools/baselines/omni_hash_baseline.py b/tools/baselines/omni_hash_baseline.py index 8d8f5ff13..a1644aeb3 100644 --- a/tools/baselines/omni_hash_baseline.py +++ b/tools/baselines/omni_hash_baseline.py @@ -159,11 +159,10 @@ def _run_once(model: str, yaml_path: str, *, async_mode: bool) -> list[dict]: wav: bytes = b"" # Batch size 1: submit a single prompt so scheduling is deterministic. for step in omni.generate([_text_prompt(question)], _sampling_params(), py_generator=False): - out = step.request_output if step.final_output_type == "text": - text = out.outputs[0].text # exact text, no strip -> truly bit-identical + text = step.outputs[0].text # exact text, no strip -> truly bit-identical elif step.final_output_type == "audio": - wav = _audio_bytes(out.outputs[0].multimodal_output["audio"]) + wav = _audio_bytes(step.outputs[0].multimodal_output["audio"]) if text is None: raise RuntimeError(f"prompt_id={prompt_id} async={async_mode}: produced no text output") if not wav: