# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project """Startup / loading benchmark for multi-stage omni models (MammothModa2 first). Measures, from a single fresh process: * python import time (torch + vllm_omni) * Omni() construction wall time (all stages: weight load, profiling, KV cache, warmup) * first request latency (includes JIT / lazy init that only happens on first run) * steady-state request latency (repeat N-1 more requests) * optional host RSS / device memory samples (--sample-memory, once a second) Per-stage breakdown (weight loading, model-load memory, engine init) is emitted by the stage engine-core subprocesses into the log; use parse_startup_log.py on the captured stdout/stderr to merge them with the JSON written here. Usage (keep the log: the per-stage breakdown is parsed from it afterwards): python benchmarks/mammoth_moda2/bench_startup.py --model /path/MammothModa2-Preview \ --deploy-config vllm_omni/deploy/mammoth_moda2.yaml \ --height 1024 --width 1024 --seed 42 \ --extra-body '{"text_guidance_scale": 4.0, "cfg_range": [0.0, 1.0], "num_inference_steps": 50}' \ --repeat 2 --label nfs-warm --output-json out.json 2>&1 | tee run.log python benchmarks/mammoth_moda2/parse_startup_log.py run.log --markdown """ import argparse import json import os import platform import subprocess import threading import time T_PROCESS_START = time.perf_counter() import torch # noqa: E402 from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args # noqa: E402 from vllm_omni.entrypoints.omni import Omni # noqa: E402 from vllm_omni.entrypoints.openai.stage_params import clone_sampling_params # noqa: E402 from vllm_omni.inputs.data import OmniDiffusionSamplingParams # noqa: E402 from vllm_omni.model_extras import ( # noqa: E402 build_text_to_image_prompt as build_model_text_to_image_prompt, ) from vllm_omni.model_extras import ( # noqa: E402 get_extra_body_params, get_model_class_name, should_init_extra_args_for_non_diffusion_stages, ) from vllm_omni.platforms import current_omni_platform # noqa: E402 T_IMPORTS_DONE = time.perf_counter() def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument("--model", required=True) p.add_argument("--deploy-config", default=None) p.add_argument("--prompt", default="A stylish woman riding a motorcycle in NYC, movie poster style") p.add_argument("--negative-prompt", default=None) p.add_argument("--height", type=int, default=1024) p.add_argument("--width", type=int, default=1024) p.add_argument("--seed", type=int, default=42) p.add_argument("--extra-body", type=json.loads, default=None, help="JSON object forwarded as model extra_body") p.add_argument( "--repeat", type=int, default=2, help="number of generate() calls; 1st = first-request, rest = steady" ) p.add_argument("--label", default="run", help="free-form label, e.g. nfs-cold / nfs-warm / local-nvme") p.add_argument("--output-json", default=None) p.add_argument("--save-image", default=None, help="optional path to save the first request's image") p.add_argument("--sample-memory", action="store_true", help="sample memory once a second (adds overhead)") p.add_argument( "--parallel-stage-init", action="store_true", help="initialize stages that share a GPU concurrently (VllmOmniOrchestratorConfig.parallel_stage_init)", ) args = p.parse_args() if args.repeat < 1: p.error("--repeat must be at least 1") return args def gpu_info() -> dict: info = {"torch": torch.__version__, "cuda": torch.version.cuda, "python": platform.python_version()} try: import vllm info["vllm"] = vllm.__version__ except Exception: # pragma: no cover pass try: import vllm_omni info["vllm_omni"] = getattr(vllm_omni, "__version__", "src") except Exception: # pragma: no cover pass try: out = subprocess.run( ["nvidia-smi", "--query-gpu=name,driver_version,memory.total", "--format=csv,noheader"], capture_output=True, text=True, timeout=10, ) info["gpu"] = out.stdout.strip() except Exception: info["gpu"] = "n/a" return info class PeakSampler: """Poll host RSS of this process tree and GPU memory in use once a second (benchmark-side only).""" def __init__(self, interval_s: float = 1.0): self.interval_s = interval_s self.host_rss_peak_gib = 0.0 self.gpu_used_peak_gib = 0.0 self._stop = threading.Event() self._thread = threading.Thread(target=self._run, daemon=True) def _sample(self) -> None: try: import psutil root = psutil.Process() rss = root.memory_info().rss + sum( c.memory_info().rss for c in root.children(recursive=True) if c.is_running() ) self.host_rss_peak_gib = max(self.host_rss_peak_gib, rss / 2**30) except Exception: pass try: out = subprocess.run( ["nvidia-smi", "--query-gpu=memory.used", "--format=csv,noheader,nounits"], capture_output=True, text=True, timeout=5, ) used = max(float(x) for x in out.stdout.split() if x.strip()) self.gpu_used_peak_gib = max(self.gpu_used_peak_gib, used / 1024) except Exception: pass def _run(self) -> None: while not self._stop.is_set(): self._sample() self._stop.wait(self.interval_s) def start(self) -> "PeakSampler": self._thread.start() return self def stop(self) -> dict: self._stop.set() self._thread.join(timeout=10) self._sample() return { "host_rss_peak_gib": round(self.host_rss_peak_gib, 2), "gpu_used_peak_gib": round(self.gpu_used_peak_gib, 2), } def fs_type(path: str) -> str: try: out = subprocess.run(["stat", "-f", "-c", "%T", path], capture_output=True, text=True, timeout=10) return out.stdout.strip() except Exception: return "unknown" def build_request(omni: Omni, args: argparse.Namespace, generator: torch.Generator): model_class_name = get_model_class_name(omni) declared = get_extra_body_params(model_class_name) prompt_dict = {"prompt": args.prompt} if args.negative_prompt: prompt_dict["negative_prompt"] = args.negative_prompt prompt_dict = build_model_text_to_image_prompt( model_class_name=model_class_name, prompt=prompt_dict, height=args.height, width=args.width ) diffusion_params = OmniDiffusionSamplingParams( height=args.height, width=args.width, seed=args.seed, generator=generator, ) user_extra = dict(args.extra_body or {}) if declared: apply_declared_extra_args(diffusion_params, declared, user_extra) else: diffusion_params.extra_args.update({k: v for k, v in user_extra.items() if v is not None}) init_non_diffusion = should_init_extra_args_for_non_diffusion_stages(model_class_name) sampling_params_list = [clone_sampling_params(p) for p in (omni.default_sampling_params_list or [])] if not sampling_params_list: sampling_params_list = [diffusion_params] replaced = False for idx, params in enumerate(sampling_params_list): if isinstance(params, OmniDiffusionSamplingParams): sampling_params_list[idx] = diffusion_params replaced = True elif init_non_diffusion and hasattr(params, "extra_args"): params.extra_args = {**(params.extra_args or {}), **(diffusion_params.extra_args or {})} if hasattr(params, "seed"): params.seed = args.seed # MammothModa2 AR stage: one visual token per grid cell + one EOL per row + 1 look-ahead. info = prompt_dict.get("additional_information", {}) if idx == 0 and info.get("omni_task") == ["t2i"]: ar_w = int(info.get("ar_width", [0])[0]) ar_h = int(info.get("ar_height", [0])[0]) if ar_w > 0 and ar_h > 0: params.max_tokens = ar_h * (ar_w + 1) + 1 if not replaced and len(sampling_params_list) == 1: sampling_params_list = [diffusion_params] return prompt_dict, sampling_params_list def stage_metrics(outputs) -> dict: """Pull vllm-omni's per-stage timing out of the request output when present.""" res = {} for out in outputs or []: metrics = getattr(out, "metrics", None) if isinstance(metrics, dict) and "stage_metrics" in metrics: for sid, m in metrics["stage_metrics"].items(): if not isinstance(m, dict): continue res[str(sid)] = { k: m.get(k) for k in ("stage_gen_time_ms", "num_tokens_in", "num_tokens_out", "vllm_ttft_ms", "vllm_tpot_ms") if k in m } return res def main() -> None: args = parse_args() result = { "label": args.label, "model": args.model, "prompt": args.prompt, "negative_prompt": args.negative_prompt, "sample_memory": args.sample_memory, "model_fs": fs_type(args.model), "deploy_config": args.deploy_config, "height": args.height, "width": args.width, "seed": args.seed, "extra_body": args.extra_body, "parallel_stage_init": args.parallel_stage_init, "omp_num_threads": os.environ.get("OMP_NUM_THREADS"), "env": gpu_info(), "phases_s": {"imports": round(T_IMPORTS_DONE - T_PROCESS_START, 3)}, "requests": [], } omni_kwargs = {"model": args.model, "mode": "text-to-image", "log_stats": True} if args.deploy_config: omni_kwargs["deploy_config"] = args.deploy_config if args.parallel_stage_init: omni_kwargs["parallel_stage_init"] = True sampler = PeakSampler().start() if args.sample_memory else None t0 = time.perf_counter() omni = None try: omni = Omni(**omni_kwargs) t1 = time.perf_counter() result["peak_mem_startup"] = sampler.stop() if sampler else {} sampler = None result["phases_s"]["engine_init"] = round(t1 - t0, 3) result["phases_s"]["process_to_engine_ready"] = round(t1 - T_PROCESS_START, 3) print( f"BENCH engine_init={t1 - t0:.2f}s process_to_engine_ready={t1 - T_PROCESS_START:.2f}s " f"peak_mem_startup={result['peak_mem_startup']}", flush=True, ) sampler = PeakSampler().start() if args.sample_memory else None generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(args.seed) outputs = None first_outputs = None first_image_elapsed = 0.0 for i in range(args.repeat): prompt_dict, sampling_params_list = build_request(omni, args, generator) tr0 = time.perf_counter() outputs = omni.generate(prompt_dict, sampling_params_list=sampling_params_list) tr1 = time.perf_counter() if first_outputs is None: first_outputs = outputs first_image_elapsed = tr1 - T_PROCESS_START rec = { "index": i, "kind": "first" if i == 0 else "steady", "latency_s": round(tr1 - tr0, 3), "stages": stage_metrics(outputs), } result["requests"].append(rec) print(f"BENCH request[{i}] {rec['kind']} latency={tr1 - tr0:.2f}s stages={rec['stages']}", flush=True) result["peak_mem_requests"] = sampler.stop() if sampler else {} sampler = None steady = [r["latency_s"] for r in result["requests"] if r["kind"] == "steady"] result["summary_s"] = { "imports": result["phases_s"]["imports"], "engine_init": result["phases_s"]["engine_init"], "first_request": result["requests"][0]["latency_s"], "steady_request_avg": round(sum(steady) / len(steady), 3) if steady else None, "time_to_first_image_from_process_start": round(first_image_elapsed, 3), } for key in ("host_rss_peak_gib", "gpu_used_peak_gib"): result["summary_s"][key] = ( max(result["peak_mem_startup"][key], result["peak_mem_requests"][key]) if args.sample_memory else None ) from vllm_omni.diffusion.utils.image_output import extract_images_from_outputs images = extract_images_from_outputs(first_outputs) if not images: raise RuntimeError("First request returned no image") if args.save_image: os.makedirs(os.path.dirname(os.path.abspath(args.save_image)), exist_ok=True) images[0].save(args.save_image) print("BENCH_JSON " + json.dumps(result), flush=True) if args.output_json: with open(args.output_json, "w") as f: json.dump(result, f, indent=2) print(f"BENCH wrote {args.output_json}", flush=True) finally: if sampler: sampler.stop() if omni is not None: omni.close() if __name__ == "__main__": main()