diff --git a/doc/architecture/SPEC/direct_routing.md b/doc/architecture/SPEC/direct_routing.md index 9207ea0a..355e0738 100644 --- a/doc/architecture/SPEC/direct_routing.md +++ b/doc/architecture/SPEC/direct_routing.md @@ -1,15 +1,14 @@ # direct_routing.py —— 规范 - + -`LOC ~917 · Direct 模式完整策略包与知识路由 · refactor-status: known-debt` +`LOC ~880 · Direct 模式完整策略包与仓库中立的知识路由 · refactor-status: stable` ## 职责 -Direct 模式的知识路由:owner/model 路由表和选路机制。从 -`thin_mcp_server.py` **逐字迁出**(下游曾经 `importlib` 拿它的私有名), -公开面由 `contract.py` 再导出。它显式携带仓库专属知识 —— 这正是 -`contract.py` 必须保持中立而这里不必的原因;把表外置到 -`adapters//` 是**未被这次搬迁改变的既有欠账**。 +Direct 模式的知识路由**机制**。模块中不出现任何被服务仓库的名字:每个仓库的 +owner/model 路由表是知识数据 `knowledge/repos//_routes.yaml`,仓库别名 +来自 `adapters//manifest.yaml` 的 `repo.full_name`/`repo.aliases`。 +公开面由 `contract.py` 再导出。 ## 公开契约 经 `contract.py` 再导出的五个名字:`direct_review_plan`(完整、一次性的 @@ -29,9 +28,15 @@ Direct policy bundle)、`direct_knowledge_routes`、 又不许看"不是)。 - adapter-backed changed-file 路由同样拆开 `(quick_map, status)`,绝不把 tuple 当成文本跨边界,也绝不把 unavailable 误报成无需读取。 -- knowledge/adapters 根由 `sdk._resources` 的 `importlib.resources` 解析,editable - source 与安装 wheel 走同一标记校验;仓库路径只留在本模块内部的旧版 dict, - `sdk.v1.DirectClient` 对外转换为 document ID。 +- **每请求一个 `KnowledgeView`**:入口函数在开始时解析一次视图(未设置 + `KNOWLEDGE_ROOT` 时为打包知识;设置时为激活快照的真实目录并按清单校验), + 本请求内所有读取经同一视图。模块不再在导入期缓存知识根;`_KNOWLEDGE` 只保留 + 为模块级 `__getattr__` 惰性别名。 +- **路由选择按数据而非仓库名**:仓库有非空 `_routes.yaml` → title/body owner + 路由 + 模型路由 + scope fallback;否则有 adapter → changed-file 路由;否则 + `unsupported_exact_router`。`_routes.yaml` 引用的页面不存在时 fail-closed。 +- `repo` 必填:空值抛 `ValueError`,不再默认某个仓库;形似路径的仓库名直接 + 视为不支持。 - **changed files 校验选择、绝不静默替换选择**:title/body 选 owner, diff 只报告支持或矛盾;scope-fallback 是最后手段且永远显式 (`status="scope_fallback"`)。 @@ -46,23 +51,26 @@ Direct policy bundle)、`direct_knowledge_routes`、 (`test_contract.py::test_direct_routing_does_not_import_a_server_module`)。 ## 边界 —— 不属于这里 -不执行、不调模型;只有路由表 + 机制。多 adapter 桥接经 +不执行、不调模型;只有机制,路由表在知识数据里。多 adapter 桥接经 `_normalize_repo`/`_adapter_for_repo` 走 `adapters/`。 ## 依赖(允许) -stdlib + `.adapters`(AdapterError / AdapterRegistry / RepoAdapter)+ -`.sdk._resources`。 +stdlib + PyYAML + `.adapters`(AdapterError / AdapterRegistry / RepoAdapter)+ +`.knowledge_view` + `.sdk._resources`。 位于 `contract.py` 和 `thin_mcp_server.py` 之下。 ## 扩展点 -新 owner 路由/模型规则 → 表数据;跨仓库通用化 → 外置进 -`adapters//routing`(既有欠账的正解,不是在这里再长表)。 +新 owner 路由/模型规则 → 改对应仓库的 `_routes.yaml`(schema_version 1: +`owners[{owner, path, signals, scope_prefixes}]`、可选 `models{dir, page}`); +新仓库 → 提供 adapter 与 `_routes.yaml`,不改 `src/`。 ## 测试 -`test_contract.py`(公开家、import 方向、中立性豁免); +`test_knowledge_view_routing.py`(150 个真实 vllm-omni PR 的黄金路由输出、 +第二仓库经自身 `_routes.yaml` 路由、快照切换下一请求生效、快照篡改 fail-closed、 +SDK 完成校验钉住计划时快照、模块源码不含仓库名);`test_contract.py`(公开家、import 方向、中立性豁免); `test_thin_mcp_server.py` / `test_thin_mcp.py`(经新家继续锻炼全部 下划线函数:路由、预算、完成门)。 ## 重构备注 -768 行里约 500 行是表数据。`known-debt` 指的就是表:机制是稳定的, -表的归宿在 adapter 数据面(见结构重组计划 Stage 8 的 routing.yaml 方案)。 +原 `_DIRECT_OWNER_ROUTES`/`_REPO_ALIASES` 已外置(`known-debt` 清零), +`test_v2_p0.py` 与 `test_repo_vocabulary.py` 中本模块的泄漏上限随之移除。 diff --git a/doc/architecture/SPEC/knowledge_docs.md b/doc/architecture/SPEC/knowledge_docs.md index 91c92427..509e5007 100644 --- a/doc/architecture/SPEC/knowledge_docs.md +++ b/doc/architecture/SPEC/knowledge_docs.md @@ -1,6 +1,6 @@ # knowledge_docs.py —— 规范 - + `LOC ~122 · 供 MCP 面使用的只读知识检索 · refactor-status: ok` @@ -30,3 +30,6 @@ ## 重构备注 **保持它不含模型**:这是 Direct MCP 路径与工具桥共用的**唯一**知识读取器, 在这里加一次模型调用,等于把第二个模型塞进了"server 不跑模型"这条保证里面。 + +## 2026-09-28 快照校验 +可选 `verify(rel)` 回调在 `read`/`search` 读取每个文件前调用;MCP 传入 `KnowledgeView.path`,快照中缺失或被改动的文件使读取/搜索 fail-closed。 diff --git a/doc/architecture/SPEC/knowledge_view.md b/doc/architecture/SPEC/knowledge_view.md new file mode 100644 index 00000000..fbff8563 --- /dev/null +++ b/doc/architecture/SPEC/knowledge_view.md @@ -0,0 +1,33 @@ +# knowledge_view.py —— 规范 + + + +`LOC ~170 · 每请求的知识树视图(打包知识或激活快照) · refactor-status: stable` + +## 职责 +为每个请求解析**一个**知识根,并让本请求内所有读取经过它。知识服务通过原子 +切换符号链接激活新快照;导入期常量会一直服务旧树(包括已退役规则)直到进程 +重启,因此视图必须在请求开始时解析。 + +## 公开契约 +- `KnowledgeView.current()`:`KNOWLEDGE_ROOT` 未设置 → 打包知识, + `snapshot="packaged"`;已设置 → 对其 `realpath` **只取一次**。含 + `MANIFEST.json` 的目录是激活快照,根为其 `knowledge/`;只含 `AGENTS.md` 的 + 目录作为开发用未校验树(`snapshot="unverified:"`);其余 fail-closed。 +- `view.path(rel)` / `view.read_text(rel)`:拒绝逃逸与缺失;快照模式下文件必须 + 在清单中且 sha256 一致,否则抛 `KnowledgeViewError`。 +- `view.relative(path)`:绝对路径 → 知识相对 id。 +- `build_manifest(root, snapshot)`:生成 schema_version 1 清单 + (`files` 逐文件 sha256、`tree_sha256`)。 + +## 不变量 +- 一个请求不混用两棵树:解析后使用真实目录,不再经过符号链接。 +- 快照首次加载时校验:磁盘文件集合与清单完全一致、`tree_sha256` 与文件列表 + 一致;每次读取再校验单文件 sha256。 +- 快照不可变,因此按解析后的目录缓存加载结果是安全的:新激活是另一个目录。 + +## 依赖(允许) +stdlib + `.sdk._resources`。叶子模块。 + +## 测试 +`test_knowledge_view_routing.py`。 diff --git a/doc/architecture/SPEC/sdk.md b/doc/architecture/SPEC/sdk.md index 36e6c0b1..15feb6c8 100644 --- a/doc/architecture/SPEC/sdk.md +++ b/doc/architecture/SPEC/sdk.md @@ -1,6 +1,6 @@ # sdk/ —— 规范 - + `Python SDK v1 · 跨仓库唯一 typed 边界 · refactor-status: ok` @@ -118,3 +118,6 @@ knowledge checkout,不改仓库真实 `knowledge/`。 ## Carried finding rechecks Direct 1.1 / Strict 1.3 accept typed `CarriedFinding` inputs (unique IDs, source head, severity, location, title and evidence). Direct binds the carried set into its issued context and validates `FindingRecheck` answers. Strict passes the set through the policy allowlist, performs a read-only recheck against the frozen PR head, and persists answers for the public structured result. Every carried ID needs an explicit outcome: `fixed`, `still_affected`, or `unverified`. Missing evidence is never a fix. + +## 2026-09-28 知识视图 +`DirectClient` 每次 `plan` 解析一个 `KnowledgeView`,文档引用与 `resource_revision` 均取自该视图;`validate` 按计划时钉住的知识根校验资源未变,激活新快照不会使进行中的 review 失效;`diagnostics.knowledge_snapshot` 报告所用快照。`read_document(..., review_context_id=...)` 从该 review 计划时钉住的知识树读取(含分页),未签发的 context id 被拒绝;不带 context id 时读当前激活树。 diff --git a/doc/architecture/SPEC/thin_mcp_server.md b/doc/architecture/SPEC/thin_mcp_server.md index d9ffac8c..11186d3d 100644 --- a/doc/architecture/SPEC/thin_mcp_server.md +++ b/doc/architecture/SPEC/thin_mcp_server.md @@ -1,6 +1,6 @@ # thin_mcp_server.py —— 规范 - + `LOC ~490 · 默认 MCP:Direct 门面 + Strict 入口 · refactor-status: ok` @@ -82,3 +82,6 @@ stdlib + `mcp` extra + `.direct_routing`(下划线别名 re-import)+ 迁到 `direct_routing`,避免 MCP 和 Python SDK 各拼一份协议。 拆分保住了"server 不跑模型"—— 它仍是 Direct 模式的产品承诺; 后续增长优先落到 `direct_routing`/adapter 数据面,不回到这里。 + +## 2026-09-28 知识视图 +知识根改为每次调用经 `KnowledgeView.current()` 解析;`_KNOWLEDGE` 仅为惰性别名。 diff --git a/knowledge/repos/vllm-omni/_routes.yaml b/knowledge/repos/vllm-omni/_routes.yaml new file mode 100644 index 00000000..7b8ee1fc --- /dev/null +++ b/knowledge/repos/vllm-omni/_routes.yaml @@ -0,0 +1,37 @@ +# Direct-mode owner routing for vllm-omni. Read by the repo-neutral router in +# src/infermatrix_copilot/direct_routing.py; nothing in src/ names this repo. +# +# owners: the PR's title/body is matched against `signals` (word-bounded, case- +# and separator-insensitive) to pick owner pages; `scope_prefixes` report which +# owners the changed files touch, and select them only as a last resort. +# models: every / under `dir` is a model route matched by name. +# Order of `owners` is the tie-break order and must stay stable. +schema_version: 1 +owners: + - owner: configuration + path: repos/vllm-omni/components/configuration/rules.md + signals: [config, configuration, yaml, registry, deploy, pipeline, override, default, cli flag, topology] + scope_prefixes: [vllm_omni/config/, vllm_omni/deploy/] + - owner: serving + path: repos/vllm-omni/components/serving/rules.md + signals: [serving, server, endpoint, openai, request, response, http, chat completion, completion endpoint, speech api, sse, websocket, sleep, wake, partial wake, engine lifecycle, idempotency, ack] + scope_prefixes: [vllm_omni/entrypoints/openai/, vllm_omni/entrypoints/api_server.py, vllm_omni/entrypoints/async_omni.py] + - owner: model-executor + path: repos/vllm-omni/components/model-executor/rules.md + signals: [model executor, loader, checkpoint, tokenizer, processor, stage input, stage handoff, runtime info, runtime_info, batch, sampling] + scope_prefixes: [vllm_omni/model_executor/, vllm_omni/inputs/] + - owner: diffusion + path: repos/vllm-omni/components/diffusion/rules.md + signals: [diffusion, image generation, text to image, image to image, lora, vae, dit, ulysses, cache dit] + scope_prefixes: [vllm_omni/diffusion/, vllm_omni/model_executor/models/diffusers/] + - owner: distributed + path: repos/vllm-omni/components/distributed/_index.md + signals: [distributed, tensor parallel, data parallel, replica, collective, rpc] + scope_prefixes: [vllm_omni/distributed/, vllm_omni/worker/, vllm_omni/entrypoints/async_omni.py] + - owner: scheduler + path: repos/vllm-omni/components/scheduler/rules.md + signals: [scheduler, scheduling, prefix cache, token budget, queue, side stream] + scope_prefixes: [vllm_omni/core/sched/] +models: + dir: repos/vllm-omni/models + page: rules.md diff --git a/src/infermatrix_copilot/direct_routing.py b/src/infermatrix_copilot/direct_routing.py index fa681249..b29166b3 100644 --- a/src/infermatrix_copilot/direct_routing.py +++ b/src/infermatrix_copilot/direct_routing.py @@ -1,13 +1,14 @@ -"""Direct-mode knowledge routing: the tables and the routing mechanism. +"""Direct-mode knowledge routing: the repo-neutral routing mechanism. Its four entry points — `direct_knowledge_routes`, `direct_execution_budget`, `direct_completion_result`, `direct_mandatory_review_guides` — are re-exported by -`contract.py`, which is the surface consumers import. They live here rather than -there because the routing tables below are repo-specific knowledge, and the -public contract module must stay repo-neutral (invariant 6). This module carries -that debt, inherited verbatim from `thin_mcp_server.py` where it used to sit; -extracting the tables into `adapters//` is the pending cleanup, unchanged -by this move. +`contract.py`, which is the surface consumers import. + +Nothing here names a served repository. Each repository's owner table lives in +its own knowledge slice, `knowledge/repos//_routes.yaml`; repository +aliases come from `adapters//manifest.yaml`. Every request resolves one +`KnowledgeView` up front and all reads inside it use that view, so an activated +knowledge snapshot takes effect on the next request without a restart. They were moved out of `thin_mcp_server.py` because a downstream consumer imported the four `_direct_*` privates from it through `importlib` — a coupling @@ -19,149 +20,113 @@ import re import time +from functools import lru_cache from pathlib import Path +import yaml + from .adapters import AdapterError, AdapterRegistry, RepoAdapter -from .sdk._resources import adapters_root, knowledge_root +from .knowledge_view import KnowledgeView +from .sdk._resources import adapters_root _ROOT = Path(__file__).resolve().parents[2] +_ADAPTERS = adapters_root() +ROUTES_FILE = "_routes.yaml" +_ROUTES_SCHEMA_VERSION = 1 -def _knowledge_root() -> Path: - return knowledge_root() +def _view(view: KnowledgeView | None = None) -> KnowledgeView: + return view if view is not None else KnowledgeView.current() -_KNOWLEDGE = _knowledge_root() +def __getattr__(name: str): + # `_KNOWLEDGE` used to be an import-time constant. Kept as a lazy alias for + # existing importers; it now reflects the CURRENT view on every access. + if name == "_KNOWLEDGE": + return KnowledgeView.current().root + raise AttributeError(name) -_ADAPTERS = adapters_root() -_REPO_ALIASES = { - "vllm-project/vllm-omni": "vllm-omni", - "vllm-project/afd-plugin": "afd-plugin", -} -_DIRECT_OWNER_ROUTES = ( - { - "owner": "configuration", - "path": "repos/vllm-omni/components/configuration/rules.md", - "signals": ( - "config", - "configuration", - "yaml", - "registry", - "deploy", - "pipeline", - "override", - "default", - "cli flag", - "topology", - ), - "scope_prefixes": ( - "vllm_omni/config/", - "vllm_omni/deploy/", - ), - }, - { - "owner": "serving", - "path": "repos/vllm-omni/components/serving/rules.md", - "signals": ( - "serving", - "server", - "endpoint", - "openai", - "request", - "response", - "http", - "chat completion", - "completion endpoint", - "speech api", - "sse", - "websocket", - "sleep", - "wake", - "partial wake", - "engine lifecycle", - "idempotency", - "ack", - ), - "scope_prefixes": ( - "vllm_omni/entrypoints/openai/", - "vllm_omni/entrypoints/api_server.py", - "vllm_omni/entrypoints/async_omni.py", - ), - }, - { - "owner": "model-executor", - "path": "repos/vllm-omni/components/model-executor/rules.md", - "signals": ( - "model executor", - "loader", - "checkpoint", - "tokenizer", - "processor", - "stage input", - "stage handoff", - "runtime info", - "runtime_info", - "batch", - "sampling", - ), - "scope_prefixes": ( - "vllm_omni/model_executor/", - "vllm_omni/inputs/", - ), - }, - { - "owner": "diffusion", - "path": "repos/vllm-omni/components/diffusion/rules.md", - "signals": ( - "diffusion", - "image generation", - "text to image", - "image to image", - "lora", - "vae", - "dit", - "ulysses", - "cache dit", - ), - "scope_prefixes": ( - "vllm_omni/diffusion/", - "vllm_omni/model_executor/models/diffusers/", - ), - }, - { - "owner": "distributed", - "path": "repos/vllm-omni/components/distributed/_index.md", - "signals": ( - "distributed", - "tensor parallel", - "data parallel", - "replica", - "collective", - "rpc", - ), - "scope_prefixes": ( - "vllm_omni/distributed/", - "vllm_omni/worker/", - "vllm_omni/entrypoints/async_omni.py", - ), - }, - { - "owner": "scheduler", - "path": "repos/vllm-omni/components/scheduler/rules.md", - "signals": ( - "scheduler", - "scheduling", - "prefix cache", - "token budget", - "queue", - "side stream", - ), - "scope_prefixes": ( - "vllm_omni/core/sched/", - ), - }, -) +@lru_cache(maxsize=1) +def _repo_aliases() -> dict[str, str]: + """casefolded full name / alias / adapter name -> knowledge repo name.""" + aliases: dict[str, str] = {} + try: + adapters = AdapterRegistry(_ADAPTERS).all() + except AdapterError: + return aliases + for adapter in adapters: + name = _adapter_repo_name(adapter) + repo = adapter.manifest.get("repo") or {} + for value in (adapter.name, repo.get("full_name"), *(repo.get("aliases") or ())): + if value: + aliases[str(value).strip().casefold()] = name + return aliases + + +def _adapter_repo_name(adapter: RepoAdapter) -> str: + repo_subdir = str( + (adapter.manifest.get("knowledge") or {}).get("repo_subdir") or "") + if repo_subdir.startswith("repos/"): + return repo_subdir.removeprefix("repos/").strip("/") + return adapter.name.replace("_", "-") + + +def load_routes(repo: str, view: KnowledgeView | None = None) -> dict | None: + """Return a repository's validated `_routes.yaml`, or None when it has none.""" + view = _view(view) + return _load_routes(view, _normalize_repo(repo)) + + +_REPO_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*") + + +@lru_cache(maxsize=64) +def _load_routes(view: KnowledgeView, repo: str) -> dict | None: + if not _REPO_NAME.fullmatch(repo): + return None + relative = f"repos/{repo}/{ROUTES_FILE}" + try: + text = view.read_text(relative) + except FileNotFoundError: + return None + data = yaml.safe_load(text) + return _validate_routes(data, relative, view) + + +def _validate_routes(data: object, relative: str, view: KnowledgeView) -> dict: + if not isinstance(data, dict) or data.get("schema_version") != _ROUTES_SCHEMA_VERSION: + raise ValueError(f"{relative}: schema_version must be {_ROUTES_SCHEMA_VERSION}") + owners = data.get("owners") or [] + if not isinstance(owners, list): + raise ValueError(f"{relative}: owners must be a list") + seen: set[str] = set() + normalized = [] + for index, item in enumerate(owners): + if not isinstance(item, dict): + raise ValueError(f"{relative}: owners[{index}] must be a mapping") + owner = str(item.get("owner") or "").strip() + path = str(item.get("path") or "").strip() + signals = item.get("signals") or [] + prefixes = item.get("scope_prefixes") or [] + if not owner or owner in seen: + raise ValueError(f"{relative}: owners[{index}] needs a unique owner") + if not isinstance(signals, list) or not isinstance(prefixes, list): + raise ValueError(f"{relative}: owners[{index}] signals/scope_prefixes must be lists") + view.path(path) # fail closed on a route to a missing page + seen.add(owner) + normalized.append({ + "owner": owner, + "path": path, + "signals": tuple(str(value) for value in signals), + "scope_prefixes": tuple(str(value) for value in prefixes), + }) + models = data.get("models") or None + if models is not None: + if not isinstance(models, dict) or not models.get("dir") or not models.get("page"): + raise ValueError(f"{relative}: models needs dir and page") + models = {"dir": str(models["dir"]).strip("/"), "page": str(models["page"])} + return {"owners": tuple(normalized), "models": models} _DIRECT_MANDATORY_REVIEW_GUIDES = ( @@ -229,17 +194,12 @@ def _knowledge_root() -> Path: def _normalize_repo(repo: str) -> str: - selected = str(repo or "vllm-omni").strip() - alias = _REPO_ALIASES.get(selected.casefold()) + selected = str(repo or "").strip() + if not selected: + raise ValueError("repo is required") + alias = _repo_aliases().get(selected.casefold()) if alias: return alias - adapter = _adapter_for_repo(selected) - if adapter is not None: - repo_subdir = str( - (adapter.manifest.get("knowledge") or {}).get("repo_subdir") or "") - if repo_subdir.startswith("repos/"): - return repo_subdir.removeprefix("repos/").strip("/") - return adapter.name.replace("_", "-") return selected.replace("_", "-") @@ -253,7 +213,9 @@ def _adapter_for_repo(repo: str) -> RepoAdapter | None: def _adapter_changed_file_routes( repo: str, changed_files: list[str], + view: KnowledgeView | None = None, ) -> tuple[list[dict[str, object]], list[str]]: + view = _view(view) selected_repo = _normalize_repo(repo) adapter = _adapter_for_repo(selected_repo) if adapter is None: @@ -277,7 +239,7 @@ def _adapter_changed_file_routes( continue for hit in hits: doc = str(hit["doc"]) - doc_path = _knowledge_path(doc) + doc_path = _knowledge_path(doc, view) quick_map, quick_map_status = _direct_quick_map(doc_path) routed.append({ "owner": str(hit["owner"]), @@ -293,23 +255,15 @@ def _adapter_changed_file_routes( return routed, unmatched -def _knowledge_path(relative_path: str) -> str: - path = (_KNOWLEDGE / relative_path).resolve() - try: - path.relative_to(_KNOWLEDGE.resolve()) - except ValueError as exc: - raise ValueError( - f"knowledge route escapes the knowledge root: {relative_path}" - ) from exc - if not path.is_file(): - raise FileNotFoundError(f"knowledge route is missing: {path}") - return str(path) +def _knowledge_path(relative_path: str, view: KnowledgeView | None = None) -> str: + return str(_view(view).path(relative_path)) -def _direct_mandatory_review_guides() -> list[str]: +def _direct_mandatory_review_guides(view: KnowledgeView | None = None) -> list[str]: """Return required cross-owner review procedures, failing closed if absent.""" + view = _view(view) return [ - _knowledge_path(relative_path) + _knowledge_path(relative_path, view) for relative_path in _DIRECT_MANDATORY_REVIEW_GUIDES ] @@ -440,6 +394,7 @@ def _direct_knowledge_routes( title: str = "", body: str = "", changed_files: list[str] | None = None, + view: KnowledgeView | None = None, ) -> dict: """Select bounded Direct knowledge routes from PR intent. @@ -456,6 +411,7 @@ def _direct_knowledge_routes( its reason. Handing the host nothing while holding the answer was the worse option: the owners were already computed and then discarded. """ + view = _view(view) selected_repo = _normalize_repo(repo) changed_files = changed_files or [] if not isinstance(changed_files, list) or any( @@ -467,7 +423,8 @@ def _direct_knowledge_routes( # description-less PR on an unsupported repo fell through to the generic path — # and once that path started deriving routes from changed files, it would have # served this repo's owner knowledge for a repo we do not serve. - if selected_repo != "vllm-omni": + routes_table = _load_routes(view, selected_repo) + if routes_table is None or not routes_table["owners"]: # Adapter presence decides which non-default-repo case this is. Without the # gate, a repo we do not serve at all fell into the adapter path, whose # helper returns every changed file as "unmatched" when there is no @@ -483,7 +440,7 @@ def _direct_knowledge_routes( "scope_validation": [], } routes, unmatched = _adapter_changed_file_routes( - selected_repo, changed_files) + selected_repo, changed_files, view) if routes or unmatched: return { "status": "ready" if routes else "description_unrouted", @@ -508,25 +465,30 @@ def _direct_knowledge_routes( "scope_validation": [], } + owner_table = routes_table["owners"] intent = _route_text(f"{title}\n{body}") owner_routes: list[dict[str, object]] = [] if intent: - for route in _DIRECT_OWNER_ROUTES: + for route in owner_table: matched = [ signal for signal in route["signals"] if _signal_matches(intent, signal) ] if matched: owner_routes.append(_direct_route( - str(route["owner"]), _knowledge_path(str(route["path"])), + str(route["owner"]), _knowledge_path(str(route["path"]), view), f"title/body: {', '.join(matched[:3])}")) model_routes: list[dict[str, object]] = [] - model_root = _KNOWLEDGE / "repos" / "vllm-omni" / "models" - if intent: - for model_dir in sorted(model_root.iterdir(), key=lambda path: -len(path.name)): - rules = model_dir / "rules.md" + models = routes_table["models"] + if intent and models is not None: + model_root = view.root / models["dir"] + # longest name first; ties by name. Length alone left equal-length + # models in filesystem order, so two machines routed the same PR + # differently once the three-route cap cut between them. + for model_dir in sorted(model_root.iterdir(), key=lambda path: (-len(path.name), path.name)): + rules = model_dir / models["page"] if not rules.is_file(): continue model_name = _route_text(model_dir.name) @@ -536,14 +498,15 @@ def _direct_knowledge_routes( compact_match = len(compact_name) >= 8 and compact_name in compact_intent if exact_match or compact_match: model_routes.append(_direct_route( - f"model:{model_dir.name}", str(rules.resolve()), + f"model:{model_dir.name}", + _knowledge_path(view.relative(rules), view), f"title/body model: {model_dir.name}")) routes = (model_routes + owner_routes)[:3] scope_hits: dict[str, list[str]] = {} scope_validation = [] - for route in _DIRECT_OWNER_ROUTES: + for route in owner_table: hits = sorted({ path for path in changed_files if any( @@ -578,9 +541,9 @@ def _direct_knowledge_routes( surviving = {str(item["owner"]) for item in routes} fallback_used = False if scope_hits and not (surviving & set(scope_hits)): - by_owner = {str(r["owner"]): r for r in _DIRECT_OWNER_ROUTES} + by_owner = {str(r["owner"]): r for r in owner_table} # most changed files first, then owner name — the choice reflects evidence - # rather than the declaration order of _DIRECT_OWNER_ROUTES + # rather than the declaration order of the owner table candidates = sorted(scope_hits, key=lambda o: (-len(scope_hits[o]), o)) for owner in candidates: if len(routes) >= 3: @@ -589,7 +552,7 @@ def _direct_knowledge_routes( routes.pop() # displace the weakest description route, cap stays 3 spec = by_owner[owner] routes.append(_direct_route( - owner, _knowledge_path(str(spec["path"])), + owner, _knowledge_path(str(spec["path"]), view), f"changed files: {', '.join(scope_hits[owner][:3])}")) fallback_used = True @@ -622,6 +585,7 @@ def direct_review_plan( title: str = "", body: str = "", changed_files: list[str] | None = None, + view: KnowledgeView | None = None, ) -> dict: """Return the complete Direct policy bundle for one frozen review. @@ -630,6 +594,7 @@ def direct_review_plan( from reconstructing a smaller, divergent protocol out of routing helpers. """ started = time.perf_counter() + view = _view(view) changed_files = list(changed_files or []) route_started = time.perf_counter() routing = _direct_knowledge_routes( @@ -637,6 +602,7 @@ def direct_review_plan( title=title, body=body, changed_files=changed_files, + view=view, ) route_ms = int((time.perf_counter() - route_started) * 1000) knowledge_routes = list(routing.get("routes") or []) @@ -645,7 +611,7 @@ def direct_review_plan( for route in knowledge_routes if route.get("quick_map_status") != "ok" ] - mandatory_review_guides = _direct_mandatory_review_guides() + mandatory_review_guides = _direct_mandatory_review_guides(view) budget_started = time.perf_counter() execution_budget = _direct_execution_budget( changed_files, @@ -659,7 +625,7 @@ def direct_review_plan( "knowledge_entry": ( knowledge_routes[0]["path"] if knowledge_routes - else _knowledge_path("AGENTS.md") + else _knowledge_path("AGENTS.md", view) ), "knowledge_routes": knowledge_routes, "mandatory_review_guides": mandatory_review_guides, @@ -679,7 +645,7 @@ def direct_review_plan( ), "max_routes": 3, "stop_after_routes": True, - "fallback_entry": _knowledge_path("AGENTS.md"), + "fallback_entry": _knowledge_path("AGENTS.md", view), }, "execution_budget": execution_budget, "first_review_checklist": list(DIRECT_REVIEW_CHECKLIST), @@ -721,6 +687,7 @@ def direct_review_plan( "if_missing": "partial_review", }, "diagnostics": { + "knowledge_snapshot": view.snapshot, "timing_ms": { "routing": route_ms, "execution_budget": budget_ms, diff --git a/src/infermatrix_copilot/knowledge_docs.py b/src/infermatrix_copilot/knowledge_docs.py index 07cc1d0e..9234dc10 100644 --- a/src/infermatrix_copilot/knowledge_docs.py +++ b/src/infermatrix_copilot/knowledge_docs.py @@ -5,6 +5,7 @@ import re from dataclasses import dataclass from pathlib import Path +from typing import Callable class KnowledgeDocsError(ValueError): @@ -25,7 +26,17 @@ def as_dict(self) -> dict: class KnowledgeDocs: """Read/search only the shared general slice and one repo-specific slice.""" - def __init__(self, root: str | Path, repo_subdir: str | None = None): + def __init__( + self, + root: str | Path, + repo_subdir: str | None = None, + *, + verify: Callable[[str], Path] | None = None, + ): + # `verify(rel)` is called before every file is read; an activated + # knowledge snapshot passes `KnowledgeView.path`, which raises when a + # file is missing from, or differs from, the snapshot manifest. + self._verify = verify self.root = Path(root).resolve() if not self.root.is_dir(): raise KnowledgeDocsError(f"knowledge root does not exist: {self.root}") @@ -69,8 +80,13 @@ def _resolve_doc(self, relative_path: str) -> Path: raise KnowledgeDocsError("path is not a regular file") if target.suffix.casefold() != ".md": raise KnowledgeDocsError("only Markdown documents are readable") + self._checked(target) return target + def _checked(self, target: Path) -> None: + if self._verify is not None: + self._verify(target.relative_to(self.root).as_posix()) + def read(self, path: str, *, offset: int = 0, limit: int = 24_000) -> dict: if isinstance(offset, bool) or not isinstance(offset, int) or offset < 0: raise KnowledgeDocsError("offset must be a non-negative integer") @@ -103,6 +119,7 @@ def search(self, query: str, *, limit: int = 40) -> list[dict]: if target in seen_files or not self._in_scope(target) or not target.is_file(): continue seen_files.add(target) + self._checked(target) rel = target.relative_to(self.root).as_posix() for lineno, line in enumerate( target.read_text(encoding="utf-8", errors="replace").splitlines(), 1): diff --git a/src/infermatrix_copilot/knowledge_view.py b/src/infermatrix_copilot/knowledge_view.py new file mode 100644 index 00000000..0753ecdc --- /dev/null +++ b/src/infermatrix_copilot/knowledge_view.py @@ -0,0 +1,180 @@ +"""Per-request view of the knowledge tree: the packaged copy or an activated snapshot. + +Knowledge used to be resolved once, at import, from the wheel. The knowledge +service activates a new snapshot by atomically swapping a symlink, and a module +constant captured at import would keep serving the old tree (retired rules +included) until the process restarted. A view is therefore resolved at the START +of each request and every read inside that request goes through it. + +Resolution: + +* ``KNOWLEDGE_ROOT`` unset -> the packaged tree (``sdk._resources``), exactly as + before. ``snapshot`` is ``"packaged"``. +* ``KNOWLEDGE_ROOT`` set -> ``realpath`` of it, taken ONCE per view, so a swap + mid-request cannot mix two trees. A directory holding ``MANIFEST.json`` is an + activated snapshot: its ``knowledge/`` subtree is the root and every read is + checked against the manifest's sha256 (fail closed on any mismatch). A plain + directory holding ``AGENTS.md`` is served unverified, for development only. +""" + +from __future__ import annotations + +import hashlib +import json +import os +from dataclasses import dataclass, field +from functools import lru_cache +from pathlib import Path, PurePosixPath +from types import MappingProxyType +from typing import Mapping + +from .sdk._resources import knowledge_root + +KNOWLEDGE_ROOT_ENV = "KNOWLEDGE_ROOT" +MANIFEST_NAME = "MANIFEST.json" +MANIFEST_SCHEMA_VERSION = 1 + + +class KnowledgeViewError(RuntimeError): + """The configured knowledge root is missing, malformed, or tampered with.""" + + +@dataclass(frozen=True) +class KnowledgeView: + root: Path + snapshot: str + files: Mapping[str, str] | None = field(default=None, compare=False) + + @classmethod + def current(cls) -> "KnowledgeView": + configured = os.environ.get(KNOWLEDGE_ROOT_ENV, "").strip() + if not configured: + return _packaged_view() + try: + resolved = Path(configured).expanduser().resolve(strict=True) + except OSError as exc: + raise KnowledgeViewError( + f"{KNOWLEDGE_ROOT_ENV} does not resolve: {configured}" + ) from exc + return _load_view(str(resolved)) + + @property + def verified(self) -> bool: + return self.files is not None + + def relative(self, path: str | Path) -> str: + """Return the knowledge-relative id of an absolute path inside this view.""" + try: + return Path(path).resolve().relative_to(self.root).as_posix() + except ValueError as exc: + raise KnowledgeViewError( + f"path is outside the knowledge view: {path}" + ) from exc + + def path(self, relative_path: str) -> Path: + """Resolve one knowledge-relative file, refusing escapes and tampering.""" + value = str(relative_path).strip().replace("\\", "/") + pure = PurePosixPath(value) + if not value or pure.is_absolute() or ".." in pure.parts: + raise ValueError(f"knowledge route escapes the knowledge root: {relative_path}") + path = (self.root / pure).resolve() + try: + rel = path.relative_to(self.root).as_posix() + except ValueError as exc: + raise ValueError( + f"knowledge route escapes the knowledge root: {relative_path}" + ) from exc + if self.files is not None and rel in self.files and not path.is_file(): + # listed by the manifest but gone: the snapshot is corrupted, which is + # not the same as an optional file that was never there + raise KnowledgeViewError(f"snapshot file listed in manifest is missing: {rel}") + if not path.is_file(): + raise FileNotFoundError(f"knowledge route is missing: {path}") + if self.files is not None: + expected = self.files.get(rel) + if expected is None: + raise KnowledgeViewError(f"file is not in snapshot manifest: {rel}") + if _file_sha256(path) != expected: + raise KnowledgeViewError(f"snapshot file does not match manifest: {rel}") + return path + + def read_text(self, relative_path: str) -> str: + return self.path(relative_path).read_text(encoding="utf-8") + + +def _file_sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +@lru_cache(maxsize=1) +def _packaged_view() -> KnowledgeView: + return KnowledgeView(root=knowledge_root(), snapshot="packaged") + + +@lru_cache(maxsize=16) +def _load_view(resolved: str) -> KnowledgeView: + """Load a root once per process. Snapshots are immutable, so caching by the + resolved directory is safe: a new activation is a different directory.""" + base = Path(resolved) + manifest_path = base / MANIFEST_NAME + if manifest_path.is_file(): + return _load_snapshot(base, manifest_path) + if (base / "AGENTS.md").is_file(): + return KnowledgeView(root=base, snapshot=f"unverified:{base}") + raise KnowledgeViewError( + f"{KNOWLEDGE_ROOT_ENV} is neither a snapshot (no {MANIFEST_NAME}) " + f"nor a knowledge tree (no AGENTS.md): {base}" + ) + + +def _load_snapshot(base: Path, manifest_path: Path) -> KnowledgeView: + try: + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + except (OSError, ValueError) as exc: + raise KnowledgeViewError(f"unreadable snapshot manifest: {manifest_path}") from exc + if not isinstance(manifest, dict) or manifest.get("schema_version") != MANIFEST_SCHEMA_VERSION: + raise KnowledgeViewError(f"unsupported snapshot manifest: {manifest_path}") + snapshot = str(manifest.get("snapshot") or "").strip() + files = manifest.get("files") + if not snapshot or not isinstance(files, dict) or not files: + raise KnowledgeViewError(f"snapshot manifest lacks snapshot/files: {manifest_path}") + root = (base / "knowledge").resolve() + if not (root / "AGENTS.md").is_file(): + raise KnowledgeViewError(f"snapshot has no knowledge tree: {root}") + on_disk = { + path.relative_to(root).as_posix() + for path in root.rglob("*") if path.is_file() + } + if on_disk != set(files): + extra = sorted(on_disk - set(files))[:3] + missing = sorted(set(files) - on_disk)[:3] + raise KnowledgeViewError( + f"snapshot tree differs from manifest (extra={extra}, missing={missing})" + ) + tree = hashlib.sha256() + for rel in sorted(files): + tree.update(f"{rel}\0{files[rel]}\n".encode("utf-8")) + if manifest.get("tree_sha256") != tree.hexdigest(): + raise KnowledgeViewError("snapshot manifest tree_sha256 does not match its files") + return KnowledgeView( + root=root, + snapshot=snapshot, + files=MappingProxyType({str(k): str(v) for k, v in files.items()}), + ) + + +def build_manifest(root: Path, snapshot: str) -> dict: + """Manifest for a knowledge tree at ``root`` (the snapshot's ``knowledge/``).""" + files = { + path.relative_to(root).as_posix(): _file_sha256(path) + for path in sorted(root.rglob("*")) if path.is_file() + } + tree = hashlib.sha256() + for rel in sorted(files): + tree.update(f"{rel}\0{files[rel]}\n".encode("utf-8")) + return { + "schema_version": MANIFEST_SCHEMA_VERSION, + "snapshot": snapshot, + "files": files, + "tree_sha256": tree.hexdigest(), + } diff --git a/src/infermatrix_copilot/sdk/v1/direct.py b/src/infermatrix_copilot/sdk/v1/direct.py index 749ee764..d14b90a4 100644 --- a/src/infermatrix_copilot/sdk/v1/direct.py +++ b/src/infermatrix_copilot/sdk/v1/direct.py @@ -14,7 +14,7 @@ from typing import Any from ... import __version__ -from .._resources import adapters_root, knowledge_root +from .._resources import adapters_root from .models import ( DIRECT_API_VERSION, KNOWLEDGE_API_VERSION, @@ -88,7 +88,9 @@ def get_capabilities( """Return the SDK/distribution/resource handshake without starting a server.""" from ...knowledge_service.curation import _apply_supported - knowledge = knowledge_root() + from ...knowledge_view import KnowledgeView + + knowledge = KnowledgeView.current().root adapters = adapters_root() repositories = tuple(sorted( path.name @@ -121,6 +123,7 @@ class _IssuedContext: expected_head_sha: str resource_revision: str carried_findings: tuple[dict, ...] = () + knowledge_view: Any = None class DirectClient: @@ -134,47 +137,60 @@ class DirectClient: def __init__(self, *, max_issued_contexts: int = _DEFAULT_CONTEXT_LIMIT) -> None: if max_issued_contexts < 1: raise InvalidRequestError("max_issued_contexts must be >= 1") - self._knowledge = knowledge_root() self._adapters = adapters_root() self._max_issued_contexts = int(max_issued_contexts) self._issued_contexts: OrderedDict[str, _IssuedContext] = OrderedDict() self._context_lock = threading.Lock() + @staticmethod + def _view(): + # Resolved per request: an activated knowledge snapshot takes effect on + # the next request, and one request never mixes two trees. + from ...knowledge_view import KnowledgeView + + return KnowledgeView.current() + @property def resource_revision(self) -> str: - return _resource_revision(str(self._knowledge), str(self._adapters)) + return self._revision(self._view().root) + + def _revision(self, knowledge: Path) -> str: + return _resource_revision(str(knowledge), str(self._adapters)) def capabilities(self) -> Capabilities: return get_capabilities() - def _document_path(self, document_id: str) -> Path: + def _document_path(self, document_id: str, view=None) -> Path: + view = view if view is not None else self._view() value = str(document_id).strip().replace("\\", "/") pure = PurePosixPath(value) if not value or pure.is_absolute() or ".." in pure.parts: raise DocumentNotFoundError(f"invalid document_id: {document_id!r}") - path = (self._knowledge / pure).resolve() + path = (view.root / pure).resolve() try: - path.relative_to(self._knowledge) + path.relative_to(view.root) except ValueError as exc: raise DocumentNotFoundError( f"document_id escapes the knowledge bundle: {document_id!r}" ) from exc if not path.is_file(): raise DocumentNotFoundError(f"unknown document_id: {document_id!r}") - return path + return view.path(value) - def _document_id(self, path_value: str | Path) -> str: + def _document_id(self, path_value: str | Path, view=None) -> str: + view = view if view is not None else self._view() path = Path(path_value).expanduser().resolve() try: - return path.relative_to(self._knowledge).as_posix() + return path.relative_to(view.root).as_posix() except ValueError as exc: raise DocumentNotFoundError( "provider returned a document outside the packaged knowledge bundle" ) from exc - def _document_ref(self, path_value: str | Path) -> DocumentRef: - document_id = self._document_id(path_value) - data = self._document_path(document_id).read_bytes() + def _document_ref(self, path_value: str | Path, view=None) -> DocumentRef: + view = view if view is not None else self._view() + document_id = self._document_id(path_value, view) + data = self._document_path(document_id, view).read_bytes() excerpt_data = data[:_EXCERPT_BYTES] return DocumentRef( document_id=document_id, @@ -189,12 +205,30 @@ def read_document( *, offset: int = 0, max_bytes: int = _EXCERPT_BYTES, + review_context_id: str = "", ) -> DocumentPage: + """Read one knowledge document page. + + With ``review_context_id`` the read (and every later page of it) comes + from the knowledge tree that review's plan was issued from, so a snapshot + activated mid-review cannot change what the reviewer reads. Without it, + the currently active tree is used. + """ if offset < 0 or max_bytes < 1 or max_bytes > _EXCERPT_BYTES: raise InvalidRequestError( "offset must be >= 0 and max_bytes must be within 1..65536" ) - data = self._document_path(document_id).read_bytes() + view = None + context_id = str(review_context_id).strip().casefold() + if context_id: + with self._context_lock: + issued = self._issued_contexts.get(context_id) + if issued is None: + raise InvalidRequestError( + "review_context_id was not issued by this DirectClient" + ) + view = issued.knowledge_view + data = self._document_path(document_id, view).read_bytes() page = data[offset:offset + max_bytes] next_offset = offset + len(page) if offset + len(page) < len(data) else None return DocumentPage( @@ -233,12 +267,16 @@ def _request_values(self, request: DirectReviewRequest) -> tuple[str, list[str]] changed_files.append(path.as_posix()) return alias, changed_files - def _remember_context(self, context_id: str, expected_head_sha: str, carried=()) -> None: + def _remember_context( + self, context_id: str, expected_head_sha: str, carried=(), view=None, + ) -> None: + view = view if view is not None else self._view() with self._context_lock: self._issued_contexts[context_id] = _IssuedContext( expected_head_sha=expected_head_sha, - resource_revision=self.resource_revision, + resource_revision=self._revision(view.root), carried_findings=tuple(carried), + knowledge_view=view, ) self._issued_contexts.move_to_end(context_id) while len(self._issued_contexts) > self._max_issued_contexts: @@ -255,16 +293,19 @@ def plan(self, request: DirectReviewRequest) -> DirectReviewPlan: validate_carried(carried) alias, changed_files = self._request_values(request) expected_head = request.expected_head_sha.strip().casefold() + view = self._view() + revision = self._revision(view.root) raw = direct_review_plan( alias, title=request.title, body=request.body, changed_files=changed_files, + view=view, ) routes = tuple( KnowledgeRoute( owner=str(item.get("owner") or "unknown"), - document=self._document_ref(str(item["path"])), + document=self._document_ref(str(item["path"]), view), reason=str(item.get("reason") or ""), quick_map=str(item.get("quick_map") or ""), quick_map_status=str(item.get("quick_map_status") or "unavailable"), @@ -274,10 +315,10 @@ def plan(self, request: DirectReviewRequest) -> DirectReviewPlan: ) repo_name = alias.replace("_", "-") map_candidates = ( - self._knowledge / "README.md", - self._knowledge / "general" / "_index.md", - self._knowledge / "repos" / "_index.md", - self._knowledge / "repos" / repo_name / "_index.md", + view.root / "README.md", + view.root / "general" / "_index.md", + view.root / "repos" / "_index.md", + view.root / "repos" / repo_name / "_index.md", ) context_payload = { "review_id": request.review_id, @@ -288,7 +329,7 @@ def plan(self, request: DirectReviewRequest) -> DirectReviewPlan: "body": request.body, "changed_paths": [item.to_dict() for item in request.changed_paths], "carried_findings": carried, - "resource_revision": self.resource_revision, + "resource_revision": revision, } review_context_id = _sha256(json.dumps( context_payload, sort_keys=True, separators=(",", ":"), @@ -297,7 +338,7 @@ def plan(self, request: DirectReviewRequest) -> DirectReviewPlan: navigation_policy = dict(raw.get("navigation_policy") or {}) fallback = navigation_policy.pop("fallback_entry", "") if fallback: - navigation_policy["fallback_document_id"] = self._document_id(fallback) + navigation_policy["fallback_document_id"] = self._document_id(fallback, view) completion_gate = dict(raw.get("completion_gate") or {}) completion_gate["operation"] = str( completion_gate.pop("tool", "") or "validate_direct_review" @@ -305,17 +346,17 @@ def plan(self, request: DirectReviewRequest) -> DirectReviewPlan: plan = DirectReviewPlan( protocol_version=DIRECT_API_VERSION, review_context_id=review_context_id, - resource_revision=self.resource_revision, + resource_revision=revision, repository=request.repository, expected_head_sha=expected_head, - knowledge_entry=self._document_ref(raw["knowledge_entry"]), + knowledge_entry=self._document_ref(raw["knowledge_entry"], view), knowledge_routes=routes, mandatory_review_guides=tuple( - self._document_ref(path) + self._document_ref(path, view) for path in raw.get("mandatory_review_guides") or [] ), document_maps=tuple( - self._document_ref(path) + self._document_ref(path, view) for path in map_candidates if path.is_file() ), routing=dict(raw.get("routing") or {}), @@ -327,7 +368,7 @@ def plan(self, request: DirectReviewRequest) -> DirectReviewPlan: diagnostics=dict(raw.get("diagnostics") or {}), carried_findings=request.carried_findings, ) - self._remember_context(review_context_id, expected_head, carried) + self._remember_context(review_context_id, expected_head, carried, view) return plan def validate( @@ -352,7 +393,17 @@ def validate( missing.append( "expected_head_sha does not match the issued review context" ) - if self.resource_revision != issued.resource_revision: + # Compare against the knowledge tree the plan was issued from, not + # whatever is active now: activating a newer snapshot mid-review must + # not invalidate a review pinned to the previous one. + # Recompute WITHOUT the digest cache: the cached value was filled + # at plan time and would hide a file changed or deleted since. + pinned = issued.knowledge_view.root + if ( + not pinned.is_dir() + or _resource_revision.__wrapped__(str(pinned), str(self._adapters)) + != issued.resource_revision + ): missing.append( "provider resources changed after the review context was issued" ) diff --git a/src/infermatrix_copilot/thin_mcp_server.py b/src/infermatrix_copilot/thin_mcp_server.py index db65c0fa..716b2c11 100644 --- a/src/infermatrix_copilot/thin_mcp_server.py +++ b/src/infermatrix_copilot/thin_mcp_server.py @@ -14,9 +14,6 @@ # module's own call sites unchanged and delegate DOWN — nothing imports back up # into a server module. from .direct_routing import ( # noqa: F401 — aliases kept for existing importers - _DIRECT_OWNER_ROUTES, - _KNOWLEDGE, - _REPO_ALIASES, _ROOT, _adapter_changed_file_routes, _adapter_for_repo, @@ -30,6 +27,7 @@ _normalize_repo, direct_review_plan, ) +from .knowledge_view import KnowledgeView from .intent import resolve_repo_alias from .knowledge_docs import KnowledgeDocs, KnowledgeDocsError from .mcp_policy import PolicyError @@ -40,21 +38,30 @@ re.IGNORECASE, ) _PR_NUMBER = re.compile(r"^(?:pr\s*#?\s*)?(\d+)$", re.IGNORECASE) +def __getattr__(name: str): + # formerly an import-time constant; now the knowledge root of the current view + if name == "_KNOWLEDGE": + return KnowledgeView.current().root + raise AttributeError(name) + + def _supported_repos() -> list[str]: - if not (_KNOWLEDGE / "repos").is_dir(): + knowledge = KnowledgeView.current().root + if not (knowledge / "repos").is_dir(): return [] return sorted( - path.name for path in (_KNOWLEDGE / "repos").iterdir() + path.name for path in (knowledge / "repos").iterdir() if path.is_dir() and (path / "_index.md").is_file() ) def _docs(repo: str) -> KnowledgeDocs: repo = _normalize_repo(repo) - repo_dir = _KNOWLEDGE / "repos" / repo + view = KnowledgeView.current() + repo_dir = view.root / "repos" / repo if not repo_dir.is_dir(): raise KnowledgeDocsError(f"unsupported knowledge repo: {repo}") - return KnowledgeDocs(_KNOWLEDGE, f"repos/{repo}") + return KnowledgeDocs(view.root, f"repos/{repo}", verify=view.path) def _guard(fn): @@ -66,7 +73,7 @@ def _guard(fn): def _knowledge_entry(name: str) -> str: - path = (_KNOWLEDGE / name).resolve() + path = (KnowledgeView.current().root / name).resolve() if not path.is_file(): raise FileNotFoundError(f"knowledge entry is missing: {path}") return str(path) @@ -84,7 +91,7 @@ def _contributing_entry() -> str: tree, so an installed wheel still answers the tool. """ for candidate in (_ROOT / "doc" / "knowledge" / "CONTRIBUTING.md", - _KNOWLEDGE.parent / "doc" / "knowledge" / "CONTRIBUTING.md"): + KnowledgeView.current().root.parent / "doc" / "knowledge" / "CONTRIBUTING.md"): if candidate.is_file(): return str(candidate.resolve()) raise FileNotFoundError( diff --git a/test/fixtures/direct_routing/golden_routes.jsonl b/test/fixtures/direct_routing/golden_routes.jsonl new file mode 100644 index 00000000..91e68b96 --- /dev/null +++ b/test/fixtures/direct_routing/golden_routes.jsonl @@ -0,0 +1,153 @@ +{"pr":8208,"title":"[Misc] Sort OMNI_PIPELINES registry alphabetically","body":"Closes #8207\r\n\r\n## Purpose\r\n\r\n`OMNI_PIPELINES` in `vllm_omni/config/pipeline_registry.py` has grown to 64\r\nentries in insertion order, while the import block above it is kept\r\nalphabetically sorted (ruff/isort). This PR sorts the dict by `model_type`\r\nkey (case-insensitive lexicographic order) — the convention upstream vLLM\r\n[documents for model lists](https://docs.vllm.ai/en/latest/contributing/model/registration.html)\r\n— and adds a docstring note so future entries keep the order. Alias comments\r\nstay attached to their entries.\r\n\r\nPure reordering: dict lookup is order-independent, so no behavior change.\r\n\r\n## Test Plan\r\n\r\n- `pre-commit run --files vllm_omni/config/pipeline_registry.py`\r\n- AST comparison against `origin/main`: same 64 key→value pairs, order only\r\n- `pytest tests/config/test_pipeline_registry.py` — covered by CI (no vllm\r\n install on the authoring machine)\r\n\r\n**vLLM Version:** N/A (no runtime change)\r\n\r\n**vLLM-Omni Commit:** deedb58bb\r\n\r\n## Test Result\r\n\r\n- pre-commit: all applicable hooks passed (ruff check/format, mypy, SPDX,\r\n forbidden imports)\r\n- AST check: 64 entries identical to `origin/main`, keys sorted\r\n","changed_files":["tests/config/test_pipeline_registry.py","vllm_omni/config/pipeline_registry.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, registry, pipeline","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/pipeline_registry.py"],"selected_from_description":true}]}} +{"pr":8195,"title":"ci: extend AMD Z-Image cold-start timeout","body":"## Summary\n- extend the AMD Z-Image merge-lane timeout from 30 minutes to 55 minutes\n- preserve the existing test target and add a pipeline regression assertion\n\n## Root cause\nAMD build 12829 exited with status 124 at the outer 30-minute timeout. Model loading consumed about 366 seconds, and the first-request AITER compilation was still progressing when the job was terminated. This is a cold-start budget issue rather than a captured kernel failure.\n\n## Validation\n- python3 -m pytest --confcutdir=tests/buildkite -o addopts= -q tests/buildkite/test_amd_pipeline.py\n- 7 passed\n\nBuild: https://buildkite.com/vllm/vllm-omni-amd-ci/builds/12829","changed_files":[".buildkite/amd/test-amd-merge.yml","tests/buildkite/test_amd_pipeline.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":8192,"title":"[Perf][PersonaPlex] Serve 16 concurrent realtime duplex sessions on one GPU","body":"## Purpose\n\nPart of RFC #7389. Makes PersonaPlex serve many concurrent realtime sessions on the unified duplex path. On one 141 GB Hopper-class GPU the number of paced sessions that stay realtime goes from 1 to 16.\n\nBuilds on #7695 (PersonaPlex on the unified duplex plugin), now merged.\n\n## What was wrong (measured on #7695)\n\n| sessions | client RTF (per session) | frame deficit / 375 |\n| --- | --- | --- |\n| 1 | 1.01 | 4 |\n| 2 | 1.44 | 69 |\n| 4 | 1.87 | ~140 |\n| 8 | stage 0 dies (device-side assert) | |\n\n1. **Crash when a new session joins a live one.** `make_omni_output` truncated the token hidden states to the number of cached audio-code rows (`personaplex_talker.py`). A step that mixes a new session's multi-row prefill with a live session's one-row append then indexes the truncated tensor with token-space `logits_indices`, and stage 0 dies for every session. This reproduces at N=2 whenever the two land in one step.\n2. **Sessions do not share a step.** Each live session appends one frame per 80 ms tick as a one-token resumable request. With stage 0 `async_scheduling: true` the next step is scheduled before a session's previous frame is parked, so a session can only join every oth","changed_files":["tests/model_executor/models/personaplex/duplex/test_plugin.py","tests/model_executor/models/personaplex/duplex/test_stage0_runtime.py","tests/model_executor/models/personaplex/test_depformer_num_steps.py","tests/model_executor/models/personaplex/test_ring_per_row_offsets.py","tests/model_executor/models/personaplex/test_talker_omni_output.py","tests/model_executor/stage_input_processors/test_personaplex.py","vllm_omni/deploy/personaplex.yaml","vllm_omni/model_executor/models/personaplex/duplex/stage0.py","vllm_omni/model_executor/models/personaplex/personaplex_depformer.py","vllm_omni/model_executor/models/personaplex/personaplex_mimi.py","vllm_omni/model_executor/models/personaplex/personaplex_talker.py","vllm_omni/model_executor/models/personaplex/personaplex_temporal.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:personaplex","path":"repos/vllm-omni/models/personaplex/rules.md","reason":"title/body model: personaplex","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/personaplex/duplex/stage0.py, vllm_omni/model_executor/models/personaplex/personaplex_depformer.py, vllm_omni/model_executor/models/personaplex/personaplex_mimi.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/deploy/personaplex.yaml"],"selected_from_description":false},{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/personaplex/duplex/stage0.py","vllm_omni/model_executor/models/personaplex/personaplex_depformer.py","vllm_omni/model_executor/models/personaplex/personaplex_mimi.py","vllm_omni/model_executor/models/personaplex/personaplex_talker.py","vllm_omni/model_executor/models/personaplex/personaplex_temporal.py"],"selected_from_description":false}]}} +{"pr":8175,"title":"[Doc] Update README and docs for v0.30.0","body":"Follows the pattern of #6858 (v0.28.0 doc update) for the [v0.30.0 release](https://github.com/vllm-project/vllm-omni/releases/tag/v0.30.0).\n\n## Documentation\n\n- **README.md**: added the `2026/09` news bullet for 0.30.0 (unified full-duplex framework around engine-owned sessions for MiniCPM-o 4.5/AURA, cross-stage KV and multimodal payload transfer via Mooncake/NIXL, LingBot World interactive world-model serving, realtime MiniMax H3 Turbo on Blackwell/Ascend 950) and refreshed the supported-model examples: Tencent AuK and Breeze-TTS-2 under TTS, LingBot World and MAGI-2 under diffusion, π0.5 under robot-policy.\n- **docs/README.md**: same model-example refresh as the root README.\n- **docs/getting_started/installation/README.md**, **gpu.md**, **gpu/cuda.inc.md**, **gpu/rocm.inc.md**: dropped the \"until 0.30.0 artifacts are published\" caveats now that the release is out; pre-built wheel commands, the wheels-available list, and the docker run examples moved from 0.28.0 to 0.30.0 (`vllm/vllm-omni:v0.30.0`, `vllm/vllm-omni-rocm:v0.30.0`).\n- **docs/getting_started/installation/npu/npu.inc.md**: source clone moved from `v0.30.0rc1` to the final `v0.30.0` tag.\n- **docs/configuration/README.","changed_files":["README.md","docs/README.md","docs/configuration/README.md","docs/configuration/environment_variables.md","docs/getting_started/installation/README.md","docs/getting_started/installation/gpu.md","docs/getting_started/installation/gpu/cuda.inc.md","docs/getting_started/installation/gpu/rocm.inc.md","docs/getting_started/installation/npu/npu.inc.md"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:lingbot-world","path":"repos/vllm-omni/models/lingbot-world/rules.md","reason":"title/body model: lingbot-world","quick_map_status":"ok","read_required":false},{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8172,"title":"[Model] Support SenseNova-U1-A3B-MoT (MoE)","body":"## Purpose\r\n\r\nAdds support for [sensenova/SenseNova-U1-A3B-MoT](https://huggingface.co/sensenova/SenseNova-U1-A3B-MoT), the MoE member of the SenseNova-U1 family and the only one still unsupported. Closes #3612.\r\n\r\nA3B runs on the existing `SenseNovaU1Pipeline`. The checkpoint keeps `model_type: neo_chat`, so there is no new registry entry or deploy YAML; only the LLM backbone changes. Both Mixture-of-Tokenizers branches are sparse: the understanding branch (`mlp`) routes over 128 experts and the generation branch (`mlp_mot_gen`) over 32, top-8 each, with `moe_intermediate_size=768`. The dense U1-8B and U1.5 checkpoints stay on the code path they already used.\r\n\r\n- **Config** (`transformers_utils/configs/sensenova_u1.py`): `SenseNovaU1MoELLMConfig`, a Qwen3-MoE config plus `gen_num_experts` / `gen_num_experts_per_tok` / `gen_moe_intermediate_size`, each falling back to its understanding-path value. `SenseNovaU1Config` selects it whenever `llm_config` carries `num_experts`.\r\n- **Transformer** (`sensenova_u1_transformer.py`): `SenseNovaU1SparseMoeBlock` on vLLM `FusedMoE`, chosen per layer and per branch with the upstream Qwen3-MoE `mlp_only_layers` / `decoder_sparse_step` rule.\r\n- *","changed_files":["docs/models/supported_models.md","docs/user_guide/diffusion_features.md","recipes/SenseNova/SenseNova-U1.md","tests/diffusion/models/sensenova_u1/test_sensenova_u1_moe.py","tests/diffusion/models/sensenova_u1/test_sensenova_u1_moe_cuda.py","vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py","vllm_omni/diffusion/models/sensenova_u1/sensenova_u1_transformer.py","vllm_omni/transformers_utils/configs/__init__.py","vllm_omni/transformers_utils/configs/sensenova_u1.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:sensenova-u1","path":"repos/vllm-omni/models/sensenova-u1/rules.md","reason":"title/body model: sensenova-u1","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, yaml, registry","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py, vllm_omni/diffusion/models/sensenova_u1/sensenova_u1_transformer.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py","vllm_omni/diffusion/models/sensenova_u1/sensenova_u1_transformer.py"],"selected_from_description":false}]}} +{"pr":8166,"title":"test: keep Hunyuan LoRA CPU path on native linear","body":"## Summary\n\n- keep the Hunyuan Image 3 PEFT QKV numerical contract on native CPU linear math\n- leave real QKV construction, tensor-parallel sharding, adapter loading, and LoRA application on production code\n- avoid dispatching CPU tensors into the ROCm-only unquantized GEMM custom operator\n\n## Root cause\n\nThe test is intentionally CPU-only, but vLLM selects unquantized GEMM from the process-wide platform. In the ROCm image that selects vllm::rocm_unquantized_gemm even though the test tensors are on CPU, producing NotImplementedError for the CPU backend for TP sizes 1, 2, and 4.\n\nExample failure: https://buildkite.com/vllm/vllm-omni-amd-ci/builds/12820/table?jid=01a0dab5-3687-400c-b435-24f5786b8c98&tab=output\n\n## Validation\n\n- python3 -m py_compile tests/diffusion/lora/test_lora_manager.py\n- Ruff format check with the repository 120-column setting\n- git diff --check\n- exact-head AMD runtime validation requested after opening\n\nSigned-off-by: andyluo7 ","changed_files":["tests/diffusion/lora/test_lora_manager.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:hunyuan-image3","path":"repos/vllm-omni/models/hunyuan-image3/rules.md","reason":"title/body model: hunyuan-image3","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, lora","quick_map_status":"ok","read_required":false},{"owner":"distributed","path":"repos/vllm-omni/components/distributed/_index.md","reason":"title/body: tensor parallel","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8164,"title":"[CI][Diffusion] Restore Krea2 model type marker","body":"## Summary\n\n- restore `ModelTypeMarker.DIFFUSION` on the Krea2 test registry entry\n- fix collection across AMD CI after #5875 and #6421 landed in sequence\n\n## Root cause\n\n#5875 made `model_type_marker` a required `DiffusionModelTestOpts` field. #6421 added the Krea2 entry from an older base without that field. After both merged, AMD build #12819 failed 21 jobs during collection with:\n\n```text\nTypeError: DiffusionModelTestOpts.__new__() missing 1 required positional argument: model_type_marker\n```\n\n## Validation\n\n- `python3 -m py_compile tests/model_tests/diffusion/model_settings.py`\n- parsed all 15 `DiffusionModelTestOpts` entries and verified none omit `model_type_marker`\n- executed the registry module with isolated dependency stubs and verified the Krea2 marker resolves to `ModelTypeMarker.DIFFUSION`\n- `git diff --check origin/main...HEAD`\n\nBuild: https://buildkite.com/vllm/vllm-omni-amd-ci/builds/12819","changed_files":["tests/model_tests/diffusion/model_settings.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:krea2","path":"repos/vllm-omni/models/krea2/rules.md","reason":"title/body model: krea2","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: registry","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8163,"title":"[Model] Optimize Qwen3-TTS streaming throughput and first audio on CUDA","body":"## Purpose\n\nImprove Qwen3-TTS streaming throughput and first-audio latency on a single CUDA GPU. The experimental single-GPU profile combines frame-local residual-predictor KV reuse, supplied-uniform fused sampling, time-major codec convolutions, scoped cuDNN autotuning, and eager residual prediction with first-frame delivery from the Talker.\n\nThe Talker can send the first frame without a stage hop. Code2Wav advances its causal state without emitting that frame again; explicit per-request metadata and output ordering preserve the first frame ahead of subsequent codec chunks, including when the final codec output arrives first. Reference-code requests and unsupported runners/platforms retain regular codec delivery. Qwen3-TTS profiles explicitly request first-audio delivery; it activates only for CUDA MRV2, async chunks, TP/PP 1, an in-process executor, and disabled prefix caching.\n\nThe first-frame path is also enabled by the standard `qwen3_tts.yaml`, `qwen3_tts_high_concurrency.yaml`, `qwen3_tts_mrv2.yaml`, and `qwen3_tts_high_concurrency_mrv2.yaml` profiles. On supported CUDA MRV2 streaming deployments, this enables eager Talker-MTP, an additional Talker-side decoder with weights ","changed_files":[".buildkite/common/ci_source_file_dependencies.yml","docs/configuration/stage_configs.md","docs/design/qwen3_omni_tts_performance_optimization.md","tests/config/test_environment_variables.py","tests/config/test_omni_config.py","tests/e2e/online_serving/test_qwen3_tts_customvoice.py","tests/engine/test_async_omni_engine_stage_init.py","tests/engine/test_cuda_mps.py","tests/engine/test_data_entry_keys.py","tests/engine/test_orchestrator_first_audio.py","tests/model_executor/models/qwen3_tts/test_cached_code_predictor.py","tests/model_executor/models/qwen3_tts/test_code_predictor_dtype.py","tests/model_executor/models/qwen3_tts/test_code_predictor_sampling.py","tests/model_executor/models/qwen3_tts/test_first_audio_graphs.py","tests/model_executor/models/qwen3_tts/test_qwen3_tts_code2wav.py","tests/model_executor/models/qwen3_tts/test_short_kv_attention.py","tests/model_executor/models/qwen3_tts/test_skip_delivered_first.py","tests/model_executor/models/qwen3_tts/test_time_major_conv.py","tests/model_executor/models/qwen3_tts/test_time_major_decoder.py","tests/worker_v2/test_first_audio_sender.py","tests/worker_v2/test_omni_model_state.py","vllm_omni/config/environment_variable_inventory.py","vllm_omni/config/omni_config.py","vllm_omni/config/stage_config.py","vllm_omni/data_entry_keys.py","vllm_omni/deploy/qwen3_tts.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2_single_gpu.yaml","vllm_omni/deploy/qwen3_tts_mrv2.yaml","vllm_omni/engine/cuda_mps.py","vllm_omni/engine/orchestrator.py","vllm_omni/engine/stage_engine_core_proc.py","vllm_omni/engine/stage_init_utils.py","vllm_omni/engine/stage_runtime.py","vllm_omni/model_executor/models/common/qwen3_code_predictor.py","vllm_omni/model_executor/models/qwen3_tts/cached_code_predictor.py","vllm_omni/model_executor/models/qwen3_tts/code_predictor_sampling.py","vllm_omni/model_executor/models/qwen3_tts/first_audio.py","vllm_omni/model_executor/models/qwen3_tts/first_frame_decoder.py","vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code2wav.py","vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code_predictor_vllm.py","vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_talker.py","vllm_omni/model_executor/models/qwen3_tts/segmented_graph_wrapper.py","vllm_omni/model_executor/models/qwen3_tts/short_kv_attention.py","vllm_omni/model_executor/models/qwen3_tts/tokenizer_12hz/modeling_qwen3_tts_tokenizer_v2.py","vllm_omni/model_executor/models/qwen3_tts/tokenizer_12hz/time_major_conv.py","vllm_omni/model_executor/models/qwen3_tts/tokenizer_12hz/time_major_stack.py","vllm_omni/model_executor/stage_input_processors/qwen3_tts.py","vllm_omni/utils/device_copy.py","vllm_omni/worker_v2/first_audio_sender.py","vllm_omni/worker_v2/model_states/eager_mtp.py","vllm_omni/worker_v2/model_states/omni_model_state.py","vllm_omni/worker_v2/omni_ar_model_runner.py","vllm_omni/worker_v2/omni_model_runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: yaml","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/environment_variable_inventory.py","vllm_omni/config/omni_config.py","vllm_omni/config/stage_config.py","vllm_omni/deploy/qwen3_tts.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2_single_gpu.yaml","vllm_omni/deploy/qwen3_tts_mrv2.yaml"],"selected_from_description":true},{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/common/qwen3_code_predictor.py","vllm_omni/model_executor/models/qwen3_tts/cached_code_predictor.py","vllm_omni/model_executor/models/qwen3_tts/code_predictor_sampling.py","vllm_omni/model_executor/models/qwen3_tts/first_audio.py","vllm_omni/model_executor/models/qwen3_tts/first_frame_decoder.py","vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code2wav.py","vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code_predictor_vllm.py","vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_talker.py","vllm_omni/model_executor/models/qwen3_tts/segmented_graph_wrapper.py","vllm_omni/model_executor/models/qwen3_tts/short_kv_attention.py","vllm_omni/model_executor/models/qwen3_tts/tokenizer_12hz/modeling_qwen3_tts_tokenizer_v2.py","vllm_omni/model_executor/models/qwen3_tts/tokenizer_12hz/time_major_conv.py","vllm_omni/model_executor/models/qwen3_tts/tokenizer_12hz/time_major_stack.py","vllm_omni/model_executor/stage_input_processors/qwen3_tts.py"],"selected_from_description":true}]}} +{"pr":8160,"title":"[Bugfix] Fix MiniCPM-o 4.5 seeded turns and window E2E inputs","body":"## Purpose\n\nAddresses #8154.\n\nSeeded MiniCPM-o 4.5 duplex requests fail with missing `response.done` and listen-only events. After #8152, the seed is marked pending, but its prompt opens an assistant turn while initialization still leaves `current_turn_ended=True`. Seeded plain-text requests can consequently remain listen-only. Initialize the existing native turn state with the seeded prefix and retain pending input through `tts_bos`, consuming it on the first content token. The model's `turn_eos` still closes the response and later silence returns to normal listening.\n\nThe short-window tests have a separate input problem: they discard context before retaining a complete 5.47-second fixture utterance, then stop the audio clock immediately after speech. The same input produced only listen decisions with windowing **disabled**. Retain enough context and send a five-second pause before commit. Basic and context windows still rebuild; completion, audio, error, close, subsequent-session, burst-input, camera, and two-minute-input assertions are unchanged.\n\nAdd a seed lifecycle regression covering absent/empty/English/Chinese text, speech-envelope handling, and silence after `turn_eos`.\n\n","changed_files":["tests/e2e/online_serving/helpers/minicpmo_window_e2e.py","tests/worker/test_native_duplex_hooks.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py","vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: response","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py, vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py","vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni.py"],"selected_from_description":false}]}} +{"pr":8159,"title":"[Bugfix] Fix MRV2 payload slicing with CUDA Graph padding","body":"## Purpose\n\nFix the two `--no-async-chunk` Qwen3-TTS failures in [nightly build 16148](https://buildkite.com/vllm/vllm-omni/builds/16148/canvas?sid=01a0d805-eb4d-4a63-ba65-16d5f6f12d59&tab=output): concurrent Base voice cloning and CustomVoice speech produce empty or incomplete transcripts. Concurrent non-streaming requests are affected too.\n\nThe MRV2 full-payload builder uses the hidden-state row count to identify token-aligned tensors. In an observed three-request decode batch, CUDA Graph pads hidden states to four rows while codec outputs have three rows. The builder consequently gives every request the whole codec batch. Use the scheduled token count and propagate the padded count so both padded and unpadded multimodal tensors are sliced per request.\n\nAdd regression coverage for sync/async transfers, independent hidden/codec padding, decode and mixed prefill/decode batches, and request-local reference codes whose length matches the padded batch. No model, deploy configuration, assertion threshold, or retry changes.\n\n## Test Plan\n\nH200, one GPU per server (physical GPU 6 for Base; physical GPU 7 for CustomVoice), existing `qwen3_tts.yaml` with CUDA Graph enabled. Models: `Qwen/Q","changed_files":["tests/worker_v2/test_omni_ar_model_runner.py","vllm_omni/worker_v2/omni_ar_model_runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration, yaml, deploy","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, request","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":8157,"title":"[Bugfix] Restore torch default device between tests","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nCombined `core_model and cpu` pytest runs fail `test_make_omni_output_reads_history_from_runner_cpu_buffer` with `assert ['cpu', 'cpu'] == ['cpu']`, while the same test passes in the split merge-CI job. Patching `Tensor.tolist` is re-entered once default-device dispatch is active. `get_default_device()` reports `cpu` both when no default device was set and when `cpu` was set explicitly, so writing that value back leaves dispatch on.\r\n\r\n- Move dtype/device restoration from `tests/diffusion/conftest.py` to an autouse fixture in `tests/helpers/fixtures/clean.py`, which the root `tests/conftest.py` loads for every test. Snapshot `torch._GLOBAL_DEVICE_CONTEXT.device_context` and restore with `set_default_device(None)` when it was unset.\r\n- Drop the MiniCPM-o-only invalid-audio server test and cover a corrupt audio data URL on the shared omni chat invalid-input test (`audio_data_url_corrupt`, HTTP 400, `Invalid or corrupted audio data`).\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** not installed in the local environment used for this change.\r\n\r\n**vLLM-Omni Commit:** `1c33efb7cdb25d44997347663b58fc32cd81e808`\r","changed_files":["tests/diffusion/conftest.py","tests/helpers/fixtures/clean.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, http","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8152,"title":"[Bugfix] Restore MiniCPM duplex Seed-TTS startup","body":"## Purpose\r\n\r\nFixes #8138. Since #7974, native duplex Seed-TTS can remain listening without starting its first response. Mark nonempty initial text as pending input during session initialization; existing generation logic consumes it while preserving forced listening and post-response silence.\r\n\r\nKeep #7974's discarded-lookahead guard. Source inspection and synthetic-logits replay indicate that earlier discarded sampling could clear `current_turn_ended`, masking the missing seed initialization. No historical GPU token/state trace was captured.\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** 0.30.0.\r\n\r\n**vLLM-Omni Commit:** `6fb2b36` before/after the patch; `b308e19` + patch; pristine controls `d486bf0` and `7a8d956`.\r\n\r\nOne GB200; `openbmb/MiniCPM-o-4_5` at `503e754`, `zhaochenyang20/seed-tts-eval` at `8f5e1aa`. Retain the original deployment, four English targets, two warmups, assertions and timeouts; model/dataset paths point to local copies. CI uses H100. Benchmark command from CI, followed by focused checks:\r\n\r\n```bash\r\nexport BENCHMARK_DIR=tests/dfx/perf/results\r\npytest -s -v tests/dfx/perf/scripts/run_benchmark.py \\\r\n --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_duplex_see","changed_files":["tests/worker/test_native_duplex_hooks.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py"],"selected_from_description":true}]}} +{"pr":8147,"title":"[CI/Build] Fix MiniCPM-o codec readback test under default-device dispatch","body":"## Purpose\n\nFix the two MiniCPM-o codec-history test failures in [weekly CPU coverage build #16128](https://buildkite.com/vllm/vllm-omni/builds/16128#01a0d6c1-3699-4b90-b635-9eb6b8942614): `assert ['cpu', 'cpu'] == ['cpu']`.\n\nAfter the Sana Video scheduler tests install a CPU default-device context, PyTorch's `__torch_function__` dispatch re-enters the test's `Tensor.tolist` monkeypatch. The same CPU conversion is counted twice. Running the Talker file alone does not reproduce the failure.\n\nUse `TorchFunctionMode` to observe `tolist` without replacing it, and parameterize the existing CPU/CUDA test with and without a CPU default-device context. Keep the exact-one-readback, CPU-device, emitted-code, history and repetition-penalty assertions. Production code is unchanged.\n\n## Test Plan\n\nOn an H800 with Torch 2.13.0+cu130, Transformers 5.14.1 and pytest 9.1.1; no model weights required:\n\n```bash\n# Reproduce the original test-order failure, then verify the fix.\npython -m pytest -q \\\n tests/diffusion/models/sana_video/test_pipeline_sana_video.py::test_sana_video_i2v_consumes_batch_schedule_and_step_kwargs \\\n tests/model_executor/models/minicpmo_4_5/test_talker_batching.py::test_make_o","changed_files":["tests/model_executor/models/minicpmo_4_5/test_talker_batching.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"model:sana-video","path":"repos/vllm-omni/models/sana-video/rules.md","reason":"title/body model: sana-video","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline, default","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8146,"title":"[Bugfix][MiniCPM-o] Build the Stage-0 duplex runtime at load time","body":"## Purpose\r\n\r\nFixes #8123 (`test_duplex_soft_interrupt[three-stage-single-gpu]` fails in release CI).\r\n\r\n**Root cause.** The MiniCPM-o Stage-0 duplex helper (`MiniCPMO45Stage0DuplexRuntime`, dominated by the remote-code `AutoProcessor.from_pretrained`) is built lazily inside `preprocess`, i.e. inside the **first audio unit of the first duplex session** on a fresh server. That unit takes 7.5 s instead of ~40 ms. Since the client streams audio in real time, that session then runs 8-12 s behind its input for the rest of its life.\r\n\r\nIn `test_minicpmo_4_5_duplex_expansion.py` the soft-interrupt test is that first session: the preceding admission/expiry probe never reaches Stage-0 `preprocess`. On a slower host the model hears the question ~10 s late, gives one long answer that overlaps the final commit, and the second response and the listens around the commit never happen. That is exactly the failure in the issue (Stage-0 `vllm_ttft_ms` = 12178, first `response.created` at 20 s). On a faster host the backlog drains in time and the test passes by a thin margin.\r\n\r\n**Fix.** For the `llm` stage in a `session_mode: duplex` deployment, build the helper at the end of `__init__`. The loader ","changed_files":["tests/model_executor/models/minicpmo_4_5/duplex/test_stage0_runtime_load.py","vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, response","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: loader","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni.py"],"selected_from_description":true}]}} +{"pr":8132,"title":"[Bugfix][Qwen3-TTS] Decode full payload from model buffer","body":"## Purpose\n\nFixes #8091. The Qwen3-TTS Base `--no-async-chunk` online test produced audio unrelated to its prompt (CI reported transcript similarity 0.025 for “The weather is nice today, perfect for a walk in the park.”).\n\nReproduce with the existing B200 test environment and model cache:\n\n```bash\npytest -s -v 'tests/e2e/online_serving/test_qwen3_tts_base_expansion.py::test_voice_clone_streaming_001[no_async_chunk]'\n```\n\nIn the full-payload path, Stage 0 sends codec IDs through the per-request `model_intermediate_buffer` side channel. Code2Wav only read the older `runtime_additional_information` argument, so it fell back to placeholder `input_ids` and decoded the wrong audio. Read the current side channel when the older one is empty. The regression test checks the exact codec IDs received by the decoder.\n\nThe change is limited to `qwen3_tts_code2wav.py` and its unit test.\n\n## Test Plan\n\n**vLLM Version:** 0.30.0\n\n**vLLM-Omni Commit:** `574f2e685501177b29dde617f5a5872be57435c7` (rebased on `7b4dc7633bde8642f0c05f77bd018751541df404`)\n\n- Run Code2Wav unit tests and the Qwen3-TTS ready and merge CI selections.\n- Serve a real Base speech request with `--no-async-chunk` and transcribe the","changed_files":["tests/model_executor/models/qwen3_tts/test_qwen3_tts_code2wav.py","vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code2wav.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code2wav.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code2wav.py"],"selected_from_description":false}]}} +{"pr":8131,"title":"[Doc] Announce PR limitation to avoid agent spam","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n- We set PR limits (up to 5) to avoid agent spamming for maintenance availability, this PR updates PR description and doc for announcement.\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:**\r\n\r\n**vLLM-Omni Commit:**\r\n\r\n## Test Result\r\n\r\n**BEFORE SUBMITTING:** read [CONTRIBUTING.md](https://github.com/vllm-project/vllm-omni/blob/main/CONTRIBUTING.md) and run the [precheck-pr skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) with the code agent for a self-check against project conventions.\r\n(anything written below this line will be removed by GitHub Actions)\r\n","changed_files":[".github/PULL_REQUEST_TEMPLATE.md","CONTRIBUTING.md","docs/contributing/README.md"],"expected":{"status":"description_unrouted","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[],"scope_validation":[]}} +{"pr":8130,"title":"[Bugfix] Print text alongside audio in multimodal chat example","body":"Fixes #8105.\n\nNon-streaming chat completions can contain both text and audio in the same choice after #7879. The multimodal example client's `if audio / elif content` saved the audio but silently skipped the text. Process the two fields independently so both outputs are available to users and the existing example tests.\n\nValidation uses the unchanged nightly command with real `Qwen/Qwen3-Omni-30B-A3B-Instruct` weights and the example image/video inputs:\n\n```sh\npytest -s -v tests/examples/ -m \"full_model and omni and H100 and B200 and cards_2\" --run-level \"full_model\"\n```\n\n- Issue commit `57d3e9fdf`: reproduced all three reported failures; four other tests passed.\n- Rebased main `24b21278c`: the same three failures; four other tests passed.\n- Fixed branch: all 7 passed, including image/video text+audio, text-only, audio-only, explicit audio+text, curl, and streaming. The four audio/text similarity checks were 0.9923, 1.0000, 0.9869, and 0.9894 (unchanged threshold: >0.8).\n- Related audio-choice merge and modality-validation tests: 12 passed.\n- All applicable pre-commit hooks passed; the SPDX hook added the missing file header.\n\nLocal runs use vLLM 0.30.0, `HF_HOME=/workspace/models`","changed_files":["examples/online_serving/openai_chat_completion_client_for_multimodal_generation.py"],"expected":{"status":"description_unrouted","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[],"scope_validation":[]}} +{"pr":8125,"title":"[CI/Build] Initialize RoPE frequencies in MiniMax H3 TeaCache tests","body":"## Purpose\n\nFix the intermittent `TestMiniMaxH3Extractor.test_extractor_matches_native_forward` failure in [Buildkite #16038](https://buildkite.com/vllm/vllm-omni/builds/16038/list?sid=01a0d295-1707-4ee5-89af-04d3118eb160&tab=output): all 16 video output elements fail `assert_close` with NaN differences.\n\nThe fixture constructs an H3 model without loading a checkpoint. Its fake linear layers are initialized, but `rope.inv_freq` is allocated with `torch.empty`, leaving it dependent on allocator contents. Nonfinite values propagate through RoPE into the outputs. Initialize the fixture's single inverse frequency to `1.0`, the correct value for one frequency per axis. Production loading and comparison tolerances are unchanged.\n\n## Test Plan\n\n**vLLM Version:** `0.29.0+precompiled`; PyTorch `2.13.0+cu130`, Python 3.12.\n\n**vLLM-Omni Commit:** `3b02e5338ace4aafe9fec5c0fb7a1316e155c15a` (the failing CI commit).\n\nThe tests use CPU tensors and fake linear/attention layers; no model checkpoint is needed. Use an environment with the repository's test dependencies. The local environment lacks pytest-xdist, so the commands override addopts to omit `--dist=loadgroup`.\n\nDeterministic reproduction b","changed_files":["tests/diffusion/cache/test_teacache_extractors.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: override","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: checkpoint","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8118,"title":"[Bugfix] Align Ming-Image prompt templates and RNG, fix number of layer to decompose","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nThis PR fixed several issues and parities in Ming-Image pipeline:\r\n\r\nFor Ming-Image-0.1-Design:\r\n- Align Ming-Image RNG and CFG semantics with the reference inference pipeline.\r\n- Apply the Bailing generation prompt template for T2I and image-to-image requests.\r\n- Deprecate `steps` and `cfg` aliases in doc.\r\n\r\nFor Ming-Image-0.1-Design-Layer:\r\n- `num_layers=6` does not work, still producing 2 layers (2 outputs); fixed the num_layers passing issue (root cause is the cls name lookup for mode repo without model index json and config json)\r\n- aligned resample method in VAE Image processor\r\n\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** 0.30.0\r\n\r\n**vLLM-Omni Commit:** 6daf5b30f\r\n\r\n## Test Result\r\n\r\n### `Ming-Image-0.1-Design` T2I\r\n\r\n| - | on main `6daf5b30f` | on current branch |\r\n| :--- | :--- | :--- |\r\n| four-season-cabin | \"four_seasons_cabin_prev\" | \"four_seasons_cabin\"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nCorrupt `audio_url` data is rejected by vLLM's shared audio decoder (`Invalid or corrupted audio data`) before model inference. Qwen3-Omni's thinker accepts chat audio, so the same `POST /v1/chat/completions` path does not need a separate MiniCPM-o 4.5 server.\r\n\r\nThis drops `tests/dfx/reliability/invalid_param_test/test_invalid_minicpmo_4_5_omni.py` and adds the case to the existing Qwen3-Omni chat invalid-input test as `invalid_audio_data_url`. The old assertion looked for `Invalid or unsupported audio file`, which no longer matches the decoder error. The case now expects HTTP 400 and `Invalid or corrupted audio data`, and it shares the module-scoped Qwen3-Omni server (`qwen3_omni_moe.yaml`, 2 GPUs) with the other chat invalid cases. Weekly CI already collects that server via `-m \"slow and H100 and cards_2\"`.\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** Not recorded. The GPU case was not run locally.\r\n\r\n**vLLM-Omni Commit:** e3b20f607c06bf2d03aacbf04c4c587373748433\r\n\r\nRequires 2x H100 and a cached `Qwen/Qwen3-Omni-30B-A3B-Instruct`.\r\n\r\nSame selection as the weekly 2-GPU invalid-parameter job:\r\n\r\n```bas","changed_files":["tests/dfx/reliability/invalid_param_test/test_invalid_minicpmo_4_5_omni.py","tests/dfx/reliability/invalid_param_test/test_invalid_omni_chat.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: yaml","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, http","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":8107,"title":"[CI/Build][NPU] Run HunyuanVideo-1.5 perf step via run_benchmark.py","body":"## Purpose\n\nFixes #8074.\n\nThe NPU nightly step **Diffusion X2V · HunyuanVideo-1.5 Perf Test** failed with pytest exit 5 (`collected 1 item / 1 deselected / 0 selected`). PR #7737 migrated `tests/dfx/perf/tests/test_hunyuanvideo15_t2v_vllm_omni.json` from the diffusion schema (`benchmark_params[].dataset`) to the omni-bench schema (`benchmark_params[].dataset_name`) and switched the CUDA nightly HunyuanVideo-1.5 step and the NPU Wan 2.2 step to `run_benchmark.py`, but missed this NPU step. It still invoked `run_diffusion_benchmark.py`, whose schema-based `is_diffusion_perf_config` filter (it does not look at the `mark` field) skipped both cases as omni-bench generation cases, so pytest selected 0 tests and Buildkite treated the empty selection as a failure.\n\n## Changes\n\n- `.buildkite/npu/test-npu-nightly.yml`: run the HunyuanVideo-1.5 perf step via `run_benchmark.py` with `BENCHMARK_DIR` and `tests/dfx/perf/results/*.json` artifact upload — same shape as the already-migrated NPU Wan 2.2 step and the fixed CUDA HunyuanVideo-1.5 step.\n- `tests/dfx/perf/tests/test_runner_metadata.py`: add `test_buildkite_perf_steps_use_matching_runner_schema`, which scans every `.buildkite` pipeline an","changed_files":[".buildkite/npu/test-npu-nightly.yml","tests/dfx/perf/tests/test_runner_metadata.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, pipeline","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8106,"title":"[BugFix][Cosmos3] Decode uploaded video paths in v2v preprocess","body":"\r\n## Purpose\r\n\r\nFixes #8073.\r\n\r\nNightly Diffusion X2V 2-GPU perf failed on `test_cosmos3_v2v_official_demo_2gpu-1280x720_frames189_steps4` with `AssertionError: Request failures exist` (3/3 requests). Same step **t2i / t2v / i2v passed**. This is a deterministic v2v input-path bug, not a timeout and not #7912 (`no_guardrails`).\r\n\r\n## Root cause\r\n\r\nVideo serving persists multipart uploads as `/tmp/vllm_omni_video_reference_*.mp4` and puts that **path list** in `multi_modal_data[\"video\"]` (the API keeps file paths for models that need the source file).\r\n\r\nCosmos3 `pre_process_func` then:\r\n\r\n1. `_video_payload_to_frames()` saw a `list` and returned it unchanged.\r\n2. With no image, it took `raw_video_frames[0]` (the `.mp4` path) through `_pil_to_rgb`.\r\n3. `_pil_to_rgb` treated `str` as an image and called `PIL.Image.open(path)`.\r\n\r\nPIL cannot open mp4 → `UnidentifiedImageError: cannot identify image file '/tmp/vllm_omni_video_reference_….mp4'`.\r\n\r\nDecoded PIL frame lists (existing unit tests) already worked. The nightly v2v client goes through the upload/path path, so only v2v failed.\r\n\r\n## Experiments\r\n\r\n- Confirmed traceback on `pipeline_cosmos3.py` `_pil_to_rgb` / `pre_process_func`","changed_files":["tests/diffusion/models/cosmos3/test_cosmos3_pipeline.py","vllm_omni/diffusion/models/cosmos3/pipeline_cosmos3.py","vllm_omni/diffusion/models/cosmos3/transfer.py","vllm_omni/diffusion/utils/video_decode.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:cosmos3","path":"repos/vllm-omni/models/cosmos3/rules.md","reason":"title/body model: cosmos3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/cosmos3/pipeline_cosmos3.py, vllm_omni/diffusion/models/cosmos3/transfer.py, vllm_omni/diffusion/utils/video_decode.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/cosmos3/pipeline_cosmos3.py","vllm_omni/diffusion/models/cosmos3/transfer.py","vllm_omni/diffusion/utils/video_decode.py"],"selected_from_description":true}]}} +{"pr":8103,"title":"[Bugfix][Diffusion] Skip AllGather DLO weight restore on worker shutdown","body":"## Purpose\n\nFixes #8081.\n\n**What broke:** after successful generation, closing a pipeline that uses AllGather DLO left diffusion workers in cleanup. After the 15 s `_WORKER_SHUTDOWN_GRACE_S`, the executor sent SIGTERM (logged as `SystemExit: 143` inside `restore_tensor_storage(..., device=\"cpu\")`), then SIGKILL. The run also logged a 30 s orchestrator timeout and leaked semaphores.\n\n**Root cause:** `DiffusionWorker.shutdown()` called `offload_backend.disable()`. For AllGather DLO, `disable()` restores every block's master weights via `restore_next_block_to_cpu()`: per block, an AllGather, a `synchronize`, and a D2H copy into freshly allocated pageable CPU memory. Every rank therefore rebuilds the **full unsharded DiT** on the host, which is 62 GB per rank for MiniMax-H3. That restore exists only so a later `enable()` can re-shard, and nothing uses it at process exit.\n\nMeasured on 4× H200 (see the repro below):\n- 62 GB model: `disable()` took **48.6 s** on every rank, about 1.3 GB/s per rank.\n- 8 GB breakdown: AllGather + H2D about 0.1 s; D2H restore about 2.4 s (96%).\n- Timings are even across ranks, so this is slow host transfer, not a stalled collective.\n\n**Fix:**\n- Add `OffloadB","changed_files":["tests/diffusion/offloader/test_distributed_layerwise_backend.py","tests/diffusion/test_worker_wrapper_base.py","vllm_omni/diffusion/offloader/base.py","vllm_omni/diffusion/offloader/distributed_layerwise_backend.py","vllm_omni/diffusion/worker/diffusion_worker.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, dit","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/offloader/base.py","vllm_omni/diffusion/offloader/distributed_layerwise_backend.py","vllm_omni/diffusion/worker/diffusion_worker.py"],"selected_from_description":true}]}} +{"pr":8101,"title":"[Bugfix][Cosmos3] Resolve per-component transformer quantization config","body":"## Purpose\n\nFixes #7323.\n\nBased on / carries forward #7330 by @Oliver7th (credited as a co-author). Release needs this fix; opening a fresh PR from current `main` with the core change plus review follow-ups, rather than cherry-picking the original branch.\n\nCosmos3 failed to initialize when quantization used vLLM-Omni's per-component syntax:\n\n```json\n{\n \"transformer\": {\n \"method\": \"mxfp8\"\n }\n}\n```\n\n`Cosmos3VFMTransformer` previously passed that `ComponentQuantizationConfig` router straight into vLLM linear layers. Those layers announce prefixes such as `language_model.*` / `gen_layers.*`, which do not match the outer `transformer` key, so `resolve()` returned `None` and init raised:\n\n```text\nValueError: All linear layers should support quant method.\n```\n\nFlat `{\"method\": \"mxfp8\"}` already worked.\n\n## What changed\n\n- Unwrap `ComponentQuantizationConfig` to pathway leaf configs before constructing `language_model` / `gen_layers` (same leaf pattern as Flux2 / MiniMax / Boogu).\n- Keep historical `language_model` / `gen_layers` as exact-key overlays (including explicit `None`). Nested keys under those roots keep a pathway-scoped router so longest-prefix matching still selects a laye","changed_files":["tests/diffusion/models/cosmos3/test_cosmos3_transformer.py","vllm_omni/diffusion/models/cosmos3/transformer_cosmos3.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:cosmos3","path":"repos/vllm-omni/models/cosmos3/rules.md","reason":"title/body model: cosmos3","quick_map_status":"ok","read_required":false},{"owner":"model:flux2","path":"repos/vllm-omni/models/flux2/rules.md","reason":"title/body model: flux2","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/cosmos3/transformer_cosmos3.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/cosmos3/transformer_cosmos3.py"],"selected_from_description":false}]}} +{"pr":8095,"title":"[Bugfix][MammothModa2] Stop allocating AR KV cache for the replaced language model","body":"## Purpose\n\nThe MammothModa2 AR stage sizes and allocates its KV cache for 56 attention layers instead of 28: the Qwen-VL parent `__init__` builds a stock language model, MammothModa2 replaces it, and the replaced model's attention layers stay registered in `static_forward_context`. This halves AR KV capacity, and with `enforce_eager: false` the replaced model also stays in GPU memory. Related to #7075.\n\n## Propose\n\n1. `_drop_parent_language_model` removes the parent language model's attention layers from `static_forward_context` (matched by module identity), releases its `torch.compile` bytecode hook, and frees it before the MammothModa2 language model is built. Both AR classes call it (Preview on Qwen2.5-VL, Dev on Qwen3-VL).\n2. It raises if the parent registered no attention layers or if any `*.language_model.*` entry is left, so an upstream layout change fails at startup.\n3. Tests run the real `__init__` of both AR classes with the parent build stubbed: only the replacement's layers stay registered (default and `mm_encoder_only`), the parent model is freed, a compiled parent's hook is released, and an unexpected registry raises.\n\n## Test Plan\n\n**vLLM Version:** 0.30.0\n\n**vLLM-O","changed_files":["tests/model_executor/models/mammoth_moda2/test_mammoth_moda2_ar_kv_layers.py","vllm_omni/model_executor/models/mammoth_moda2/mammoth_moda2.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:mammoth-moda2","path":"repos/vllm-omni/models/mammoth-moda2/rules.md","reason":"title/body model: mammoth-moda2","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: registry, default","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/mammoth_moda2/mammoth_moda2.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/mammoth_moda2/mammoth_moda2.py"],"selected_from_description":false}]}} +{"pr":8083,"title":"[Bugfix][MammothModa2] Keep AR layer 0 in BF16 in the FP8 KV preset","body":"## Purpose\n\nWith all 28 AR layers in FP8, `mammoth_moda2_fp8_kv.yaml` stops following the text-to-image prompt on A800: 1 of 6 studio-cat images and 0 of 6 Samoyed images keep the prompt.\n\n## Propose\n\n1. Keep the KV cache of AR decoder layer 0 in BF16 with `kv_cache_dtype_skip_layers: [\"0\"]`; the other 27 layers stay FP8.\n2. Extend `test_mammoth_fp8_kv_deploy_projects_only_ar_stage` to check the skip list on stage 0 through the legacy and typed projections, and its absence on stage 1.\n3. Add the A800 results to the recipe and scope the H800 results to all-layer FP8.\n\n## Test Plan\n\n```bash\npytest tests/engine/test_stage_engine_args.py\n\npython examples/offline_inference/text_to_image/text_to_image.py \\\n --model ./MammothModa2-Preview --deploy-config vllm_omni/deploy/mammoth_moda2_fp8_kv.yaml \\\n --prompt \"$PROMPT\" --height 1024 --width 1024 --seed $SEED \\\n --guidance-scale 4.0 --num-inference-steps 50 --extra-body '{\"cfg_range\": [0.0, 1.0]}'\n```\n\n`$PROMPT` is `A single tabby cat sitting upright, centered, facing the camera, full body in frame, plain light grey studio backdrop, soft even lighting.` or `Peephole view of a fluffy white Samoyed standing on a wooden front porch, its fac","changed_files":["recipes/MammothModa2/MammothModa2.md","tests/engine/test_stage_engine_args.py","vllm_omni/deploy/mammoth_moda2_fp8_kv.yaml"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:mammoth-moda2","path":"repos/vllm-omni/models/mammoth-moda2/rules.md","reason":"title/body model: mammoth-moda2","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, yaml, deploy","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: text to image","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/deploy/mammoth_moda2_fp8_kv.yaml"],"selected_from_description":true}]}} +{"pr":8082,"title":"[Bugfix] Reclaim aborted chunk SHM and consumed connector state","body":"## Purpose\n\nFixes #6352.\n\nCurrent main already fences aborted sender work, but unconsumed SHM and lock files remain after abort. Sender `_pending_keys` also retains keys consumed by a different process, and receiver cancellation tombstones accumulate after requests finish.\n\n- Queue cleanup of exact chunk keys on the existing save thread after in-flight work drains. Preserve successful terminal payloads for downstream consumption and order cleanup before external-ID reuse.\n- Reap consumed producer keys with a bounded round-robin scan, including when the sender is idle.\n- Use existing receiver registration identity to reject stale work without persistent cancellation tombstones. Publish the request-ID mapping inside that same protected path.\n- Remove the lock file after SHM has been consumed even if deserialization fails.\n\nThis carries forward the consumed-SHM lock-file cleanup from #4349 by @gagandhakrey, credited as a co-author. Request ownership is determined by exact keys generated by the adapter; underscore-prefix matching is not used.\n\n## Test Plan\n\n**vLLM Version:** 0.30.0; Python 3.12; torch 2.13.0+cu130; transformers 5.14.1. CPU tests use `CUDA_VISIBLE_DEVICES=\"\"`; model ser","changed_files":["tests/distributed/omni_connectors/test_chunk_transfer_adapter.py","tests/distributed/omni_connectors/test_shm_connector.py","vllm_omni/distributed/omni_connectors/connectors/base.py","vllm_omni/distributed/omni_connectors/connectors/shm_connector.py","vllm_omni/distributed/omni_connectors/transfer_adapter/base.py","vllm_omni/distributed/omni_connectors/transfer_adapter/chunk_transfer_adapter.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: queue","quick_map_status":"ok","read_required":false},{"owner":"distributed","path":"repos/vllm-omni/components/distributed/_index.md","reason":"changed files: vllm_omni/distributed/omni_connectors/connectors/base.py, vllm_omni/distributed/omni_connectors/connectors/shm_connector.py, vllm_omni/distributed/omni_connectors/transfer_adapter/base.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"distributed","changed_files":["vllm_omni/distributed/omni_connectors/connectors/base.py","vllm_omni/distributed/omni_connectors/connectors/shm_connector.py","vllm_omni/distributed/omni_connectors/transfer_adapter/base.py","vllm_omni/distributed/omni_connectors/transfer_adapter/chunk_transfer_adapter.py"],"selected_from_description":false}]}} +{"pr":8070,"title":"[Doc] Add Qwen-Image-2.1 recipe to recipes index","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:**\r\n\r\n**vLLM-Omni Commit:**\r\n\r\n## Test Result\r\n\r\n**BEFORE SUBMITTING:** read [CONTRIBUTING.md](https://github.com/vllm-project/vllm-omni/blob/main/CONTRIBUTING.md) and run the [precheck-pr skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) with the code agent for a self-check against project conventions.\r\n(anything written below this line will be removed by GitHub Actions)\r\n","changed_files":["recipes/README.md"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8065,"title":"[Bugfix] Fix Qwen3-TTS prefill probe on MRv2 runner","body":"## Purpose\n\nFixes #8049.\n\nThe Qwen3-TTS chunked-prefill phase-contract probe still accessed the legacy runner's `_talker_mtp_forward`. The default MRv2 `OmniARModelRunner` runs MTP through `model_state._run_batched_mtp`, so the probe failed during initialization before checking the audio result.\n\nUpdate the test-only worker extension to observe the active runner's preprocess and MTP paths. Preserve the legacy path, and verify on MRv2 that prefill rows do not enter MTP and that the one-token prefill tail embedding reaches the next stage unchanged.\n\nThe first two Read the Docs builds for this PR failed because three external Read the Docs inventories returned HTTP 429. Disable those optional inventories in `mkdocs.yml` so strict documentation builds no longer depend on rate-limited hosts.\n\n## Test Plan\n\n**vLLM Version:** 0.30.0\n\n**vLLM-Omni Commit:** `6a14af3be` (based on `57d3e9fdf82c06094ee0c0b39c79d24b247fe8c3`)\n\n- H200 physical GPU 2: `tests/e2e/features/prefill_phase/test_chunked_prefill_tail.py::test_one_token_prefill_tail_matches_unchunked_audio` with `-m 'advanced_model and cuda' --run-level advanced_model`.\n- `pre-commit run --files mkdocs.yml tests/e2e/features/prefill_phas","changed_files":["mkdocs.yml","tests/e2e/features/prefill_phase/worker_extension.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: http","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":8057,"title":"[CI/Build] Fix Qwen3-Omni Talker benchmark workload","body":"## Purpose\n\nThe Qwen3-Omni non-async random performance test fixes Thinker output at 900 tokens\nbut lets Talker stop naturally. Different audio lengths change both the workload\nand the fixed Thinker overhead per generated audio second, making RTF comparisons\nhard to interpret. Related to #7915.\n\nThis change makes the five random concurrency points request exactly 1536 Talker\noutput tokens through the existing `sampling_params_list` API. It preserves the\nTalker temperature/top-k/repetition penalty, fixes the request seed, and explicitly\nsets benchmark seed and input-length range. Warmups retain the existing\n`max(2, concurrency)` request count. Code2Wav retains server defaults. CUDA and NPU share this configuration; async/random-mm tests and\nproduction/model/API defaults are unchanged.\n\nBenchmark results retain ordered `request_stage_metrics` for formal requests,\nincluding missing entries, without audio bodies. The runner rejects failed\nrequests, missing metrics and mismatched output counts for stages configured with\n`min_tokens == max_tokens`. Tests cover exact lengths, premature termination,\nmissing metrics, compatibility with nonfixed workloads, warmup exclusion and\nout-of-order c","changed_files":["tests/benchmarks/patch/test_patch.py","tests/dfx/perf/scripts/run_benchmark.py","tests/dfx/perf/tests/test_qwen3_omni_no_async_chunk.json","tests/dfx/perf/tests/test_runner_metadata.py","vllm_omni/benchmarks/patch/patch.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: sampling","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8047,"title":"[BugFix][Diffusion] Scope Wan RMSNorm patch to NPU","body":"Fixes #8030.\n\nImporting the Wan 2.2 package replaced diffusers' `WanRMS_norm` globally, including on CPU and CUDA. The Wan VAE fastpath only supports the original diffusers class, so its normalization optimization was skipped and six tests failed depending on import order. Apply the automatic replacement only on NPU, restore module aliases after tests that explicitly exercise the patch, and correct the moved `RMSNormVAE` test import.\n\nValidation: reproduced the reported six failures on upstream `75570ee3a` with diffusers 0.40.0; both affected test files now pass together (79 tests). `ruff check`, `ruff format --check`, and `git diff --check` pass. The reproduction host has vLLM 0.29.0; the reported CI used 0.30.0.\n","changed_files":["tests/diffusion/distributed/test_wan_vae_fastpath_install.py","tests/diffusion/models/wan2_2/test_wan22_quant_config_propagation.py","vllm_omni/diffusion/models/wan2_2/__init__.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, vae","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/wan2_2/__init__.py"],"selected_from_description":true}]}} +{"pr":8044,"title":"[Bugfix] Validate Breeze PCM speech content and calibrate HNR guard","body":"## Purpose\n\nFixes #8033. The full-length English Breeze PCM test reported HNR -0.11 dB against a 0 dB floor on L4. PCM responses also skipped the shared transcript check, so HNR alone was deciding speech quality. Set this fixed fixture's HNR floor to -1 dB and validate its spoken text by wrapping PCM as 24 kHz mono int16 WAV for Whisper, including the existing stronger-ASR fallback. The shared default floor remains 1 dB. Silence and constant signals now fail even a nonpositive HNR floor, and HNR/transcription use the same declared PCM rate.\n\nThis changes test validation only. The targeted L4 rerun reproduced -0.11 dB and Whisper transcribed the requested sentence correctly. The previous 0 dB floor would reject that output; the revised test checks both the HNR guard and spoken content. This establishes the result for the fixed fixture, not a general audio-quality guarantee.\n\n## Test Plan\n\n**vLLM Version:** 0.30.0\n\n**vLLM-Omni Commit:** based on 75570ee3a6572a7dc7a9463ada35a9a11c367993\n\nRun the shared assertion regressions and all five full-length Breeze cases on one L4 with the model and Whisper weights available:\n\n```bash\npytest -s -v tests/helpers/tests/test_assertions.py -m \"core","changed_files":["tests/e2e/online_serving/test_breeze_tts_2.py","tests/helpers/assertions.py","tests/helpers/client.py","tests/helpers/tests/test_assertions.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":8032,"title":"[CI/Build] Move Breeze-TTS-2 voice design e2e to weekly","body":"\nPLEASE FILL IN THE PR DESCRIPTION HERE.\n\n## Purpose\n\nMove Breeze-TTS-2 voice-design online serving coverage out of PR-ready and post-merge CI.\n\n- Drop the `TTS · Breeze-TTS-2 Voice Design` steps from `.buildkite/cuda/test-ready.yml` and `.buildkite/cuda/test-merge.yml`.\n- In `tests/e2e/online_serving/test_breeze_tts_2.py`, replace the module-level `core_model` and `advanced_model` marks with `slow`. The tests stay `tts` and `@hardware_test(res={\"cuda\": \"L4\"}, num_cards=1)`.\n- Weekly `TTS · L4` already collects `tests/e2e/` with `-m \"slow and L4 and tts\"` and `--run-level full_model`, so these cases run there (`max_new_tokens=250` and the deep speech checks). Nightly still requires `full_model`, so it does not pick them up.\n\n## Test Plan\n\nNo new tests. Marker-only change; existing cases are selected by the weekly sweep:\n\n```bash\npytest -s -v tests/e2e/online_serving/test_breeze_tts_2.py -m \"slow and L4 and tts\" --run-level=full_model\n```\n\n**vLLM Version:** 0.30.0\n\n**vLLM-Omni Commit:** 4b3ff3ba1\n\n## Test Result\n\nNot run locally (needs 1x CUDA L4 and cached `BreezeBlue/Breeze-TTS-2` weights). Pending weekly `TTS · L4`.\n\n**BEFORE SUBMITTING:** read [CONTR","changed_files":[".buildkite/cuda/test-merge.yml",".buildkite/cuda/test-ready.yml","tests/e2e/online_serving/test_breeze_tts_2.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":8027,"title":"[NPU] upgrade to v0.30.0","body":"\r\n\r\n## Purpose\r\n\r\n- Update NPU Docker defaults and installation image examples to v0.30.0.\r\n\r\n- Align the NPU model runners with vLLM v0.30.0: forward `kv_cache_allocation_context` during KV cache initialization, use the runner's `_get_mamba_state_copy_funcs()`, and remove obsolete XD-RoPE branches.\r\n\r\n## Test Plan\r\n\r\nValidated on Ascend A3 with `quay.nju.edu.cn/atlas-ci/vllm-ascend:v0.30.0-a3`\r\n\r\n\r\n- [x] Qwen3-TTS online E2E: `python -m pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py -m 'core_model and npu' --run-level core_model`\r\n- [x] Wan2.2 online E2E: `python -m pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k npu_i2v --run-level full_model`\r\n- [x] HunyuanImage-3.0 E2E: `python -m pytest -s -v tests/e2e/accuracy/test_hunyuan_image3_pixel_accuracy.py -m \"full_model and A3\" --run-level full_model`\r\n\r\n**vLLM Version:** v0.30.0\r\n\r\n**vLLM-Omni Commit:** main\r\n\r\n## Test Result\r\n\r\n```text\r\nQwen3-TTS CustomVoice: 2 passed, 1 deselected\r\nWan2.2 NPU I2V: 2 passed, 21 deselected, 15 warnings in 278.53s (0:04:38)\r\ntencent/HunyuanImage-3.0-Instruct: 2 passed, 16 warnings in 415.06s (0:06:55)\r\n```\r\n\r\n**BEFORE SUBMITTING:** re","changed_files":["docker/Dockerfile.npu","docker/Dockerfile.npu.ci","docs/getting_started/installation/npu/npu.inc.md","vllm_omni/platforms/npu/worker/npu_ar_model_runner.py","vllm_omni/platforms/npu/worker/npu_generation_model_runner.py","vllm_omni/platforms/npu/worker/npu_model_runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:hunyuan-image3","path":"repos/vllm-omni/models/hunyuan-image3/rules.md","reason":"title/body model: hunyuan-image3","quick_map_status":"ok","read_required":false},{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":8024,"title":"[Misc] remove legacy full_duplex alias for auto_response","body":"## Purpose\r\n\r\nKeep auto_response as the sole runtime switch for automatic duplex responses.\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** 0.30.0\r\n\r\n**vLLM-Omni Commit:** f6f748edf43bf2c8d8c9472117be0f786ec2c62a\r\n\r\ngrep for full_duplex\r\n\r\n## Test Result\r\n\r\nno reference remains in either code or doc.\r\n\r\n","changed_files":["vllm_omni/engine/duplex/session/emitter.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/serving_adapter.py","vllm_omni/model_executor/models/qwen3_omni/duplex/plugin.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: response","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/nemotron_voicechat/duplex/serving_adapter.py, vllm_omni/model_executor/models/qwen3_omni/duplex/plugin.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/nemotron_voicechat/duplex/serving_adapter.py","vllm_omni/model_executor/models/qwen3_omni/duplex/plugin.py"],"selected_from_description":false}]}} +{"pr":8021,"title":"[New Model] Ming-Image (inclusionAI/Ming-Image-0.1-Design) Support","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nSupport Ming-Image by [inclusionAI](https://huggingface.co/inclusionAI) :\r\n1. [inclusionAI/Ming-Image-0.1-Design](https://huggingface.co/inclusionAI/Ming-Image-0.1-Design)\r\n2. [inclusionAI/Ming-Image-0.1-Design-Layer](https://huggingface.co/inclusionAI/Ming-Image-0.1-Design-Layer)\r\n\r\n### Supported Features\r\n\r\n- [x] (design) Text-to-image generation\r\n- [x] (design) Single-reference image editing\r\n- [x] (design-layer) RGBA layer decomposition\r\n- [x] Stage 1 regional CUDA Graph\r\n\r\n\r\n### TODO (draft)\r\n\r\n- [ ] (wip) Update prompt refiner guidance and example inputs @yuanheng-zhao \r\n- [ ] Check request batching / continuous batching\r\n- [ ] Quantization (to validate)\r\n- [ ] Offloading (e.g., for specific devices)\r\n- [ ] Parallelism, validate and find out appropriate settings.\r\n- [ ] Kernel perspectives\r\n\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** 0.30.0\r\n\r\n**vLLM-Omni Commit:** 59db6a428\r\n\r\n## Test Result\r\n\r\nExample outputs (resized to view easily)\r\n\r\n\"four_seasons_cabin\"\r\n= 2048` (same helper as Boogu: `VLLM_OMNI_FUSED_QK_NORM_ROPE_MIN_TOKENS`, `0` = always fuse) instead of reverting #5931.\r\n\r\n## CI cases from #7780\r\n\r\nAll failing rows are still `bench=512x512_steps20*` (short seq). From the issue body and [congw729's 2026-09-21 update](https://github.com/vllm-project/vllm-omni/issues/7780#issuecomment-5769126209) (still failing 5 days; adds high_concurrency):\r\n\r\n| Test | Config | Metrics failing on H100 (vs baseline) |\r\n| --- | --- | --- |\r\n| `test_qwen_image_single_device` | `512x512_steps20`, c=1, n=10 | `e2e_latency_ms` ~−26%, `throughput_qps` ~−20% |\r\n| `test_qwen_image_single_device_step_execution` | `512x512_steps20`, c=1, n=10 | `e2e_latency_ms` ~−21%, `throughput_qps` ~−18% |\r\n| `test_qwen_image_single_device_step_execution` | `512x512_steps20_h","changed_files":["tests/diffusion/models/qwen_image/test_qwen_image_fused_qk_norm_rope.py","vllm_omni/diffusion/models/qwen_image/qwen_image_transformer.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image/qwen_image_transformer.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image/qwen_image_transformer.py"],"selected_from_description":false}]}} +{"pr":7964,"title":"[CI] Bump the public env var snapshot count after #6848 landed","body":"## Purpose\n\n`tests/config/test_environment_variables.py::test_inventory_matches_reviewed_snapshot_counts` fails on `main` since `d4570afa` (#6848): the inventory now holds 30 public `VLLM_OMNI_*` variables while the pinned snapshot says 29, and the test runs in `Simple · Other Test` on every PR lane.\n\n#6848 registered `VLLM_OMNI_SPEAKER_REGISTRATION_POLICY` and moved the pinned count from 28 to 29 on its branch. After that branch point `main` had independently moved the same line from 28 to 29 for another variable, so the three-way merge kept 29 for the identical hunk while the inventory gained a 30th entry. This pins the reviewed count at 30; no inventory or production change.\n\n## Test Plan\n\n```bash\npytest tests/config/test_environment_variables.py -q\n```\n\n**vLLM Version:** 0.29.0\n\n**vLLM-Omni Commit:** d4570afa\n\n## Test Result\n\nCounted the `_PUBLIC_OMNI` tuple on `main` at `d4570afa`: 30 entries, matching the new pin. The failing CI assertion (build 15829) reports `PUBLIC_OMNI: 30 != 29` with the other five categories identical.\n","changed_files":["tests/config/test_environment_variables.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7963,"title":"[CI/Build][MiniCPM-o] Gate Seed-TTS WER over chat completions instead of the native duplex path","body":"## Purpose\r\n\r\nFix the two failing MiniCPM-o 4.5 nightly accuracy tests (Buildkite build 15686, commit 9f552135):\r\n\r\n```\r\nFAILED test_minicpmo_4_5_duplex_seed_tts_wer_bench[three-stage-single-gpu] WER 0.0797 > 0.05\r\nFAILED test_minicpmo_4_5_duplex_seed_tts_wer_bench[three-stage-single-gpu-bf16-attention-cache] WER 0.0795 > 0.05\r\n```\r\n\r\n**Why the backing API changes to `/v1/chat/completions`:** the turn-based path is gone from the new full-duplex session, but the original test was calibrated for turn-based mode.\r\n\r\n- Before #7413 (Unified Full-duplex Framework) the test's client drove `/v1/realtime` with `conversation.item.create`. The server ran that through the turn-based whole-utterance Talker, the same path `/v1/chat/completions` uses, and the 0.05 gate was set on that path (WER 0.023).\r\n- The unified duplex session has no turn-based path over `/v1/realtime` any more. #7413 rewrote the client to seed `duplex_initial_user_text` and stream silence, so since then the test has exercised the native per-unit path for the first time. The gate was never calibrated for that path and it cannot meet it:\r\n\r\n| Path, same 200 turns (50 sessions x 4) | Mean WER |\r\n|---|---|","changed_files":["tests/e2e/accuracy/minicpmo_4_5/test_minicpmo_4_5.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7961,"title":"[Bugfix][XPU] Enable LTX-2 family on XPU","body":"\r\n\r\nTwo defects stop every LTX-2 class checkpoint from running on a 32 GiB Intel\r\nXPU card.\r\n\r\n## 1. The text encoder is never declared offloadable\r\n\r\n`LTX2Pipeline` declares `_layerwise_offload_blocks_attrs` for its DiT but no\r\n`_offload_plan`, so the layerwise backends have no block path for the Gemma\r\ntext encoder and it stays resident next to the DiT. On `Lightricks/LTX-2` that\r\nencoder is 48.07 GiB in bf16, larger than the 35.16 GiB transformer it feeds.\r\n\r\nFix: declare the encoder's block path. It is duck-typed on the encoder exposing\r\n`model.language_model.layers`, so Gemma3 and Gemma4 are both covered.\r\n\r\nPeak memory on `Lightricks/LTX-2` drops from 30,069 MiB (OOM) to 18,979 MiB\r\nagainst a 32,650 MiB card.\r\n\r\n## 2. `_run_ltx_vocoder` relies on fp32 autocast, which only CUDA supports\r\n\r\nThe BWE path casts its input with `.float()` and expects\r\n`torch.autocast(dtype=torch.float32)` to lift the bf16 weights to match. XPU and\r\nCPU autocast accept only fp16/bf16 and degrade silently, so fp32 activations\r\nmeet bf16 weights.\r\n\r\nFix: extend the existing MPS branch, which already materializes fp32 weights for\r\nthis case, to the other backends whose autocast lacks fp32.\r\n\r\nOnly chec","changed_files":["vllm_omni/diffusion/models/ltx2/ltx2_components.py","vllm_omni/diffusion/models/ltx2/ltx2_runtime.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: checkpoint","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: dit","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/ltx2/ltx2_components.py","vllm_omni/diffusion/models/ltx2/ltx2_runtime.py"],"selected_from_description":true}]}} +{"pr":7959,"title":"[Bugfix] Reject stray <|AUDIO|> placeholder with use_audio_in_video in Qwen2.5-Omni","body":"## Purpose\n\nRunning the Qwen2.5-Omni offline example with `--query-type use_audio_in_video` kills the thinker engine on current `main`:\n\n```text\nFile \".../qwen2_5_omni/qwen2_5_omni.py\", line 489, in get_mrope_input_positions\n audio_seqlen = audio_seqlens[audio_idx]\nIndexError: list index out of range\n...\nvllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue.\n```\n\n**Repro (before this PR):**\n\n```bash\ncd examples/offline_inference/qwen2_5_omni\npython end2end.py --model Qwen/Qwen2.5-Omni-3B --query-type use_audio_in_video\n```\n\n**Root cause.** The example prompt contains `<|vision_bos|><|VIDEO|><|vision_eos|>` *plus* a separate `<|audio_bos|><|AUDIO|><|audio_eos|>`. With `use_audio_in_video=True`, `_maybe_apply_prompt_updates` drops the `audio` prompt updates and derives the audio placeholder from the video placeholder (the audio is interleaved into the video span). The standalone `<|AUDIO|>` token is therefore left unexpanded with no audio item behind it. It passes input processing, reaches the engine, and `get_mrope_input_positions` indexes past the end of `audio_feature_lengths`, which takes down the whole stage instead of failing the one request.\n\n**Fix.**\n\n","changed_files":["examples/offline_inference/qwen2_5_omni/end2end.py","tests/model_executor/models/qwen2_5_omni/test_per_video_audio_mask.py","vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py"],"selected_from_description":false}]}} +{"pr":7953,"title":"[Bugfix] Restore GLM-Image unshifted timesteps after set_timesteps","body":"## Summary\n\nFixes #7951.\n\n`diffusers` 0.40 `FlowMatchEulerDiscreteScheduler.set_timesteps` overwrites caller-supplied timesteps with `sigmas * num_train_timesteps`. GLM-Image integrates on resolution-shifted sigmas but conditions the DiT on the unshifted timesteps it was trained with, so the overwrite produced magenta/grainy images after the #6459 pin bump.\n\nRestore the provided unshifted timesteps after `set_timesteps` in GLM-Image's `retrieve_timesteps` (both-args path). Shifted sigmas still drive the Euler update.\n\n## Test plan\n\n- [x] CPU unit test `tests/diffusion/models/glm_image/test_glm_image_retrieve_timesteps.py` (1 passed)\n- [x] `ruff check` / `ruff format --check` on changed files\n- [ ] No GPU image render in this environment","changed_files":["tests/diffusion/models/glm_image/test_glm_image_retrieve_timesteps.py","vllm_omni/diffusion/models/glm_image/pipeline_glm_image.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, dit","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/glm_image/pipeline_glm_image.py"],"selected_from_description":true}]}} +{"pr":7930,"title":"[Model] Default Qwen3-TTS to experimental Model Runner V2","body":"## Summary\n\nMake the bundled Qwen3-TTS deploy profile select Model Runner V2 on CUDA, and document MRV2 as experimental with an explicit opt-out. Follow-up to #7781, which enabled MRV2 as opt-in only.\n\n- `vllm_omni/deploy/qwen3_tts.yaml`: `model_runner: v2`, stage-0 talker prefill bound to 512 tokens (the validated basic MRV2 profile), and `platforms:` sections that pin V1 on NPU, XPU, ROCm and MUSA.\n- `qwen3_tts_mrv2.yaml`: kept as an explicit MRV2 profile; header updated because the base file now selects V2 as well. Removing the now-redundant profile can be a separate cleanup.\n- Config tests: the \"default profiles are explicit opt-in\" test is split — `qwen3_tts_high_concurrency.yaml` stays V1 with its opt-in MRV2 sibling, and a new test pins the Qwen3-TTS default plus per-platform fallbacks.\n- Docs: `docs/configuration/stage_configs.md` and `recipes/Qwen/Qwen3-TTS.md` note the experimental default and how to opt out.\n\n## Scope\n\nQwen3-TTS only. Qwen3-Omni and MOSS keep V1; their opt-in MRV2 profiles are untouched, and no other model family is affected.\n\n## Testing\n\nStatic checks passed locally: YAML validation, a standalone simulation of config resolution (CUDA → v2; NPU/XPU/ROCm/","changed_files":["docs/configuration/stage_configs.md","recipes/Qwen/Qwen3-TTS.md","tests/config/test_omni_config.py","vllm_omni/deploy/qwen3_tts.yaml","vllm_omni/deploy/qwen3_tts_mrv2.yaml"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, configuration, yaml","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/deploy/qwen3_tts.yaml","vllm_omni/deploy/qwen3_tts_mrv2.yaml"],"selected_from_description":true}]}} +{"pr":7928,"title":"[BugFix] Download Anima assets for README CI test","body":"## Issue\n\nFixes #7910\n\nThe Anima README test executes placeholder model paths, causing `HFValidationError` before model loading.\n\n## Fix\n\nDownload the Anima checkpoint and required components before running the test, then replace the placeholders with their local cache paths. Link the asset preparation recipe in the README.\n\n## Validation\n\n- Two CPU regression tests and all pre-commit checks passed.\n- Real downloads passed the previously failing path validation.\n\n","changed_files":["examples/offline_inference/text_to_image/README.md","tests/diffusion/test_diffusion_engine.py","tests/examples/offline_inference/test_text_to_image.py","vllm_omni/diffusion/diffusion_engine.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: checkpoint","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/diffusion_engine.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/diffusion_engine.py"],"selected_from_description":false}]}} +{"pr":7926,"title":"[Bugfix][Core] Fix stage initialization spawn-lock/device-lock inversion","body":"## Purpose\n\nFixes #7290.\n\n**Failure:** Shared-GPU stage initialization can stall until the device-lock timeout, logging `Timeout waiting for device ... initialization lock, proceeding anyway`. One stage holds a GPU flock and needs the spawn-environment lock; another holds the spawn-environment lock while waiting for that GPU flock.\n\n**Root cause:** Device-flock acquisition runs inside the process-wide spawn-environment lock. #7328 mitigates the original scheduling scenario by grouping overlapping devices, but does not remove the inverted lock ordering.\n\n**Fix:** Pass resolved physical devices explicitly to `acquire_device_locks` and wait outside the spawn-environment lock in the current common LLM launch path. Apply the same ordering to distributed-local diffusion, retaining scoped device visibility for configuration and process creation, lock ownership, and failure cleanup. Colocated diffusion initialization still runs under its scoped device environment. Remove the now-unused runtime scope wrappers.\n\nThis applies the approach discussed in #4895 to current main and also covers the diffusion follow-up discussed in #7372. The diffusion hazard is latent under current scheduling: inli","changed_files":["tests/engine/test_async_omni_engine_stage_init.py","vllm_omni/engine/stage_engine_startup.py","vllm_omni/engine/stage_init_utils.py","vllm_omni/engine/stage_runtime.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false},{"owner":"distributed","path":"repos/vllm-omni/components/distributed/_index.md","reason":"title/body: distributed","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7923,"title":"[Misc] Add portable MiniMax H3 skills","body":"## Purpose\r\n\r\nAdd a portable collection of the nine skills bundled with MiniMax H3:\r\n\r\n- preserve the H3 prompt-writing contract for T2VA, I2VA, FL2VA, L2VA, and Ref2VA;\r\n- adapt the eight MiniMax Hub-oriented creative workflows to file-based agents and available generation backends;\r\n- add a shared production contract for references, long-form clip assembly, source-audio handling, and output verification;\r\n- expose one canonical copy under `.agents/skills/` to Codex-compatible agents and through relative symlinks under `.claude/skills/`;\r\n- record the pinned upstream snapshot, attribution, unchanged files, and adaptation boundaries in `H3-SOURCES.md`.\r\n\r\nThe source snapshot is MiniMax-AI/MiniMax-H3 commit `d21241f0a4b3acbb34c97dae47fa417b7065e438`. That snapshot did not contain a `LICENSE`, `COPYING`, or `NOTICE` file, so the provenance document explicitly avoids asserting that this repository's license relicenses the upstream material. Maintainer guidance on redistribution is welcome.\r\n\r\n## Test Plan\r\n\r\nUse the 100.362-second song **《此生不换》** as an end-to-end MV example. The creative request is an ancient-costume love story featuring an adult East Asian woman and man, with narrati","changed_files":[".agents/skills/3d-animation-short-generator/SKILL.md",".agents/skills/3d-animation-short-generator/agents/openai.yaml",".agents/skills/3d-animation-short-generator/references/fallback-policy.md",".agents/skills/3d-animation-short-generator/references/model-selection.md",".agents/skills/3d-animation-short-generator/references/qc-checklist.md",".agents/skills/3d-animation-short-generator/references/shot-table-spec.md",".agents/skills/3d-animation-short-generator/references/storyboard-guidelines.md",".agents/skills/H3-SOURCES.md",".agents/skills/README.md",".agents/skills/brand-promo-video-generator/SKILL.md",".agents/skills/brand-promo-video-generator/agents/openai.yaml",".agents/skills/co-op-game-intro-generator/SKILL.md",".agents/skills/co-op-game-intro-generator/agents/openai.yaml",".agents/skills/co-op-game-intro-generator/references/h3-confirmation-image-template.md",".agents/skills/co-op-game-intro-generator/references/h3-video-prompt-template.md",".agents/skills/h3-prompt-writing/SKILL.md",".agents/skills/h3-prompt-writing/agents/openai.yaml",".agents/skills/h3-prompt-writing/references/base-format.md",".agents/skills/h3-prompt-writing/references/portable-workflow.md",".agents/skills/h3-prompt-writing/references/ref-format.md",".agents/skills/handdrawn-live-video-generator/SKILL.md",".agents/skills/handdrawn-live-video-generator/agents/openai.yaml",".agents/skills/minimalist-product-ad-generator/SKILL.md",".agents/skills/minimalist-product-ad-generator/agents/openai.yaml",".agents/skills/minimalist-product-ad-generator/references/product-direction.md",".agents/skills/music-video-subtitle-generator/SKILL.md",".agents/skills/music-video-subtitle-generator/agents/openai.yaml",".agents/skills/music-video-subtitle-generator/references/music-timeline.md",".agents/skills/paper-collage-explainer-generator/SKILL.md",".agents/skills/paper-collage-explainer-generator/agents/openai.yaml",".agents/skills/paper-collage-explainer-generator/references/collage-design.md",".agents/skills/papercraft-stop-motion-explainer/SKILL.md",".agents/skills/papercraft-stop-motion-explainer/agents/openai.yaml",".agents/skills/papercraft-stop-motion-explainer/references/paper-staging.md",".claude/skills/3d-animation-short-generator",".claude/skills/brand-promo-video-generator",".claude/skills/co-op-game-intro-generator",".claude/skills/h3-prompt-writing",".claude/skills/handdrawn-live-video-generator",".claude/skills/minimalist-product-ad-generator",".claude/skills/music-video-subtitle-generator",".claude/skills/paper-collage-explainer-generator",".claude/skills/papercraft-stop-motion-explainer",".claude/skills/readme.md"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7921,"title":"[Bugfix][Core] Preserve static KV staging inputs in AR-Diffusion","body":"## Purpose\r\n\r\nFix CUDA Graph input handling for AR-Diffusion history staging when `VLLM_OMNI_AR_DIFFUSION_KV_GATHER=1` and `reuse_history_staging=True`.\r\n\r\nThe KV manager marks its persistent staging tensors static, but `history_staging()` returns fresh prefix views. In the tested environment, the compiled block therefore sees unmarked mutated inputs and skips CUDA Graph capture. Return the manager-owned tensors across the compiled boundary and slice the active window inside the existing attention custom op. Gather directly into that window with `torch.index_select(..., out=...)`, removing an intermediate allocation and copy.\r\n\r\nThis extends the graph-input tensor-identity rule already applied to the flat KV pools in #7648 to its separate history-staging buffers. Regression tests cover tensor identity, repeated full/reused gathers, changed current KV, and untouched spare capacity. Existing optional-tensor checks and backend aliases are narrowed to pass changed-file mypy without adding exceptions.\r\n\r\n**Dependency:** this draft depends on #7648 at `72d6719cdeeb2bb8cdaf546dee1003c02f294d27`. The current comparison against `main` includes its prerequisite commits. The increment to revi","changed_files":["tests/diffusion/ar_diffusion/test_history_staging_gpu.py","tests/diffusion/ar_diffusion/test_paged_attention.py","vllm_omni/experimental/ar_diffusion/kv_cache/paged_attention.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7917,"title":"[Bugfix] Keep --no-guardrails out of diffusion stage config","body":"## What broke\n\nCosmos3's two-GPU startup fails before model initialization with\n`ValueError: Unknown diffusion config field(s) for stage 0: 'no_guardrails'`\nwhen `--no-guardrails` is supplied. Fixes #7912.\n\n## Root cause and fix\n\n`OmniServeCommand.cmd` maps this CLI-only alias to\n`model_config.guardrails=False`, but leaves `no_guardrails` in\n`TrackingNamespace.explicit_keys`. It is then forwarded as a diffusion\nstage override and rejected by the strict config validator.\n\nRemove the alias from `explicit_keys` while retaining `model_config`.\nThe regression test exercises the real CLI parser and config validator.\n\n## Validation\n\n- With the production change reverted, the regression test fails with\n the reported `ValueError`; with the fix, it passes.\n- RTX 4090: 71 targeted serve/config tests passed.\n- 2×RTX 3090: the same 71 tests passed. A configuration-only preflight\n using the nightly test's CLI arguments resolved `guardrails=False`,\n world size 2, and no leaked `no_guardrails` stage field.\n- Applicable pre-commit hooks and `git diff --check` passed.\n\nThese are logic/configuration checks on GPU hosts, not model-generation\nbenchmarks. The full Cosmos3 performance case was not run","changed_files":["tests/entrypoints/test_serve.py","vllm_omni/entrypoints/cli/serve.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:cosmos3","path":"repos/vllm-omni/models/cosmos3/rules.md","reason":"title/body model: cosmos3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, configuration, override","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7916,"title":"[Core][Diffusion] Add byte backpressure to chunk transfers","body":"## Summary\n\nPart of #6872.\n\n```diff\n VAE chunk -> uint8 preparation -> pinned D2H ring -> MP4 encoder\n- admit while a slot is free\n+ admit while a slot is free\n+ and pending bytes stay <= 256 MiB\n```\n\nThe existing two-slot ring bounded chunk count, but its memory footprint still\nscaled with resolution, batch size, and chunk length. This adds byte-aware\nadmission to the same ring: hitting either the slot or byte limit propagates\nbackpressure to the VAE producer.\n\nA native chunk larger than the byte budget may proceed only when the ring is\notherwise empty, avoiding a permanent wait for legal chunk shapes. Reservations\nare made under the ring lock and released with the slot's final reader. The\nsession drains its oldest in-flight lease before waiting so byte pressure cannot\nself-deadlock.\n\n## Evidence\n\n- **Before:** admission checked only for a free slot; two variable-sized chunks\n could both enter the ring regardless of their combined bytes.\n- **After:** focused tests cover byte-pressure waits with free slots, atomic byte\n accounting, oversized single-chunk progress, oldest-first session drainin","changed_files":["docs/serving/videos_api.md","tests/diffusion/utils/test_chunked_transfer.py","tests/diffusion/utils/test_chunked_video.py","vllm_omni/diffusion/utils/chunked_video.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: batch","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, vae","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/utils/chunked_video.py"],"selected_from_description":true}]}} +{"pr":7913,"title":"[Bugfix][MiniMax-H3] Fix Hopper keyframe and modulation precision","body":"## Purpose\n\nAddress the remaining MiniMax-H3 I2VA reference-accuracy failure on Hopper without relaxing the existing SSIM/PSNR thresholds or changing the reference assets.\n\n[Main build 15686](https://buildkite.com/vllm/vllm-omni/builds/15686/canvas?sid=01a0be45-a67b-4761-a3d9-3a615e18c50a&tab=output), at `9f5521351e4471d6538e74ca66cb63cde23b9140`, fails I2VA on H100 with **SSIM 0.947851 / PSNR 27.785708 dB** (required ≥0.97 / ≥34 dB). Ref2VA passes. The keyframe cuDNN context is still unchanged in the tested latest main, `ae3880f0e39df4ba7376481ae78d9269fc42e09c`.\n\nThe tested base's green [Build 15722](https://buildkite.com/vllm/vllm-omni/builds/15722) did **not** run the H3 I2VA/Ref2VA accuracy job (nightly upload was skipped). It is not evidence that this particular test now passes. The clean main local reproduction below is on B300, not H100.\n\nMain advanced during validation to `1b87115c9ede2d29702c847b6da98f9e854fa593` (#7449). Its only changes are ComfyUI app integration and its feature test. `vllm_omni/`, the accuracy/diffusion tests, test helpers, CI configuration, and dependencies are unchanged from the tested base `ae3880f0e`. The recorded runs remain pinned to that base a","changed_files":["tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py","tests/diffusion/models/minimax_h3/test_minimax_h3_modulation.py","vllm_omni/diffusion/layers/indexed_modulation.py","vllm_omni/diffusion/models/minimax_h3/vae.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/layers/indexed_modulation.py","vllm_omni/diffusion/models/minimax_h3/vae.py"],"selected_from_description":true}]}} +{"pr":7911,"title":"[Doc] Update vLLM-Omni WeChat QR code","body":"## Summary\n- Replace the vLLM-Omni WeChat developer group QR code with the provided current image.\n- Keep the existing JPEG asset path and dimensions (`1203x1727`).\n\n## Verification\n- Based on `upstream/main` at `ae3880f0e39df4ba7376481ae78d9269fc42e09c`\n- Changed file: `docs/assets/WeChat.jpg` only\n- `git diff --check`\n- `pre-commit run --show-diff-on-failure --files docs/assets/WeChat.jpg`\n- Output verified as RGB JPEG, `1203x1727`, with a valid JPEG header\n- Runtime tests: not applicable; documentation asset only","changed_files":["docs/assets/WeChat.jpg"],"expected":{"status":"description_unrouted","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[],"scope_validation":[]}} +{"pr":7909,"title":"[Bugfix][Model] Fix CoVo-Audio prompt processing and dummy loading","body":"\r\n\r\n## Purpose\r\n\r\nFixes #7619. \r\nFixes #7906.\r\n\r\nThis also unblocks the CoVo-Audio baseline validation for #7452.\r\n\r\nCoVo-Audio could fail in three consecutive startup and input-processing paths:\r\n\r\n1. `Token2WavDecoder` replaced PyTorch's standard `state_dict()` and `load_state_dict()` methods with incompatible signatures. During a `core_model` run, vLLM's dummy loader recursively called `state_dict(destination=..., prefix=..., keep_vars=...)`, causing:\r\n\r\n ```text\r\n TypeError: Token2WavDecoder.state_dict() got an unexpected keyword argument 'destination'\r\n ```\r\n\r\n This change removes those overrides and restores the standard `nn.Module` state-dict contract. The partial vendor checkpoint is loaded with `strict=False`, preserving the previous behavior of loading the available `wavegan` and `token2latent` weights while allowing model-created state that is absent from the checkpoint.\r\n\r\n2. The CoVo audio `PromptReplacement` used the string `\"<|cAUDIO|>\"` as its target. The pinned vLLM prompt-update interface expects a sequence of integer token IDs, so text matching attempted to decode a string as a token-ID vector and raised:\r\n\r\n ```text\r\n Ty","changed_files":["vllm_omni/model_executor/models/covo_audio/covo_audio.py","vllm_omni/model_executor/models/covo_audio/covo_audio_code2wav.py","vllm_omni/model_executor/models/covo_audio/token2wav.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: loader, checkpoint","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/covo_audio/covo_audio.py","vllm_omni/model_executor/models/covo_audio/covo_audio_code2wav.py","vllm_omni/model_executor/models/covo_audio/token2wav.py"],"selected_from_description":true}]}} +{"pr":7898,"title":"[Frontend] Add MiniMax-H3 latent editing workflows (WF-05)","body":"\r\n\r\n## Purpose\r\n\r\nImplement WF-05 from #7380 using the latent-edit interfaces from #7465 and #7575.\r\n\r\n1. Add one ComfyUI workflow covering object removal, inpainting, continuation, and extension.\r\n2. Build independent spatial masks with native ComfyUI nodes and calculate temporal masks with MiniMax-H3 Temporal Mask.\r\n3. Preview each mask as a red overlay on source video using native compositing nodes, with Preview above Result for each case.\r\n4. Upload video and audio masks as JSON files to match the server API, including small masks and audio scalars.\r\n5. Add workflow usage guides for the four editing cases.\r\n\r\n**Workflow Screenshot**\r\n\r\n\"WF-05\"\r\n\r\n## Test Plan\r\n\r\nRun MiniMax-H3 FL2VA: \r\n```bash\r\nvllm serve /root/.cache/huggingface/hub/models--MiniMaxAI--MiniMax-H3/snapshots/6818f6c32d12b210915e44ad56a4228c2608f160/FL2VA \\\r\n --omni \\\r\n --host 0.0.0.0 --port 8000 \\\r\n --trust-remote-code \\\r\n --served-model-name MiniMaxAI/MiniMax-H3 \\\r\n --task-type fl2va \\\r\n --num-gpus 8 --usp 8 --ring 1 \\\r\n --text-encoder-tp-size 8 \\\r\n --e","changed_files":["apps/ComfyUI-vLLM-Omni/README.md","apps/ComfyUI-vLLM-Omni/__init__.py","apps/ComfyUI-vLLM-Omni/comfyui_vllm_omni/nodes.py","apps/ComfyUI-vLLM-Omni/comfyui_vllm_omni/utils/api_client.py","apps/ComfyUI-vLLM-Omni/comfyui_vllm_omni/utils/latent_mask.py","apps/ComfyUI-vLLM-Omni/comfyui_vllm_omni/utils/types.py","apps/ComfyUI-vLLM-Omni/docs/wf05-h3-latent-editing.md","apps/ComfyUI-vLLM-Omni/example_workflows/vLLM-Omni Latent Mask Editing - Image Mask.json","apps/ComfyUI-vLLM-Omni/example_workflows/vLLM-Omni Latent Mask Editing - Temporal Mask.json","apps/ComfyUI-vLLM-Omni/example_workflows/vLLM-Omni Latent Mask Editing.json","apps/ComfyUI-vLLM-Omni/example_workflows/vLLM-Omni MiniMax-H3 Latent Mask Editing.json","apps/ComfyUI-vLLM-Omni/web/main.js","tests/e2e/features/comfyui/mock_videos_server.py","tests/e2e/features/comfyui/test_latent_mask.py","tests/e2e/features/comfyui/test_latent_mask_editing.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7896,"title":"[Hardware][Ascend] Add opt-in chunked anti-alias FIR upsampling","body":"## Purpose\n\nReduce the latency of long-input depthwise transposed FIR convolutions in the shared audio upsampling layer on Ascend, without loading model checkpoints or changing model-specific code.\n\nThe shared `UpSample1d` uses `F.conv_transpose1d` with `groups=channels`. On the tested NPU stack, long inputs are substantially slower than bounded, overlapping chunks. This change exposes an experimental, opt-in workaround at the existing shared layer.\n\nThis is **not** a general `ConvTranspose3d` implementation or a claim of end-to-end model acceleration. All validation inputs are synthetic tensors generated locally; no model weights, audio requests, datasets, captured workloads, or model-specific implementations are included.\n\n## Changes\n\n- Add a private chunked depthwise transposed-FIR helper to the existing `common/alias_free_activation.py`.\n- Integrate it into the existing NPU branch of `UpSample1d` only.\n- Include left context in the per-convolution input-length budget, fixing the possible overlap-induced cap overrun.\n- Preserve the existing whole-tensor replicate padding, dtype-conversion order, output cropping, parameter names, and state-dict structure.\n- Keep short inputs and ","changed_files":["tests/model_executor/models/common/test_alias_free_activation.py","tests/model_executor/models/common/test_alias_free_activation_npu.py","vllm_omni/model_executor/models/common/alias_free_activation.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/common/alias_free_activation.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/common/alias_free_activation.py"],"selected_from_description":false}]}} +{"pr":7892,"title":"[CI/Build] Promote LingBot-Video dense smoke to ready/merge and wire L4 expansion","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nPromote the dense LingBot-Video serving smoke (`robbyant/lingbot-video-dense-1.3b`) from nightly-only `full_model` coverage into the regular L2/L3 lanes, and actually collect the existing L4 expansion module in nightly.\r\n\r\n- `test_lingbot_video.py::test_text_to_image_001` and `test_video_generation_modes_001` now carry `core_model` + `advanced_model` so ready and merge share the same baseline T2I / T2V+TI2V cases.\r\n- Add **Diffusion · LingBot-Video Test** to `test-ready.yml` (`-m 'core_model and cuda'`) and `test-merge.yml` (`-m 'advanced_model and cuda and diffusion'`), H100 single-GPU, `source_file_dependencies: diffusion_lingbot_function`.\r\n- Nightly **LingBot Function Test** now runs `test_lingbot_video_expansion.py` (CFG-off / batch CFG) together with MoE and World; the dense smoke file is no longer listed there because it is no longer `full_model`.\r\n- Register `test_lingbot_video_expansion.py` in `diffusion_lingbot_function` path filters.\r\n\r\nMoE 30B smoke and LingBot-World v2 stay on `full_model` / nightly.\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** N/A (CI routing / pytest markers only)\r\n\r\n**vL","changed_files":[".buildkite/common/ci_source_file_dependencies.yml",".buildkite/cuda/test-merge.yml",".buildkite/cuda/test-nightly.yml",".buildkite/cuda/test-ready.yml","tests/e2e/online_serving/test_lingbot_video.py","tests/e2e/online_serving/test_lingbot_video_expansion.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:lingbot-video","path":"repos/vllm-omni/models/lingbot-video/rules.md","reason":"title/body model: lingbot-video","quick_map_status":"ok","read_required":false},{"owner":"model:lingbot-world","path":"repos/vllm-omni/models/lingbot-world/rules.md","reason":"title/body model: lingbot-world","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7885,"title":"[Bugfix] Avoid waveform-list GC scans and release completed MOSS batches","body":"## Purpose\n\nWarming many reference voices leaves decoded waveforms in the API server as large Python float lists. A full GC must traverse their element references even when the waveforms are live and no garbage is collected. MOSS's idle reference-encoder drainer also retains its completed `first`/`jobs` batch while awaiting the next queue item; subsequent speaker-code cache hits may never supply that next item.\n\nThis change:\n\n- Stores owned, C-contiguous float32 arrays in the shared waveform LRU and accounts for numeric storage with `nbytes`.\n- Gives MOSS reference encoding an array resolver. Existing list-based consumers, including MOSS Nano's IPC payload, retain the list interface through a temporary conversion; no second list cache is retained.\n- Drops both completed batch references in `finally` before the drainer waits for more work.\n- Adds regression coverage for array ownership, compatibility and batch lifetime.\n\nSource sampling rates, mono conversion, artifact hashes, file-change checks, resampling and batching remain unchanged. Tensor construction still copies, preserving isolation from the cached array.\n\nThis is based directly on `main`, independently of #7883. It does no","changed_files":["docs/serving/speech_api.md","tests/entrypoints/openai_api/test_reference_waveform_cache.py","tests/entrypoints/openai_api/test_serving_speech.py","tests/model_executor/models/moss_tts/test_reference_encoder.py","vllm_omni/entrypoints/openai/serving_speech.py","vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py","vllm_omni/model_executor/models/moss_tts/reference_encoder.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: batch, sampling","quick_map_status":"ok","read_required":false},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: queue","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/serving_speech.py","vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py"],"selected_from_description":true},{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/moss_tts/reference_encoder.py"],"selected_from_description":true}]}} +{"pr":7884,"title":"[Bugfix] Fix Nemotron VoiceChat duplex nightly handshake","body":"## Summary\n\nFixes #7698: Nemotron VoiceChat's nightly Realtime connection was routed to the turn-based handler and failed session setup with `Missing required field: model`.\n\nRegister Nemotron's `DuplexModelPlugin`, use the public `DuplexClient` and session preset, and restore the native duplex nightly. Tool-result batches are acknowledged through the thinker's multimodal output and explicit output whitelist, allowing later results after earlier batches have transferred to the worker.\n\n## Scope\n\nBased on main `b308e196`. Shared append-envelope construction is in `05be7468`; shared private-runtime-key validation is in `2cf96b0b`.\n\n- Keep model policy in the Nemotron plugin and reuse framework session state. Nemotron and MiniCPM now share append-envelope construction in `engine/duplex/intermediate.py`; frame sizing, prompt/token selection and model-specific worker fields stay in each plugin.\n- Use the framework `reject_private_runtime_keys` helper for Nemotron and MiniCPM. Each plugin retains its key set, exception type and error-message prefix; the dict-only input boundary and client-visible error code remain unchanged.\n- Publish request-aligned acknowledgement tensors from model-ow","changed_files":["docs/design/fullduplex.md","docs/serving/full_duplex_api.md","docs/serving/realtime_duplex_api.md","tests/clients/test_model_session_configs.py","tests/e2e/online_serving/nemotron_voicechat_realtime_duplex.py","tests/e2e/online_serving/test_nemotron_voicechat_duplex.py","tests/engine/duplex/test_session_runner_nemotron_voicechat.py","tests/model_executor/models/nemotron_voicechat/duplex/conftest.py","tests/model_executor/models/nemotron_voicechat/duplex/test_data_plane.py","tests/model_executor/models/nemotron_voicechat/duplex/test_input.py","tests/model_executor/models/nemotron_voicechat/duplex/test_runtime_contract.py","vllm_omni/clients/nemotron_voicechat.py","vllm_omni/engine/duplex/intermediate.py","vllm_omni/engine/duplex/plugin.py","vllm_omni/engine/duplex/session/model_channel.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/session.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/__init__.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/capabilities.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/data_plane.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/input.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/plugin.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/runtime.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/serving_adapter.py","vllm_omni/model_executor/models/nemotron_voicechat/nemotron_voicechat_thinker.py","vllm_omni/model_executor/models/nemotron_voicechat/pipeline.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:nemotron-voicechat","path":"repos/vllm-omni/models/nemotron-voicechat/rules.md","reason":"title/body model: nemotron-voicechat","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py, vllm_omni/model_executor/models/minicpmo_4_5/duplex/session.py, vllm_omni/model_executor/models/nemotron_voicechat/duplex/__init__.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/session.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/__init__.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/capabilities.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/data_plane.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/input.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/plugin.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/runtime.py","vllm_omni/model_executor/models/nemotron_voicechat/duplex/serving_adapter.py","vllm_omni/model_executor/models/nemotron_voicechat/nemotron_voicechat_thinker.py","vllm_omni/model_executor/models/nemotron_voicechat/pipeline.py"],"selected_from_description":false}]}} +{"pr":7879,"title":"[Bugfix] Merge audio final output into the matching text choice (#7376)","body":"## Purpose\n\nFixes #7376.\n\nA non-streaming chat request with `modalities=[\"text\", \"audio\"]` returned **two choices with duplicate `index=0`**: `choices[0]` carried the text with `audio=None`, `choices[1]` carried the audio with `content=None`. Clients that read `choices[0]` (the only choice they asked for with `n=1`) silently lost the audio.\n\nReproduced on latest main (`eb1abba`, single H20): `len(resp.choices) == 2`, `choices[1].index == 0` duplicated, audio (~31 KB wav) reachable only via `choices[1]` — details in the issue thread.\n\n## Root cause\n\n`chat_completion_full_generator` iterates over `final_outputs`, one per final output type: the \"text\" output builds choices via `_create_text_choice`, and the \"audio\" output builds **new** choices via `_create_audio_choice` (message with only `audio`, same `output.index`). `choices.extend()` then appends both groups, so a text+audio request yields duplicate indexes instead of one merged choice.\n\n## Change\n\n- Collect audio choices into `pending_audio_choices` during the output loop instead of extending `choices` with them.\n- After the loop, `_merge_audio_choices` folds each audio choice into the choice with the **matching index**: the mer","changed_files":["tests/entrypoints/openai_api/test_chat_audio_choice_merge.py","vllm_omni/entrypoints/openai/serving_chat.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request, chat completion","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/serving_chat.py"],"selected_from_description":true}]}} +{"pr":7877,"title":"[Hotfix][Core] PrefixCache: re-plan same-step prefix hits whose rows are written after the early prefetch","body":"\r\n## Purpose\r\n\r\nvLLM caches A's block hashes at `allocate_slots`, so a request B admitted in the same `schedule()` call hits blocks A only writes this step. The manager plans B's hit span at `new_step_starts`, before A's rows are registered: `_slot_ref` accepts ABSENT rows for mm keys (and for recycled blocks the previous tenant's COMMITTED rows for every key), the prefetch reads the pool, and publish never re-plans because a future already exists. B's merged prefix rows are silently wrong. Reachable with n>1, concurrent identical prompts, cold-start bursts, and chunked prefill of a shared prefix.\r\n\r\nModification:\r\n\r\nAt publish, drop prefetch futures whose `reserved_version` no longer matches after this step's writes are mapped, and let `_prefetch_hit_spans` re-plan them against the now IN_TRANSIT rows.\r\n\r\ncc @amy-why-3459 \r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** v0.29.0\r\n\r\n**vLLM-Omni Commit:** main @ 470ec6084 (base), fix 7bafe243f\r\n\r\n```\r\npytest tests/core/test_prefix_cache.py -k \"same_step_hit\" -m \"core_model and cpu\"\r\n```\r\n\r\n## Test Result\r\n\r\n```\r\n$ pytest tests/core/test_prefix_cache.py -k same_step_hit --confcutdir=tests/core -q\r\n6 passed, 84 deselected in 0.76s\r\n\r\n$ pytest tes","changed_files":["tests/core/test_prefix_cache.py","vllm_omni/core/prefix_cache/manager.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: prefix cache","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7870,"title":"[Bugfix] Stabilize NIXL ownership test claim queries","body":"Fixes #7789.\n\n## Purpose\n\n### What broke\n\n`test_read_ownership_through_terminal_and_deferred_paths` occasionally failed in Merge / Ready CI because the worker returned before reaching the patched `_wait_for_transfer`, leaving `entered.wait(5)` unset.\n\n### Reproduction\n\nThe failure is timing-sensitive and can be exercised with:\n\n```bash\npython -m pytest -q tests/distributed/omni_connectors/test_nixl_connector.py \\\n -k read_ownership_through_terminal_and_deferred_paths\n```\n\n### Root cause\n\nThis ownership-focused test was the only neighboring ownership test that did not use the existing `reliable_claim_queries` fixture. It therefore acquired its metadata claim through a real ZMQ query with a deliberately bounded 10 ms timeout. Under CI load, a transient query timeout made `consumer.get()` return before entering the transfer wait path.\n\n### Fix\n\nEnable `reliable_claim_queries` for this test. The fixture still exercises metadata resolution, wire encoding/decoding, claim generation, and the producer claim state machine, while removing unrelated socket scheduling from the ownership lifecycle assertion. Real ZMQ discovery remains covered by the handshake tests in the same module.\n\n## Test","changed_files":["tests/distributed/omni_connectors/test_nixl_connector.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"distributed","path":"repos/vllm-omni/components/distributed/_index.md","reason":"title/body: distributed","quick_map_status":"ok","read_required":false},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: scheduling","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7869,"title":"[Doc] Correct the SessionClosed admission-slot guarantee (#7636 Issue 22)","body":"\nFollow-up of the Unified Full-duplex Framework (#7413): addresses issue 22 of #7636, reported in @NolenLiang's issue comment (issuecomment-5676873365).\n\n## Purpose\n\n`docs/design/fullduplex.md` stated that observing `SessionClosed` means the admission slot is already free, and `DuplexSessionManager._close_runner`'s docstring similarly said that a client can open a replacement session immediately.\n\nThat guarantee is too strong.\n\n`SessionClosed` is emitted from a `finally` after the cleanup attempt, including when stage cleanup fails. In that failure path, the admission slot is intentionally retained until the reaper later succeeds. As a result, a client may observe `SessionClosed` and receive a successful close reply, yet an immediate replacement open can still be rejected with `resource_exhausted`.\n\nThis retention behavior is intentional and already pinned by tests, so this PR updates documentation only; there is no runtime behavior change.\n\nChanges:\n\n- `docs/design/fullduplex.md`: clarify that `SessionClosed` is emitted after the cleanup attempt, that a failed cleanup retains the admission slot until the reaper succeeds, and that the event alone does n","changed_files":["docs/design/fullduplex.md","vllm_omni/engine/duplex/session/manager.py"],"expected":{"status":"description_unrouted","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[],"scope_validation":[]}} +{"pr":7868,"title":"Fix diffusion API initialization","body":"Fix direct diffusion engine initialization failures found while testing #7759:\n\n- Give `OmniDiffusionConfig.extras` an independent dictionary default.\n- Create worker events, pipes and processes from one spawn context, preserving the caller's default start method.\n\nValidation: reproduced both failures before the fix; 96 related tests passed afterward. Qwen-Image 2.1 text-to-image and multi-image editing passed in single-worker and TP=2 runs without caller-side workarounds.\n\nAI assistance: Codex.\n","changed_files":["tests/diffusion/test_diffusion_config_propagation.py","tests/diffusion/test_multiproc_engine_concurrency.py","vllm_omni/diffusion/data.py","vllm_omni/diffusion/executor/multiproc_executor.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, text to image","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/data.py","vllm_omni/diffusion/executor/multiproc_executor.py"],"selected_from_description":true}]}} +{"pr":7866,"title":"[Bugfix] Honor request-level seed in VoxCPM2 CFM noise","body":"Fixes #7658\n\n## Root cause\n\n`/v1/audio/speech` folds `request.seed` into `SamplingParams.seed` (`serving_speech.py`), which reaches vLLM's own sampler — but never the CFM (flow-matching decoder) noise draws. In `voxcpm2_talker.py` every noise site drew from the global RNG stream, so identical text + seed returned different audio on every call. The existing `deterministic_cfm_noise` option cannot fix this either: its hash keys off the request id (benchmark replay only), so two requests with the same seed get different noise — which matches the reporter's observation that enabling it changed nothing.\n\n## Fix\n\nA seeded request now carries a dedicated `torch.Generator` in its `_RequestState`, created once at the first prefill chunk from the runner-provided `_omni_seed` hook (the runner already passes `SamplingParams.seed` there — comment: *\"Seed, so a model that samples inside forward() can be reproducible\"*). This follows the same pattern as `gepard_talker`.\n\nEvery CFM noise fill site (single-request eager/cuda-graph paths, state-buffer path, batched prefill-tail, batched decode, unified decode graph replay) routes through the request generator when present, drawing per-row slices of ","changed_files":["tests/model_executor/models/voxcpm2/test_request_seed_cfm_noise.py","vllm_omni/model_executor/models/voxcpm2/voxcpm2_talker.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:voxcpm2","path":"repos/vllm-omni/models/voxcpm2/rules.md","reason":"title/body model: voxcpm2","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/voxcpm2/voxcpm2_talker.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/voxcpm2/voxcpm2_talker.py"],"selected_from_description":false}]}} +{"pr":7859,"title":"Default Qwen-Image 2.1 CFG scale to 1.0","body":"## Summary\n\nDefault Qwen-Image 2.1 to `true_cfg_scale=1.0`, using only the positive-prompt branch unless CFG is explicitly enabled.\n\n- Apply the default before scheduling and in request/step execution; preserve explicit CFG values.\n- Update the Qwen-Image 2.1 edit launcher and recipe examples to pass CFG=1 explicitly. Shared example scripts are unchanged.\n- Remove inactive negative prompts from the default examples and document how to enable CFG.\n\nRefs #7759.\n\n## Validation\n\nParameter smoke checks passed for omitted and explicit CFG values, request/step preparation, and batch consistency. Full model inference was not rerun.\n\nAI assistance: Codex.\n","changed_files":["examples/offline_inference/image_to_image/run_qwen_image_21_edit.sh","recipes/Qwen/Qwen-Image-2.1.md","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"selected_from_description":false}]}} +{"pr":7853,"title":"[Bugfix] Preserve NextStep rectangular latent dimensions","body":"NextStep accepts rectangular output dimensions and generates the corresponding number of AR tokens, but its final unpatchify call omitted the grid dimensions and assumed a square. A real 768×512 request generated 1,536 tokens and then failed with `Invalid sequence length 1536`.\n\nPass the existing height/width divided by the pipeline down-factor to unpatchify. Preserve token order, VAE scaling and shift. No sampling or HSDP behavior changes are included.\n\nValidation:\n- CPU regression calls the pipeline forward path with real patchify/unpatchify, covering square, landscape and portrait grids with batch sizes 1 and 2 and nontrivial scaling/shift. Baseline: 4 failed, 2 passed. Fixed: all 6 passed.\n- Complete existing NextStep CPU test directory: 16 passed.\n- All changed-file pre-commit checks passed, including mypy.\n- The real-model failure is preserved. On the rebased source `43c4e79`, two fresh 768×512 TP2 requests complete successfully with identical output hashes; 512² output also matches the pre-fix square result. The complete balanced E2E quartet is still running, so this PR remains draft and makes no performance claim.\n\nSigned-off-by: 0z5a \n","changed_files":["tests/diffusion/models/nextstep_1_1/test_nextstep_rectangular.py","vllm_omni/diffusion/models/nextstep_1_1/pipeline_nextstep_1_1.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/nextstep_1_1/pipeline_nextstep_1_1.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/nextstep_1_1/pipeline_nextstep_1_1.py"],"selected_from_description":true}]}} +{"pr":7844,"title":"[Bugfix] Stop cancelled MiniMax-H3 requests at denoising boundaries","body":"## Purpose\n\nFixes #6403. #6759 already keeps waiting video jobs `queued` and forwards DELETE to the engine, but a request-mode H3 forward occupies the busy loop until generation finishes. Its scheduler abort queue alone cannot stop the model.\n\nOpted-in pipelines now receive an engine-owned shared-memory cancellation signal. H3 polls at input-preparation, denoising and decode boundaries and returns the existing aborted result when its entire execution wave is cancelled. Admission rollback, finalization and shutdown release the signal. Other pipelines retain their current behavior.\n\nRebased onto `7e5897b84`, resolving conflicts while preserving main's native-KV shutdown ordering, H3 continuation path and latent-editing tests. Review followup:\n\n- An opted-in rank with no local token, including an empty signal list, still participates in every checkpoint MIN collective and votes false. Pipeline-level opt-in is independent of token presence; unrelated pipelines do not acquire cancellation collectives.\n- Read local cancellation before synchronizing. Successful denoising steps no longer force device synchronization for cancellation. Actual device work already queued may finish.\n- Poll bef","changed_files":["docs/serving/videos_api.md","tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py","tests/diffusion/models/minimax_h3/test_minimax_h3_step_execution.py","tests/diffusion/test_diffusion_engine_cleanup.py","tests/diffusion/test_diffusion_model_runner.py","tests/diffusion/test_request_cancellation.py","vllm_omni/diffusion/cancellation.py","vllm_omni/diffusion/diffusion_engine.py","vllm_omni/diffusion/models/minimax_h3/denoise_loop.py","vllm_omni/diffusion/models/minimax_h3/pipeline_minimax_h3.py","vllm_omni/diffusion/request.py","vllm_omni/diffusion/worker/diffusion_model_runner.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/cancellation.py, vllm_omni/diffusion/diffusion_engine.py, vllm_omni/diffusion/models/minimax_h3/denoise_loop.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/cancellation.py","vllm_omni/diffusion/diffusion_engine.py","vllm_omni/diffusion/models/minimax_h3/denoise_loop.py","vllm_omni/diffusion/models/minimax_h3/pipeline_minimax_h3.py","vllm_omni/diffusion/request.py","vllm_omni/diffusion/worker/diffusion_model_runner.py"],"selected_from_description":false}]}} +{"pr":7838,"title":"[Model] Add MiniMax-H3 long-video latent continuation with driving audio","body":"## Description\r\n\r\nMiniMax-H3 currently limits target videos to 15 seconds and treats input audio as a short reference. This adds opt-in long-video generation with a full driving soundtrack. A 75-second request produces 1,807 frames at 24 fps (75.292 seconds after native frame-grid alignment).\r\n\r\n- `long_video=true` defaults Ref2VA to latent-tail continuation: denoise bounded windows, condition on the previous audio/video latent tail, discard the newly sampled overlap, and append only the new suffix. `long_video_mode=full` retains whole-target sampling for comparison.\r\n- Shift media temporal positions by each window's global start before RoPE evaluation, retaining fractional offsets while leaving text, static images, spatial positions, and padding unchanged. This combines continuation with global temporal positioning; it does not rescale RoPE frequencies.\r\n- Accept one `continuation_prompts` string per window. Independently encode the prompts and use a shared media origin after the longest text prefix so prompt lengths do not move the audio/video timeline.\r\n- Add `audio_mode=lock_source`: crop/pad stereo channels independently and keep target audio rows clean during denoising. The r","changed_files":["recipes/MiniMaxAI/MiniMax-H3.md","tests/diffusion/layers/test_fused_qk_norm_rope.py","tests/diffusion/models/minimax_h3/test_minimax_h3_collectives.py","tests/diffusion/models/minimax_h3/test_minimax_h3_continuation.py","tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py","tests/diffusion/models/minimax_h3/test_minimax_h3_latent_edit_pipeline.py","tests/diffusion/models/minimax_h3/test_minimax_h3_long_video_limits.py","tests/diffusion/models/minimax_h3/test_minimax_h3_step_execution.py","tests/model_executor/models/test_minimax_h3_encoder_payload.py","vllm_omni/diffusion/layers/fused_qk_norm_rope.py","vllm_omni/diffusion/models/minimax_h3/continuation.py","vllm_omni/diffusion/models/minimax_h3/denoise_loop.py","vllm_omni/diffusion/models/minimax_h3/packed_sequence.py","vllm_omni/diffusion/models/minimax_h3/pipeline_minimax_h3.py","vllm_omni/model_executor/models/minimax_h3/conditioning.py","vllm_omni/model_executor/models/minimax_h3/encoder_processing.py","vllm_omni/model_executor/models/minimax_h3/long_video.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: sampling","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minimax_h3/conditioning.py","vllm_omni/model_executor/models/minimax_h3/encoder_processing.py","vllm_omni/model_executor/models/minimax_h3/long_video.py"],"selected_from_description":true},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/layers/fused_qk_norm_rope.py","vllm_omni/diffusion/models/minimax_h3/continuation.py","vllm_omni/diffusion/models/minimax_h3/denoise_loop.py","vllm_omni/diffusion/models/minimax_h3/packed_sequence.py","vllm_omni/diffusion/models/minimax_h3/pipeline_minimax_h3.py"],"selected_from_description":false}]}} +{"pr":7826,"title":"[Bugfix]Include Reference Audio Cache Salt for HiggsAudio v2","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\nFixes #7822 \r\n\r\n## RootCause\r\n\r\nFor HiggsAudio v2 voice-clone requests, the scheduler-visible `prompt_token_ids` contain audio placeholders whose shape depends on the encoded frame count, but not the actual reference-audio content. Therefore, two different reference audios with the same frame count can produce identical prompt token IDs.\r\n\r\nThe v2 adapter discarded the content-aware cache key returned by `_resolve_ref_audio()` and did not set `prompt[\"cache_salt\"]` for the voice-clone path. As a result, vLLM prefix caching could treat requests with different reference audios as the same prefix and reuse incorrect cached KV states.\r\n\r\nThis change preserves the resolved reference-audio cache key, includes it in the conditioning metadata, and derives a cache salt for voice-clone requests while leaving the plain text-only path unchanged.\r\n## Test Plan\r\n`pytest -v tests/entrypoints/openai_api/test_serving_speech.py`\r\n**vLLM Version:**\r\n0.29.0\r\n\r\n**vLLM-Omni Commit:**\r\n679ba3bf85cce9e5b39eb4694b0ac3793d522281\r\n\r\n## Test Result\r\n282 passed \r\n\r\ncc @hsliuustc0106 @linyueqian \r\n\r\n**BEFORE SUBMITTING:** read","changed_files":["tests/entrypoints/openai_api/test_serving_speech.py","vllm_omni/entrypoints/openai/tts_adapters/higgs_audio_v2.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:higgs-audio","path":"repos/vllm-omni/models/higgs-audio/rules.md","reason":"title/body model: higgs-audio","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, openai","quick_map_status":"truncated","read_required":true},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: scheduler","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/tts_adapters/higgs_audio_v2.py"],"selected_from_description":true}]}} +{"pr":7821,"title":"[Performance][MiniCPM-o] Zero-copy block-aligned KV sliding window with in-place Re-RoPE for duplex Stage-0","body":"## Summary of Changes\n- **Zero-Copy KV Compaction**: Implements block-aligned PagedAttention KV cache window trimming for MiniCPM-o duplex Stage-0, bounding memory indefinitely without reallocation.\n- **In-Place Re-RoPE**: Rotates retained physical $K$ cache vectors directly on GPU in ~2.1ms, preserving attention correctness while leaving $V$ cache completely untouched.\n- **Glitch-Free Duplex Streaming**: Replaces prompt re-prefill stalls with constant-time in-place transformation, guaranteeing sub-5ms turn-boundary latency for real-time speech.\n\n---\n\n## Background & Prior Work\nThis PR supersedes the initial design in #7631, which rebuilt prompt embeddings and triggered a full ~6,000-token re-prefill (~384ms stall) upon trimming, causing severe downstream audio underruns in continuous full-duplex conversations.\n\n---\n\n## Deep Dive: Implementation Details\n\nThe core challenge of sliding-window attention in continuous multi-modal streaming is twofold:\n1. Reclaiming physical GPU memory blocks without copying or reallocating cached tokens.\n2. Correcting the rotary position embeddings (RoPE) already baked into the stored Key vectors without re-running model forward passes.\n\nOur solution s","changed_files":["tests/model_executor/models/minicpmo_4_5/duplex/test_rerope.py","tests/model_executor/models/minicpmo_4_5/duplex/test_window_plan.py","tests/model_executor/models/minicpmo_4_5/duplex/test_window_wiring.py","vllm_omni/core/sched/omni_ar_scheduler.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/window_kv.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/window_plan.py","vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni.py","vllm_omni/worker/gpu_ar_model_runner.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py, vllm_omni/model_executor/models/minicpmo_4_5/duplex/window_kv.py, vllm_omni/model_executor/models/minicpmo_4_5/duplex/window_plan.py","quick_map_status":"ok","read_required":false},{"owner":"distributed","path":"repos/vllm-omni/components/distributed/_index.md","reason":"changed files: vllm_omni/worker/gpu_ar_model_runner.py","quick_map_status":"ok","read_required":false},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"changed files: vllm_omni/core/sched/omni_ar_scheduler.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/window_kv.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/window_plan.py","vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni.py"],"selected_from_description":false},{"owner":"distributed","changed_files":["vllm_omni/worker/gpu_ar_model_runner.py"],"selected_from_description":false},{"owner":"scheduler","changed_files":["vllm_omni/core/sched/omni_ar_scheduler.py"],"selected_from_description":false}]}} +{"pr":7820,"title":"[Rebase] Rebase to vLLM 0.30.0","body":"## Purpose\r\n\r\nAdapt vLLM-Omni to the released vLLM 0.30.0 (`ced6857afa0ea7b2e3f0846a62e1394e90f15607`) and merge current main (`ba5b7485a7f31aa2ddd90e328e873d189f23ceaf`).\r\n\r\nCUDA CI and production, ROCm, and XPU use their official v0.30.0 images; Intel CI uses the matching release. Remove the temporary commit-wheel reinstall and dependency-repair block. Ascend pins retain their separately released dependency version.\r\n\r\nThe rebase adapts scheduler/connector output contracts, ModelOpt quantization, multimodal hashing, worker interfaces, and benchmark keyword forwarding. Worker v2 uses the released RopeState constructor, forwards dummy-run state-slot arguments, supplies the execution state's CUDA graph statistics field, and converts the new compact sampling masks without the removed argument. Regression tests exercise real upstream constructors and both compact and bitmask sampling-mask paths. Grammar rejection in the generation scheduler now runs before terminal cleanup: rejected requests emit ERROR, release resources, and cannot rearm a resumable session. Regression coverage exercises both incomplete and complete prompts.\r\n\r\nInstallation and quickstart source instructions target v","changed_files":[".buildkite/intel/pipeline-intel.yml","benchmarks/accuracy/image_to_image/README.md","benchmarks/accuracy/text_to_image/README.md","benchmarks/distributed/omni_connectors/README.md","benchmarks/glm_image/README.md","docker/Dockerfile.ci","docker/Dockerfile.cuda","docker/Dockerfile.rocm","docker/Dockerfile.xpu","docs/getting_started/installation/README.md","docs/getting_started/installation/gpu.md","docs/getting_started/installation/gpu/cuda.inc.md","docs/getting_started/installation/gpu/rocm.inc.md","docs/getting_started/installation/gpu/xpu.inc.md","docs/getting_started/quickstart.md","requirements/common.txt","requirements/cuda.txt","tests/benchmarks/patch/test_patch.py","tests/core/sched/test_generation_scheduler_restore.py","tests/core/sched/test_omni_ar_scheduler_logprobs.py","tests/core/sched/test_omni_ar_scheduler_stale_drain.py","tests/core/sched/test_omni_ar_scheduler_streaming.py","tests/core/sched/test_omni_generation_scheduler_update_session.py","tests/core/sched/test_omni_sched_ec_request_finish.py","tests/core/sched/test_omni_sched_prefill_stats_finalize.py","tests/dfx/stability/README.md","tests/diffusion/diffusion_kv/test_block_tables.py","tests/diffusion/diffusion_kv/test_mooncake_cfg_fanout.py","tests/diffusion/diffusion_kv/test_native_prefetch.py","tests/diffusion/diffusion_kv/test_paged_attention_adapter_gpu.py","tests/diffusion/models/glm_image/test_glm_image_quantization.py","tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py","tests/distributed/omni_connectors/test_chunk_transfer_adapter.py","tests/model_executor/models/indextts2/test_enhanced_codec.py","tests/model_executor/quantization/test_modelopt_nvfp4_nan_clamp.py","tests/platforms/test_cuda_ir_op_priority.py","tests/worker_v2/test_omni_ar_model_runner.py","tests/worker_v2/test_omni_gpu_model_runner.py","tests/worker_v2/test_omni_model_state.py","vllm_omni/benchmarks/patch/patch.py","vllm_omni/core/sched/omni_ar_scheduler.py","vllm_omni/core/sched/omni_generation_scheduler.py","vllm_omni/core/sched/omni_scheduler_mixin.py","vllm_omni/diffusion/diffusion_kv/model_runner_backend.py","vllm_omni/diffusion/models/cosmos3/mixed_precision/strategy.py","vllm_omni/diffusion/worker/diffusion_worker.py","vllm_omni/engine/async_omni_engine.py","vllm_omni/engine/stage_engine_core_proc.py","vllm_omni/entrypoints/async_omni.py","vllm_omni/patch.py","vllm_omni/platforms/cuda/platform.py","vllm_omni/platforms/rocm/platform.py","vllm_omni/platforms/xpu/platform.py","vllm_omni/worker/gpu_generation_model_runner.py","vllm_omni/worker/gpu_model_runner.py","vllm_omni/worker_v2/model_states/omni_model_state.py","vllm_omni/worker_v2/omni_ar_model_runner.py","vllm_omni/worker_v2/omni_generation_model_runner.py","vllm_omni/worker_v2/omni_model_runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: sampling","quick_map_status":"ok","read_required":false},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: scheduler","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/async_omni.py"],"selected_from_description":false},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/diffusion_kv/model_runner_backend.py","vllm_omni/diffusion/models/cosmos3/mixed_precision/strategy.py","vllm_omni/diffusion/worker/diffusion_worker.py"],"selected_from_description":false},{"owner":"distributed","changed_files":["vllm_omni/entrypoints/async_omni.py","vllm_omni/worker/gpu_generation_model_runner.py","vllm_omni/worker/gpu_model_runner.py"],"selected_from_description":false},{"owner":"scheduler","changed_files":["vllm_omni/core/sched/omni_ar_scheduler.py","vllm_omni/core/sched/omni_generation_scheduler.py","vllm_omni/core/sched/omni_scheduler_mixin.py"],"selected_from_description":true}]}} +{"pr":7817,"title":"[Bugfix] Avoid multimodal cache collisions in multistage image editing","body":"## Description\r\n\r\nMultistage image-edit requests assign fixed image UUIDs such as `img-image-0`. Different reference images can therefore reuse the same cached features: after editing a cat, a subsequent dog request can be contaminated by the cat image.\r\n\r\nRemove the caller-generated `multi_modal_uuids` and let vLLM derive cache keys from image content, separating different images while preserving cache reuse for identical images.\r\n\r\n## Test Plan\r\n\r\n### Start the service\r\n\r\nSave the following configuration as `deploy.yaml` (tested on two B300 GPUs):\r\n\r\n
\r\ndeploy.yaml\r\n\r\n```yaml\r\npipeline: hunyuan_image_3_moe\r\nasync_chunk: false\r\ntrust_remote_code: true\r\nconnectors:\r\n shared_memory_connector:\r\n name: SharedMemoryConnector\r\nstages:\r\n- stage_id: 0\r\n is_comprehension: true\r\n final_output: true\r\n final_output_type: text\r\n max_num_seqs: 1\r\n gpu_memory_utilization: 0.85\r\n enforce_eager: true\r\n trust_remote_code: true\r\n max_num_batched_tokens: 16384\r\n devices: '0'\r\n tensor_parallel_size: 1\r\n hf_overrides:\r\n rope_parameters:\r\n mrope_section:\r\n - 0\r\n - 32\r\n - 32\r\n rope_type: default\r\n output_connectors:\r\n to_stage_1: s","changed_files":["tests/entrypoints/openai_api/test_serving_chat_multistage_generation.py","vllm_omni/entrypoints/openai/serving_chat.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:hunyuan-image3","path":"repos/vllm-omni/models/hunyuan-image3/rules.md","reason":"title/body model: hunyuan-image3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration, yaml, deploy","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/serving_chat.py"],"selected_from_description":true}]}} +{"pr":7816,"title":"[Feature] Add experimental LingBot World last-step KV reuse","body":"## Purpose\n\nLingBot World's causal DMD sampler uses four denoising forwards followed by a fifth forward on the finished `x0` at `t=0` to write clean KV history. This PR adds an experimental option to reuse the fourth denoising forward's KV, reducing transformer calls from five to four per chunk.\n\nEnable it by passing `model_config={\"lingbot_reuse_last_step_kv\": True}` when constructing `Omni` or `AsyncOmni`. The option must be a boolean and is fixed per pipeline instance; the default is `False` (clean-KV writeback). The previous environment variable is no longer read. Reused KV comes from the last noisy input rather than the final clean latent, so generated outputs can differ. The measurements below describe the speed and output differences for the tested workload.\n\n## What Changes\n\n| File | Change |\n|---|---|\n| `vllm_omni/diffusion/models/lingbot_world/dmd_block.py` | Accept the strategy explicitly from the pipeline, write KV on the fourth probe, skip the clean forward, and preserve paged-context finalization and the upstream camera-modulation cache. |\n| `vllm_omni/diffusion/models/lingbot_world/pipeline.py` | Validate `model_config.lingbot_reuse_last_step_kv` before loading compo","changed_files":["recipes/Robbyant/LingBot-World-2.0.md","tests/diffusion/models/lingbot_world/test_pipeline_lingbot_world.py","vllm_omni/diffusion/models/lingbot_world/dmd_block.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:lingbot-world","path":"repos/vllm-omni/models/lingbot-world/rules.md","reason":"title/body model: lingbot-world","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, pipeline, default","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/lingbot_world/dmd_block.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py"],"selected_from_description":true}]}} +{"pr":7815,"title":"[Bugfix][TTS] Fix Audio8 voice clone reference resolution","body":"\r\n\r\n## Purpose\r\n\r\nFix Audio8 voice cloning through the OpenAI-compatible `/v1/audio/speech` endpoint.\r\n\r\nWhen an Audio8 request contains `ref_audio`, the server fails during request preparation with HTTP 400:\r\n\r\n```text\r\ntoo many values to unpack (expected 2)\r\n```\r\n\r\n`OmniOpenAIServingSpeech._resolve_ref_audio()` returns three values:\r\n\r\n```python\r\n(wav_samples, sample_rate, cache_key)\r\n```\r\n\r\nThe Audio8 adapter only unpacked two values. This PR updates the adapter to accept and intentionally ignore the unused cache key, consistent with the other TTS adapters:\r\n\r\n```python\r\nwav_list, sample_rate, _ = await server._resolve_ref_audio(...)\r\n```\r\n\r\nThe error occurs before model inference and is not specific to the RTX A6000 or any other GPU.\r\n\r\nA regression test verifies that `Audio8TTSAdapter.build()` correctly handles the three-value result.\r\n\r\nRelated to #5562.\r\n\r\n## Test Plan\r\n\r\nRun the Audio8 adapter tests:\r\n\r\n```bash\r\npytest -o addopts= -q tests/entrypoints/openai/test_audio8_tts_adapter.py\r\n```\r\n\r\nRuntime verification:\r\n\r\n1. Start the Audio8 server.\r\n2. Submit a voice-cloning request containing `ref_audio` and its matching `ref_text`.\r\n3. Verify that","changed_files":["tests/entrypoints/openai/test_audio8_tts_adapter.py","vllm_omni/entrypoints/openai/tts_adapters/audio8_tts.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, endpoint, openai","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/tts_adapters/audio8_tts.py"],"selected_from_description":true}]}} +{"pr":7808,"title":"[XPU][CI] Drop the USTC PyPI mirror from the XPU image build","body":"The extra index pins the XPU image build to mirrors.ustc.edu.cn, which the Intel CI agents cannot reach, so the dependency install fails before it gets to resolve anything.\r\n\r\nIt is also actively harmful even when reachable: the upstream vLLM XPU base image exports UV_EXTRA_INDEX_URL=https://download.pytorch.org/whl/xpu, and a command-line --extra-index-url takes precedence over the environment variable, so passing the mirror silently drops the PyTorch XPU index from the resolution.\r\n","changed_files":["docker/Dockerfile.xpu"],"expected":{"status":"description_unrouted","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[],"scope_validation":[]}} +{"pr":7803,"title":"[CI][Qwen3-Omni] Keep the deploy-YAML literal in the structured-config sampling check","body":"## Purpose\n\n`Omni · Qwen3-Omni Test` is still red on main after #7742 (build 15599 on d4ffde1a): `test_structured_multistage_config_reaches_runtime` now fails at the resolver check instead of the runtime check.\n\n## Root cause\n\n#7706 and #7742 fixed the same assertion in two different ways and merged without a textual conflict:\n\n- #7706 changed the code2wav expectation literal to the runtime value, `top_k: 0`.\n- #7742 kept the deploy-YAML literal (`top_k: -1`), added a check that the resolver carries the YAML values verbatim into the typed stage config, and compared the runtime `SamplingParams` against `SamplingParams(**expected)` so vLLM's greedy normalization applies on both sides.\n\nCombined, the verbatim check compares `0` against the YAML's `-1`:\n\n```\nAssertionError: assert dict_items([..., ('top_k', 0), ...]) <= dict_items([..., ('top_k', -1), ..., ('detokenize', True)])\ntests/e2e/offline_inference/test_qwen3_omni.py:135\n```\n\n## Change\n\nRestore the YAML literal (`top_k: -1`) in `expected_sampling` and say in the comment that these literals mirror `vllm_omni/deploy/qwen3_omni_moe.yaml`. With that, the resolver check matches the typed stage config and the normalized runtime compa","changed_files":["tests/e2e/offline_inference/test_qwen3_omni.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, yaml, deploy","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: sampling","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7801,"title":"Fix decode cuda graph for Qwen-Image-2.1 model-level cpu offload","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:**\r\n\r\n**vLLM-Omni Commit:**\r\n\r\n## Test Result\r\n\r\n**BEFORE SUBMITTING:** read [CONTRIBUTING.md](https://github.com/vllm-project/vllm-omni/blob/main/CONTRIBUTING.md) and run the [precheck-pr skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) with the code agent for a self-check against project conventions.\r\n(anything written below this line will be removed by GitHub Actions)\r\n","changed_files":["tests/diffusion/models/qwen_image_21/test_decode_graph.py","tests/diffusion/offloader/test_sequential_backend.py","vllm_omni/diffusion/models/qwen_image_21/decode_graph.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py","vllm_omni/diffusion/offloader/module_residency.py","vllm_omni/diffusion/offloader/sequential_backend.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image_21/decode_graph.py, vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py, vllm_omni/diffusion/offloader/module_residency.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/decode_graph.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py","vllm_omni/diffusion/offloader/module_residency.py","vllm_omni/diffusion/offloader/sequential_backend.py"],"selected_from_description":false}]}} +{"pr":7800,"title":"[Bugfix][Core] Apply Realtime item truncation once per command","body":"## Purpose\n\n### Problem\n\nA single `conversation.item.truncate` can shorten the retrieved assistant transcript twice. For a ten-character transcript and ten seconds of audio, truncating at four seconds returns `a` rather than `abcd`.\n\n### Root cause and fix\n\n`resolve_truncate_item` and the subsequent `conversation.item.truncated` projection both mutate the same item. The second call interpolates over already shortened text: without alignment marks it reapplies the same duration ratio; with marks, a cutoff between marks can also shrink the text again.\n\nRemove the early mutation. Keep cursor registration and both control payloads; apply the cut when projecting the internal acknowledgment. No new state or copy is needed. Add one resolver-to-retrieval regression and clarify the documented approximation.\n\n### Scope\n\nThis fixes duplicate processing of **one command**. It does not make repeated client truncate commands idempotent, change history policy, align text exactly with speech, or unify retrieved items with the model's history. Completed response `output_audio_buffer.clear` behavior is unchanged. The engine's canonical-history truncation is a separate function and is not modified; t","changed_files":["docs/serving/realtime_duplex_api.md","tests/engine/duplex/test_duplex_events.py","vllm_omni/engine/duplex/realtime_events.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: response","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7798,"title":"[BugFix] Fix diffusion TTS voice error responses","body":"## Purpose\r\nFixes #7793\r\n\r\nFix voice API errors returning 500 for diffusion TTS models when tokenization is unavailable. \r\n\r\n## Changes\r\n\r\n- Use upstream's standalone error constructor and remove the unused request argument.\r\n- Remove the mock that hid the bug and test production voice routes with `serving_tokenization=None`.\r\n\r\n## Tests\r\n\r\n```bash\r\n.venv/bin/python -m pytest tests/entrypoints/openai_api/test_serving_speech.py -k 'api_server or voice_routes_without_tokenization' --run-level core_model -q\r\n```\r\n\r\n```text\r\n23 passed, 264 deselected, 17 warnings in 2.67s\r\n```\r\n\r\n- All applicable pre-commit checks passed except the existing mypy error at `api_server.py:494`, reproduced on unchanged main (`bool | None` passed where `bool` is required).\r\n","changed_files":["tests/entrypoints/openai_api/test_serving_speech.py","vllm_omni/entrypoints/openai/api_server.py","vllm_omni/entrypoints/openai/errors.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, server, openai","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/api_server.py","vllm_omni/entrypoints/openai/errors.py"],"selected_from_description":true}]}} +{"pr":7797,"title":"Add FP8 KV cache support for Qwen-Image-2.1 DiT","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:**\r\n\r\n**vLLM-Omni Commit:**\r\n\r\n## Test Result\r\n\r\n**BEFORE SUBMITTING:** read [CONTRIBUTING.md](https://github.com/vllm-project/vllm-omni/blob/main/CONTRIBUTING.md) and run the [precheck-pr skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) with the code agent for a self-check against project conventions.\r\n(anything written below this line will be removed by GitHub Actions)\r\n","changed_files":["recipes/Qwen/Qwen-Image-2.1.md","vllm_omni/diffusion/models/qwen_image_21/decode_graph.py","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: dit","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/decode_graph.py","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py"],"selected_from_description":true}]}} +{"pr":7792,"title":"Add FP8 online qunatization support for Qwen-Image-2.1","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:**\r\n\r\n**vLLM-Omni Commit:**\r\n\r\n## Test Result\r\n\r\n**BEFORE SUBMITTING:** read [CONTRIBUTING.md](https://github.com/vllm-project/vllm-omni/blob/main/CONTRIBUTING.md) and run the [precheck-pr skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) with the code agent for a self-check against project conventions.\r\n(anything written below this line will be removed by GitHub Actions)\r\n","changed_files":["docs/user_guide/diffusion_features.md","docs/user_guide/quantization/fp8.md","recipes/Qwen/Qwen-Image-2.1.md","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py, vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py"],"selected_from_description":false}]}} +{"pr":7791,"title":"[Bugfix] Fix Qwen3-Omni realtime playback interruption and conversation history","body":"## Purpose\n\nQwen3-Omni realtime audio could keep playing after the user started speaking, including when generation had already completed. Cancellation could also wait behind an outstanding audio decode. Stop interrupted playback immediately, discard late output from the old response, and preserve queued events for the next response.\n\nForward clears for the latest completed response to the engine so its playback cursor is released before the next input; late clears for older responses cannot interrupt a newer turn. Preserve the assistant turn position while unaligned audio awaits playback acknowledgement, remove history items by identity, and evict assistant replies together with discarded input audio.\n\nFollow-up to #7585. The default voice prompt and protocol migration are outside this change.\n\n## Test Plan\n\n- Backend regressions: `tests/engine/duplex/` and `tests/model_executor/models/qwen3_omni/test_duplex_plugin.py`.\n- Browser regressions: `node --test tests/examples/test_realtime_web_profiles.cjs`, including actual playback-worklet clearing and asynchronous decode ordering.\n- Real Qwen checkpoint, production `qwen3_omni_duplex.yaml`, Silero VAD, checked-in speech and synthetic","changed_files":["docs/serving/realtime_duplex_api.md","examples/online_serving/realtime_web/app/static/app.js","examples/online_serving/realtime_web/app/static/profiles/qwen3_turn.js","tests/engine/duplex/test_duplex_events.py","tests/engine/duplex/test_engine_session.py","tests/entrypoints/openai_api/test_qwen3_omni_realtime_websocket.py","tests/examples/test_realtime_web_profiles.cjs","tests/model_executor/models/qwen3_omni/test_duplex_plugin.py","vllm_omni/engine/duplex/realtime_events.py","vllm_omni/engine/duplex/session/engine_session.py","vllm_omni/engine/duplex/session/runner.py","vllm_omni/model_executor/models/qwen3_omni/duplex/plugin.py","vllm_omni/model_executor/models/qwen3_omni/duplex/session.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: yaml, default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: response","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: model executor, checkpoint","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/qwen3_omni/duplex/plugin.py","vllm_omni/model_executor/models/qwen3_omni/duplex/session.py"],"selected_from_description":true}]}} +{"pr":7790,"title":"Optimize Qwen-Image 2.1 attention and decode graph reuse","body":"## Summary\n\nFollow-up to #7759; depends on #7787. Review the final commit for this optimization; rebase after #7787 merges.\n\n- Align Qwen-Image 2.1 attention with Diffusers: segmented cuDNN prefill and mask-free eager/Graph decode when all tokens are valid.\n- Skip redundant Graph prefix KV copies, refreshing on request/cache/batch changes while preserving padding fallback.\n\n## Validation\n\n1024×1024, 50 steps, CFG 4, BF16, cuDNN; three measured runs after warmup:\n\n| Workload | Before | After |\n|---|---:|---:|\n| Text-to-image | 8,863 ms | 7,958 ms |\n| Two-reference editing | 14,043 ms | 10,380 ms |\n\n100 regression tests passed on this branch. The identical runtime code also passed 4 online request/step tests. Eager/Graph outputs match exactly for text-to-image, RGB/RGBA and multi-reference edits, including A/B/A request reuse. The 50-step text-to-image output matches Diffusers exactly; two-reference editing still differs (RGB PSNR 47.09 dB).\n\nAI assistance: Codex.\n","changed_files":[".buildkite/cuda/test-merge.yml",".buildkite/cuda/test-ready.yml","examples/offline_inference/image_to_image/image_edit.py","examples/offline_inference/image_to_image/qwen_image_21_edit.py","examples/offline_inference/image_to_image/run_qwen_image_21_edit.sh","recipes/Qwen/Qwen-Image-2.1.md","tests/diffusion/attention/test_cudnn_attn.py","tests/diffusion/distributed/test_distributed_vae_executor.py","tests/diffusion/distributed/test_vae_error_recovery.py","tests/diffusion/models/qwen_image_21/test_batch_compatibility.py","tests/diffusion/models/qwen_image_21/test_decode_graph.py","tests/diffusion/models/qwen_image_21/test_transformer_numerics.py","tests/diffusion/models/qwen_image_21/test_vae.py","tests/e2e/online_serving/test_qwen_image_21.py","tests/entrypoints/openai_api/test_image_server.py","vllm_omni/diffusion/attention/backends/cudnn_attn.py","vllm_omni/diffusion/data.py","vllm_omni/diffusion/distributed/autoencoders/distributed_vae_executor.py","vllm_omni/diffusion/models/qwen_image_21/decode_graph.py","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py","vllm_omni/diffusion/worker/diffusion_model_runner.py","vllm_omni/entrypoints/openai/api_server.py","vllm_omni/entrypoints/openai/serving_chat.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: batch","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/api_server.py","vllm_omni/entrypoints/openai/serving_chat.py"],"selected_from_description":true},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/attention/backends/cudnn_attn.py","vllm_omni/diffusion/data.py","vllm_omni/diffusion/distributed/autoencoders/distributed_vae_executor.py","vllm_omni/diffusion/models/qwen_image_21/decode_graph.py","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py","vllm_omni/diffusion/worker/diffusion_model_runner.py"],"selected_from_description":true}]}} +{"pr":7786,"title":"[Bugfix][Core] Fix CLI parallel flags losing to nested deploy parallel_config","body":"## Purpose\r\n\r\nFix a precedence inversion introduced by the typed-config switch (#6849) that broke the NPU nightly HunyuanImage3 diffusion perf tests.\r\n\r\nThe legacy path `StageConfig.to_omegaconf` moves flat CLI parallel fields (`tensor_parallel_size`, `ulysses_degree`, ...) into the nested `parallel_config` dict via `_apply_diffusion_parallel_runtime_overrides`, so CLI flags win over a deploy YAML that nests its own `parallel_config` values. The new typed path `_stage_engine_values` skipped that step: CLI flags stayed flat, and `_build_parallel_config` then applied the nested deploy dict on top of the flat values — letting the deploy YAML beat the CLI.\r\n\r\nIn `vllm_omni/deploy/hunyuan_image3_dit.yaml`, the `npu` platform section sets nested `parallel_config.tensor_parallel_size: 4`, while the perf tests pass `--tensor-parallel-size 2`. The inversion turned the CLI's 2 back into the YAML's 4, so the stage demanded `tp=4 × usp=2 = 8` devices on a 4-card NPU machine:\r\n\r\n```\r\nRuntimeError: Stage 0 requires 8 device(s) based on parallel_config, but 4 device(s) are available\r\n```\r\n\r\nThis PR applies `_apply_diffusion_parallel_runtime_overrides` before `reconcile_diffusion_attention_overrid","changed_files":["tests/config/test_omni_config.py","vllm_omni/config/omni_config.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:hunyuan-image3","path":"repos/vllm-omni/models/hunyuan-image3/rules.md","reason":"title/body model: hunyuan-image3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, yaml, deploy","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, dit, ulysses","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/omni_config.py"],"selected_from_description":true}]}} +{"pr":7785,"title":"[Bugfix] Compensate cancelled duplex open, append and resume; fix serving README drift (#7636 Issues 1, 2, 3, 21)","body":"Follow-up of the Unified Full-duplex Framework (#7413): resolves #7636 Issues 1, 2, 3 and 21.\r\n\r\n## Purpose\r\n\r\nFour findings from the #7413 review that were deferred to follow-up PRs. The first three are cancellation paths where an `except Exception` rollback let an `asyncio.CancelledError` (a `BaseException`) skip the compensation; the fourth is a docs drift.\r\n\r\n### Issue 1: `open()` leaked the admission slot and runner on task cancellation (P1)\r\n\r\n**What broke:** a cancel while `DuplexSessionManager.open()` awaited the plugin or the result sink (engine teardown, task cancellation) skipped the rollback. The runner stayed in `self.runners` with its Stage0 reservation; admission counts `runners | closing | admitting`, so each cancelled open burned one slot for good and after `max_sessions` of them every new session was refused with `duplex_session_capacity_exhausted`.\r\n\r\n**Fix:** the rollback is one helper, `_rollback_open`, run from an explicit `except asyncio.CancelledError` branch (re-raised) as well as the `except Exception` branch. The bookkeeping that decides admission (`runners`, the session's request reservations, the request index) is undone synchronously before any await, ","changed_files":["docs/design/fullduplex.md","docs/serving/README.md","tests/engine/duplex/test_duplex_lease.py","tests/engine/duplex/test_session_manager.py","tests/engine/duplex/test_session_runner.py","tests/entrypoints/duplex/test_duplex_serving.py","tests/entrypoints/openai/test_duplex_session_attachment.py","tests/entrypoints/test_duplex_omni.py","vllm_omni/engine/duplex/messages.py","vllm_omni/engine/duplex/session/append_task.py","vllm_omni/engine/duplex/session/context.py","vllm_omni/engine/duplex/session/engine_session.py","vllm_omni/engine/duplex/session/lease.py","vllm_omni/engine/duplex/session/manager.py","vllm_omni/engine/duplex/session/runner.py","vllm_omni/engine/duplex_omni_engine.py","vllm_omni/entrypoints/duplex/serving.py","vllm_omni/entrypoints/duplex/session_attachment.py","vllm_omni/entrypoints/duplex_omni.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, request","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7784,"title":"[Bugfix] Refresh duplex client defaults after session updates","body":"## Purpose\n\nAddresses **Issue 13** in #7636: `InlineDuplexClient` keeps using stale implicit audio defaults after an accepted session update, so subsequent audio can be decoded with the wrong format/sample rate or classified with the old speech threshold.\n\n### Root cause and fix\n\nThe shared client dispatcher adopts `session.created` / `session.resumed`, but not `session.updated`. Adopt the acknowledged session payload there so the inline client's existing `_input_defaults()` reads current configuration. Sending a patch alone, or receiving a rejected update, does not change defaults.\n\n### Reproduction\n\n```bash\npython -m pytest tests/clients/test_duplex_client.py::test_inline_input_defaults_change_only_after_session_updated -q\n```\n\nThe added cases cover flat/nested audio configuration and accepted/rejected updates. Both accepted-update cases fail on the original base; rejected-update cases already pass.\n\n## Test Plan\n\n**vLLM Version:** 0.29.0; Python 3.12; PyTorch 2.13.0.\n\n**vLLM-Omni Commit:** rebased onto `823af923`; original reproduction and combined real-model smoke used `9fbc36ab`.\n\nL1 regression (existing `core_model` / `cpu` module; no CI wiring changes):\n\n```bash\npython -m py","changed_files":["tests/clients/test_duplex_client.py","vllm_omni/clients/duplex.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7782,"title":"[Bugfix] Preserve cancellation in duplex session control queues","body":"## Purpose\n\nAddresses **Issue 6** in #7636: a queued session-control operation can run during shutdown because awaiting its cancelled predecessor swallows `CancelledError`.\n\n### Root cause and fix\n\nNarrow the predecessor catch from `BaseException` to `Exception`. Cancellation propagates through the control queue; an ordinary predecessor failure still permits the next operation. Existing task tracking reports the original failure.\n\n### Reproduction\n\nThe added regression exercises shutdown, predecessor cancellation, tail cancellation, and an ordinary failure:\n\n```bash\npython -m pytest tests/engine/duplex/test_session_manager.py::test_control_queue_preserves_cancellation -q\n```\n\nWith the test applied to the original base, the three cancellation cases fail; the ordinary-failure case passes.\n\n## Test Plan\n\n**vLLM Version:** 0.29.0; Python 3.12; PyTorch 2.13.0.\n\n**vLLM-Omni Commit:** rebased onto `823af923`; original reproduction and combined real-model smoke used `9fbc36ab`.\n\nL1 regression (existing `core_model` / `cpu` module; no CI wiring changes):\n\n```bash\npython -m pytest tests/engine/duplex/test_session_manager.py -q\n```\n\nRequires the repository's vLLM-Omni development/test depende","changed_files":["tests/engine/duplex/test_session_manager.py","vllm_omni/engine/duplex/session/manager.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: queue","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7781,"title":"[Core][Model] Enable Qwen3-TTS MRV2 and optimize the TTS pipeline","body":"## Summary\n\nEnable opt-in Qwen3-TTS on native Model Runner V2 with its pipeline optimizations. V1 remains the default; vLLM stays at **0.29.0**, with no dependency changes. Extracted from #4582; #7088 remains independent.\n\n- Add shared MRV2 AR/generation runners, request GPU state, chunk readiness, and ordered asynchronous output delivery with cancellation/error handling.\n- Adapt Qwen3-TTS Talker/MTP and Code2Wav, retaining per-request sampling state and GPU-resident intermediate data.\n- Optimize request snapshots, compact reference-audio storage, fixed-token projections and token uploads; align API/model cache entry limits to 1024 (API payload limit 512 MiB).\n- Provide opt-in basic/B2 profiles and an experimental B4 profile. B2 sets both decode batch capacity and captured graph shapes. MPS is optional deployment documentation, with no throughput-benefit claim.\n\nQwen3-Omni/MOSS activation and experimental wakeup/transport switches are outside this PR.\n\n## Latest correctness and scope fixes\n\nHead: `b9648399f` (base used for validation and diff accounting: `d4ffde1a6`).\n\n- Preserve request-scoped reference-code tensors after selecting a batch list element. A mixed prefill/decode batc","changed_files":[".buildkite/common/ci_source_file_dependencies.yml",".buildkite/cuda/test-ready.yml","docs/configuration/stage_configs.md","tests/config/test_config_factory.py","tests/config/test_omni_config.py","tests/core/sched/test_generation_scheduler_restore.py","tests/core/sched/test_omni_ar_scheduler_aborted_queue_sweep.py","tests/core/sched/test_omni_ar_scheduler_logprobs.py","tests/core/sched/test_omni_ar_scheduler_output_policy.py","tests/core/sched/test_omni_ar_scheduler_streaming.py","tests/core/sched/test_omni_ar_scheduler_unified_decode_deferral.py","tests/core/sched/test_omni_sched_deferred_free_fence.py","tests/core/sched/test_omni_scheduler_mixin_shared.py","tests/core/sched/test_omni_scheduler_ready_inbox.py","tests/core/sched/test_omni_scheduler_streaming_input_counter.py","tests/core/sched/test_omni_scheduling_coordinator.py","tests/e2e/offline_inference/test_qwen3_tts_base.py","tests/e2e/online_serving/test_qwen3_tts_base.py","tests/engine/test_orchestrator.py","tests/engine/test_output_processor.py","tests/engine/test_output_processor_mrv2_text.py","tests/engine/test_request_snapshot.py","tests/engine/test_single_stage_mode.py","tests/engine/test_stage_engine_args.py","tests/engine/test_terminal_stage_metrics_order.py","tests/entrypoints/openai_api/test_ref_audio_cache.py","tests/model_executor/models/qwen3_tts/test_code_predictor_dtype.py","tests/model_executor/models/qwen3_tts/test_cuda_graph_decoder.py","tests/model_executor/models/qwen3_tts/test_qwen3_tts_output_async_cuda.py","tests/model_executor/models/qwen3_tts/test_qwen3_tts_stateless_capture.py","tests/model_executor/models/qwen3_tts/test_qwen3_tts_talker_preprocess.py","tests/model_executor/models/qwen3_tts/test_qwen3_tts_talker_ref_codes.py","tests/model_executor/models/qwen3_tts/test_qwen3_tts_talker_silence_ban.py","tests/model_executor/stage_input_processors/test_qwen3_tts_async_chunk.py","tests/worker/test_min_tokens_stop_ids_guard.py","tests/worker/test_omni_connector_mixin.py","tests/worker/test_omni_gpu_model_runner.py","tests/worker/test_omni_v2_profile_dispatch.py","tests/worker/test_tts_sampling_seed.py","tests/worker/test_worker_base.py","tests/worker_v2/__init__.py","tests/worker_v2/test_intermediate_buffer.py","tests/worker_v2/test_intermediate_buffer_cuda_residency.py","tests/worker_v2/test_omni_ar_model_runner.py","tests/worker_v2/test_omni_data_plane.py","tests/worker_v2/test_omni_generation_model_runner.py","tests/worker_v2/test_omni_gpu_model_runner.py","tests/worker_v2/test_omni_model_state.py","vllm_omni/config/model.py","vllm_omni/config/omni_config.py","vllm_omni/config/stage_config.py","vllm_omni/core/sched/omni_ar_scheduler.py","vllm_omni/core/sched/omni_generation_scheduler.py","vllm_omni/core/sched/omni_scheduler_mixin.py","vllm_omni/core/sched/omni_scheduling_coordinator.py","vllm_omni/core/sched/output.py","vllm_omni/data_entry_keys.py","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2_b4.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2_single_gpu.yaml"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline, default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/model.py","vllm_omni/config/omni_config.py","vllm_omni/config/stage_config.py","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2_b4.yaml","vllm_omni/deploy/qwen3_tts_high_concurrency_mrv2_single_gpu.yaml"],"selected_from_description":true},{"owner":"scheduler","changed_files":["vllm_omni/core/sched/omni_ar_scheduler.py","vllm_omni/core/sched/omni_generation_scheduler.py","vllm_omni/core/sched/omni_scheduler_mixin.py","vllm_omni/core/sched/omni_scheduling_coordinator.py","vllm_omni/core/sched/output.py"],"selected_from_description":false}]}} +{"pr":7779,"title":"[Misc] Deprecate VLLM_OMNI_VOXCPM_CODE_PATH","body":"## Purpose\r\n\r\nPart of #6232 (second half of the removal package). Follows #7776.\r\n\r\n`VLLM_OMNI_VOXCPM_CODE_PATH` still works but now logs a one-time deprecation warning pointing to `pip install voxcpm` (or `pip install -e ` for a source checkout). The TTS example README, its docs copy and the VoxCPM2 example docstring no longer suggest it, and the ImportError message stops recommending it. The inventory row stays until the variable is actually removed.\r\n\r\n`XCODEC1_PATH` is left out. The plan in #6232 was to deprecate it here too, but its replacement isn't reachable from the CLI: `model` is a shared field, so `build_stage_runtime_overrides` drops `stage_1_model`, and `--stage-overrides '{\"1\": {\"model\": ...}}'` can't point the XCodec1 stage at a local checkpoint. The only alternative today is a copied deploy YAML, so it probably needs a per-stage model override before it can be deprecated.\r\n\r\nThe touched markdown files also pick up markdownlint fixes (blank lines around headings/fences, table separators, a missing `text` fence language in the docs copy), and the SPDX hook updated the headers on the two touched Python files.\r\n\r\n## Test Plan\r\n\r\n```bash\r\npytest tests/model_executo","changed_files":["docs/user_guide/examples/offline_inference/text_to_speech.md","examples/offline_inference/text_to_speech/README.md","examples/offline_inference/text_to_speech/voxcpm2/end2end.py","tests/model_executor/models/voxcpm2/test_voxcpm2_import_utils.py","vllm_omni/model_executor/models/voxcpm2/voxcpm2_import_utils.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:voxcpm2","path":"repos/vllm-omni/models/voxcpm2/rules.md","reason":"title/body model: voxcpm2","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: yaml, deploy, override","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: checkpoint","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/voxcpm2/voxcpm2_import_utils.py"],"selected_from_description":true}]}} +{"pr":7777,"title":"[XPU][Bugfix] Make AR-Diffusion KV preallocation device-agnostic","body":"## Purpose\r\n\r\n`ARDiffusionModelRunner._available_memory_bytes()` raises on any non-CUDA device and reads free memory via `torch.cuda.mem_get_info`. It runs during `load_model()`, so every AR-Diffusion model fails at load time on XPU:\r\n\r\n```\r\nRuntimeError: AR-Diffusion KV preallocation currently requires a CUDA device\r\n```\r\n\r\n\r\n`get_free_memory` is declared on `OmniPlatform` (`platforms/interface.py:296`) and implemented for CUDA, ROCm, XPU, NPU and MUSA. The `torch.accelerator` replacements follow and existing usage in `diffusion/ipc.py` and `diffusion/executor/uniproc_executor.py`. No behavior change on CUDA.\r\n\r\n\r\n**vLLM Version:** v0.29.0\r\n\r\n**vLLM-Omni Commit:** 6bda549a\r\n\r\n## Test Result\r\n\r\nBefore: `RuntimeError: AR-Diffusion KV preallocation currently requires a CUDA device`.\r\n\r\nAfter: runs to completion, exit 0.\r\n\r\n```\r\nAR-Diffusion KV cache: blocks=24 layers=40 local_kv_heads=10 head_size=128\r\n tokens/frame=880 frames/block=2 window=9 sink=0 resident_capacity=1\r\nSAVED_MP4=.../dreamzero_prediction.mp4 frames=17 expected~17 duration~3.4s\r\n```\r\n\r\n","changed_files":["vllm_omni/diffusion/models/dreamzero/pipeline_dreamzero.py","vllm_omni/experimental/ar_diffusion/runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/dreamzero/pipeline_dreamzero.py"],"selected_from_description":true}]}} +{"pr":7775,"title":"[CI/Build][NPU] Add E2E test on Ascend A3","body":"PLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nAdd E2E test coverage for Diffusion models on Ascend A3 NPU. This PR:\r\n\r\n- registers Qwen3-Omni, HunyuanVideo-1.5, Qwen-Image, Wan2.2, and HunyuanImage in `.buildkite/npu/test-npu-ready.yml`\r\n- adds `\"npu\": \"A3\"` to the `hardware_test` decorator of the corresponding online serving tests\r\n- excludes Qwen3-Omni prefix caching tests (NPU forces block size 128 vs assumed 16)\r\n\r\n## Test Plan\r\n\r\nAdd the `npu-test` label to trigger NPU CI.\r\n\r\n**vLLM Version:** 0.29.0\r\n\r\n**vLLM-Omni Commit:** bdc64cce74aaa2587698a6aa14832a8cdf7b71df + this test change\r\n\r\nIndividual test commands for local verification:\r\n\r\n```bash\r\n# Qwen3-Omni\r\npytest -s -v tests/e2e/online_serving/test_qwen3_omni.py \\\r\n -m 'core_model and npu and A3 and omni' --run-level 'core_model'\r\n\r\n# HunyuanVideo-1.5\r\npytest -s -v tests/e2e/online_serving/test_hunyuan_video_15_expansion.py \\\r\n -m 'core_model and diffusion' --run-level 'core_model'\r\n\r\n# Qwen-Image\r\npytest -s -v tests/e2e/online_serving/test_qwen_image.py \\\r\n -m 'core_model' --run-level 'core_model'\r\n\r\n# Wan2.2\r\npytest -s -v tests/e2e/online_serving/test_wan22_t2v.py \\\r\n -m 'core_model' --run-level 'core_mode","changed_files":[".buildkite/cuda/test-ready.yml",".buildkite/npu/test-npu-ready.yml","tests/e2e/online_serving/test_hunyuan_image.py","tests/e2e/online_serving/test_qwen3_omni.py","tests/e2e/online_serving/test_qwen_image.py","tests/e2e/online_serving/test_wan22_t2v.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7773,"title":"[CI][Omni-DuplexEval] Add nightly scoring CI guard","body":"\r\n\r\n\r\n## Purpose\r\n\r\nAdd a nightly scoring-regression CI guard for the [Hothan/Omni-DuplexEval](https://huggingface.co/datasets/Hothan/Omni-DuplexEval) benchmark (RTD: 6 subtasks, PR: 3 subtasks) on MiniCPM-o-4.5 duplex accuracy. The guard runs the full `vllm bench omni-duplex-eval` pipeline (generate → evaluate → summarize) against a real server and asserts:\r\n\r\n- **P0**: `protocol_pin` stability (guard tied to a specific HRR commit)\r\n- **P0**: Sample count matches config expectations\r\n- **P1**: Score file count matches expected per-split output\r\n- **P2**: RTD mean content / temporal scores vs thresholds\r\n- **P3**: PR mean all-success rate vs thresholds\r\n\r\nAlso provides **15 CPU mock integration tests** (≈2s, no GPU/model/judge needed) that exercise the real `test_omni_duplex_eval_ci()` guard function by stubbing `_run_cli` and pre-writing fake score files — covering all assertion paths, CLI argument assembly, judge endpoint resolution, and failure modes. The extra test asserts the `--omni` flag is passed in the `omni-duplex-eval` subcommand argv.\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** v0.29.x (current development)\r\n\r\n**vLLM-Omni Commit:** `873e9bff7` (bas","changed_files":[".buildkite/common/ci_source_file_dependencies.yml",".buildkite/cuda/test-nightly.yml",".buildkite/npu/test-npu-nightly.yml",".gitignore","tests/e2e/accuracy/omni_duplex_eval/README.md","tests/e2e/accuracy/omni_duplex_eval/__init__.py","tests/e2e/accuracy/omni_duplex_eval/conftest.py","tests/e2e/accuracy/omni_duplex_eval/omni_duplex_eval_ci_config.json","tests/e2e/accuracy/omni_duplex_eval/test_omni_duplex_eval_ci.py","tests/e2e/accuracy/omni_duplex_eval/test_omni_duplex_eval_ci_mock.py","tests/helpers/fixtures/config.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, pipeline","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, endpoint","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7771,"title":"[CI/Build] Restrict ERNIE fused RoPE tests to NVIDIA CUDA","body":"## Purpose\n\n### What broke\n\nThe AMD `Diffusion · Model Test` selected the ERNIE-Image fused RoPE tests and failed four cases because `try_fused_qk_rotary_emb(...)` correctly returned `None` on ROCm.\n\nObserved in AMD builds [#12244](https://buildkite.com/vllm/vllm-omni-amd-ci/builds/12244) and [#12243](https://buildkite.com/vllm/vllm-omni-amd-ci/builds/12243), on different MI300X workers:\n\n```text\n4 failed, 56 passed, 24 skipped, 3203 deselected\n```\n\n### Reproduction\n\nOn a ROCm worker:\n\n```bash\npytest -sv tests/diffusion/models/ \\\n -m \"core_model and cuda and not (cards_2 or cards_3 or cards_4 or cards_5 or cards_6 or cards_7 or cards_8)\" \\\n --run-level core_model\n```\n\n### Root cause\n\nPyTorch exposes ROCm devices through the `torch.cuda` namespace, so `torch.cuda.is_available()` is true on ROCm. The tests therefore ran even though the implementation intentionally enables this fused kernel only when `current_omni_platform.is_cuda()` is true.\n\nThe same tests execute successfully on NVIDIA CUDA in build [#15561](https://buildkite.com/vllm/vllm-omni/builds/15561).\n\n### Fix\n\nUse the project platform abstraction in all five GPU-dependent skip guards. This keeps the NVIDIA fused-kernel c","changed_files":["tests/diffusion/models/ernie_image/test_ernie_image_fused_rope.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7770,"title":"Isolate Qwen-Image 2.1 prefill and decode scheduling","body":"## Summary\n\nPrevent new Qwen-Image 2.1 prefill requests from joining an active decode batch and failing the batch with `mixed KV-cache phases`. Compatible first-step requests still start together; late arrivals wait until the running batch finishes. Other models retain mixed-phase admission.\n\nAlso pad the negative image mask to its own prompt length for CFG step execution and document the scheduling restriction.\n\nTargets `qwen-image-2.1` (#7759). No test files added.\n\n## Validation\n\n- 14 targeted scheduler/preprocessing checks passed; existing scheduler and step-pipeline tests: 101 passed, 3 skipped.\n- Real checkpoint: two requests start together (4/6 steps), then a third arrives after their first step. Before the fix, all three fail at CFG 1. Afterward, all finish with 1024×1024 RGBA outputs at CFG 1 and 4; the delayed request exactly matches its serial reference.\n\nAI assistance: Codex.\n","changed_files":["recipes/Qwen/Qwen-Image-2.1.md","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/request.py","vllm_omni/diffusion/sched/step_scheduler.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py, vllm_omni/diffusion/request.py, vllm_omni/diffusion/sched/step_scheduler.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/request.py","vllm_omni/diffusion/sched/step_scheduler.py"],"selected_from_description":false}]}} +{"pr":7769,"title":"Align Qwen-Image 2.1 preprocessing, CFG defaults and prefix cache","body":"## Summary\n\nAlign Qwen-Image 2.1 preprocessing and prefix-cache ownership with [Diffusers #14804](https://github.com/huggingface/diffusers/pull/14804), and fix input/default handling:\n\n- Use deterministic `` tags, left padding, and a space for empty prompts. Composite RGBA references over white for the vision encoder while preserving alpha for the VAE and edit example.\n- Accept tensor/ndarray condition images without ambiguous truth-value checks.\n- Preserve explicit `true_cfg_scale=0` in request and step execution; default to 4 only for `None`.\n- Clone prefix K/V so the cache owns its storage instead of retaining full prefill tensors.\n\nTargets `qwen-image-2.1` (#7759). No test files added.\n\n## Validation\n\n- Actual checkpoint processor inputs exactly match the Diffusers reference for unequal-length/empty prompts, RGBA editing, and two reference images. Tensor/ndarray preprocessing and CFG values `None/0/1/4` pass targeted checks.\n- Prefix caches own only their required storage; prefill/decode attention outputs are unchanged for batches of 1 and 2 in a small attention probe.\n- Real-checkpoint inference: BF16, eager, VAE tiling, 4 steps, seed 42. Seven 1024×1024 RGBA outputs c","changed_files":["examples/offline_inference/image_to_image/qwen_image_21_edit.py","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py, vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","vllm_omni/diffusion/models/qwen_image_21/qwen_image_21_transformer.py"],"selected_from_description":true}]}} +{"pr":7768,"title":"Align Qwen-Image-2.1 VAE normalization with Diffusers","body":"## Motivation\n\nAlign Qwen-Image-2.1 VAE normalization with the Diffusers reference in https://github.com/huggingface/diffusers/pull/14804. The current low-precision normalization produces different encoder and decoder outputs with the same weights and inputs.\n\n## Changes\n\nCompute `F.normalize` in FP32 for low-precision inputs, then cast back to the input dtype before applying scale, weight, and bias, matching the reference implementation.\n\nTargets `qwen-image-2.1` (#7759). No test files added.\n\n## Validation\n\nReal VAE weights, BF16, one 1024×1024 RGBA sample: encoder outputs and same-latent decoder outputs exactly match Diffusers, both untiled and with 512-pixel tiles (384-pixel stride). Reference: `21b3a41d7f82acbe69fc6f00c1caf27d37c31f9a`; its spatial compression ratio was set to 16 to match the architecture. This validates numerical alignment for the tested case.\n\nAI assistance: Codex.\n","changed_files":["vllm_omni/diffusion/models/qwen_image_21/autoencoder_kl_qwenimage21.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: vae","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/autoencoder_kl_qwenimage21.py"],"selected_from_description":true}]}} +{"pr":7766,"title":"Preserve Qwen-Image 2.1 condition latent order","body":"## Summary\n\nFix condition-image latents being assigned to the wrong request when each request generates multiple images. Prompt embeddings are ordered `A,A,B,B`, while the previous batch repetition produced condition latents in `A,B,A,B` order. Use `repeat_interleave` to keep them aligned.\n\nTargets `qwen-image-2.1` (#7759). One-line source change; no test files added.\n\n## Validation\n\n- Tensor checks reproduced the mismatch before the fix; all 12 request/output-count and condition-image-slot combinations align afterward. Invalid batch sizes remain rejected and text-only requests are unaffected.\n- Real-model smoke: two requests ran in the same batch and each returned two 1024×1024 RGBA images (BF16, eager, VAE tiling, CFG 4, seed 42, 4 steps).\n\nAI assistance: Codex.\n","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"selected_from_description":true}]}} +{"pr":7765,"title":"Use pre-norm text features for Qwen-Image 2.1","body":"## Summary\n\nUse the text encoder's final pre-RMSNorm features, as expected by the Qwen-Image 2.1 DiT. Align with [Diffusers #14804](https://github.com/huggingface/diffusers/pull/14804) by temporarily bypassing the final norm and removing the hook in `finally`; Transformers 5.x otherwise returns normalized features.\n\nTargets `qwen-image-2.1` (#7759). Source-only change; no test files added.\n\n## Validation\n\n- BF16, vLLM 0.29.0 / Transformers 5.14.1: real-checkpoint features exactly match the captured norm input for text-only, padded text batches, and image-conditioned prompts. No forward hooks remain after these calls.\n- Text-to-image and image-edit smoke runs both produced 1024×1024 RGBA images: eager, VAE tiling, CFG 4, seed 42, 4 steps. No image-quality improvement claim.\n\nAI assistance: Codex.\n","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: checkpoint","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: text to image, vae, dit","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"selected_from_description":true}]}} +{"pr":7764,"title":"Separate Qwen-Image 2.1 batches by condition image layout","body":"## Summary\n\nFix batch-wide failures when Qwen-Image 2.1 requests have incompatible condition-image layouts. Set `batch_compatibility_key` from the ordered, resized image dimensions so incompatible requests run separately and compatible requests still batch together.\n\nTargets `qwen-image-2.1` (#7759).\n\n## Validation\n\n- Regression tests: **13 failed / 8 passed before → 21 passed after**, covering request and step schedulers.\n- Real-model request batching via Slurm: **6/6 requests succeeded**, including mixed layouts and a compatible two-request batch. Single GB200, vLLM 0.29.0, BF16/eager, VAE tiling, 4 steps, 1024×1024.\n- Ruff and targeted repository checks passed; full pre-commit was not rerun. Mixed prefill/decode-phase admission remains a separate issue.\n\nAI assistance: Codex.\n","changed_files":["tests/diffusion/models/qwen_image_21/test_batch_compatibility.py","vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:qwen-image","path":"repos/vllm-omni/models/qwen-image/rules.md","reason":"title/body model: qwen-image","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image_21/pipeline_qwen_image_21.py"],"selected_from_description":true}]}} +{"pr":7758,"title":"[CI/Build][MiniCPM-o] Fix the Nightly tests","body":"for fixing #7701 and #7702\r\n\r\n1. `run_benchmark.py::test_performance_benchmark[test_minicpmo_4_5-seed-tts]` performance enhanced but still abit behind the gate\r\n2. `test_minicpmo_4_5_duplex_seed_tts_wer_bench` fixed\r\n3. `test_nemotron_voicechat_duplex.py` skipped due the the porting of Nemontron is not yet done\r\n\r\nAnd there are 3 testcases were accidentially changed the backend API to v1/realtime?duplex=1 in PR #7413 , this PR restored them back to v1/chat/completions\r\n\r\n1. `test_minicpmo_4_5`\r\n2. `test_minicpmo_4_5_a2`\r\n3. `test_minicpmo_4_5_ready`\r\n\r\n## Summary\r\n\r\nNightly build 15365 (2026-09-16) failed three MiniCPM-o 4.5 jobs after #7413 (Unified\r\nFull-duplex Framework) landed:\r\n\r\n| Job | Failed test | Cause |\r\n|---|---|---|\r\n| perf | `run_benchmark.py::test_performance_benchmark[test_minicpmo_4_5-seed-tts]` | #7413 moved the perf configs from `/v1/chat/completions` to `/v1/realtime`; the concurrency-8 sweep exceeds `duplex_session.max_sessions=4` and 3 requests time out on `duplex_session_capacity_exhausted` |\r\n| accuracy | `test_minicpmo_4_5_duplex_seed_tts_wer_bench` (both params) | The test now exercises native per-unit duplex; the Talker's turn-end chunk stops at its 1 s ","changed_files":["tests/dfx/perf/tests/test_minicpmo_4_5.json","tests/dfx/perf/tests/test_minicpmo_4_5_ready.json","tests/e2e/online_serving/test_nemotron_voicechat_duplex.py","tests/model_executor/models/minicpmo_4_5/test_talker_batching.py","vllm_omni/distributed/omni_connectors/adapter.py","vllm_omni/model_executor/models/minicpmo_4_5/__init__.py","vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_tts.py","vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:nemotron-voicechat","path":"repos/vllm-omni/models/nemotron-voicechat/rules.md","reason":"title/body model: nemotron-voicechat","quick_map_status":"ok","read_required":false},{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/minicpmo_4_5/__init__.py, vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_tts.py, vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minicpmo_4_5/__init__.py","vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_tts.py","vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py"],"selected_from_description":false},{"owner":"distributed","changed_files":["vllm_omni/distributed/omni_connectors/adapter.py"],"selected_from_description":false}]}} +{"pr":7756,"title":"[Bugfix] Fix GLM-TTS DiT float32 crash with flash-attn backends","body":"## Purpose\n\nFixes #7620. GLM-TTS decodes its DiT stage in float32 (the wrapper keeps the Euler ODE state in float32). When attention runs on an automatically selected FlashAttention-family backend, decoding crashes:\n\n```\nRuntimeError: mha_fwd, ... FlashAttention only supports fp16, bf16, and fp8_e4m3 data type\n```\n\n**Repro:** run GLM-TTS decoding on a GPU where an incompatible attention backend (FlashAttention / FlashInfer / cuDNN) is auto-selected; the first float32 attention call fails. I have no local GPU, so GPU e2e is left to the L2/L3 CI pipeline.\n\n**Root cause:** `DiTAttention` in `vllm_omni/model_executor/models/glm_tts/glm_tts_dit.py` forwards float32 Q/K/V to whatever backend was selected, with no dtype guard.\n\n**Fix:** mirror the MiniMax Music 3 fix (#7354). When the attention backend was auto-selected, is float32-incompatible, and the input is float32, route through `torch.nn.functional.scaled_dot_product_attention`. Explicit backend choices keep the shared layer's fail-fast contract. The routing decision is computed once in `__init__` (no per-call overhead).\n\n## Test Plan\n\n**vLLM Version:** 0.29.0\n\n**vLLM-Omni Commit:** 8c3d3407 (main base)\n\n- New L1 CPU regression tes","changed_files":["tests/model_executor/models/test_glm_tts_dit_attention.py","vllm_omni/model_executor/models/glm_tts/glm_tts_dit.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-music3","path":"repos/vllm-omni/models/minimax-music3/rules.md","reason":"title/body model: minimax-music3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: model executor","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/glm_tts/glm_tts_dit.py"],"selected_from_description":true}]}} +{"pr":7755,"title":"[CI/Build] Retarget skipped HiDream and MammothModa2 E2E tests to current issues","body":"\nPLEASE FILL IN THE PR DESCRIPTION HERE.\n\n## Purpose\n\nRetarget existing skip markers on two weekly/full-model E2E tests so they point at the issues that currently track the failures, instead of stale or closed bugs.\n\n- `test_hidream_i1_image`: `#4662` was closed (forced 1024×1024 rescale is upstream HiDream default). Custom resolution is now tracked by RFC https://github.com/vllm-project/vllm-omni/issues/7753.\n- `test_mammothmoda2_t2i_e2e`: skip reason updated from `#3201` to https://github.com/vllm-project/vllm-omni/issues/7718 (`AssertionError: No image tensor found in pipeline output`).\n\nThis does not unskip the tests and does not fix the underlying product issues.\n\nRelates to #7753\nRelates to #7718\n\n## Test Plan\n\nSkip-reason-only change; no new tests.\n\n**vLLM Version:** N/A (test skip markers only)\n\n**vLLM-Omni Commit:** `skip` @ `95bddee21`\n\nConfirm collection still skips the two cases:\n\n```bash\npytest -q --collect-only tests/e2e/online_serving/test_hidream_i1_full_expansion.py::test_hidream_i1_image\npytest -q --collect-only tests/e2e/offline_inference/test_mammoth_moda2_expansion.py::test_mammothmoda2_t2i_e2e\n```\n\nDo not re-enable `--run-level ful","changed_files":["tests/e2e/offline_inference/test_mammoth_moda2_expansion.py","tests/e2e/online_serving/test_hidream_i1_full_expansion.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:mammoth-moda2","path":"repos/vllm-omni/models/mammoth-moda2/rules.md","reason":"title/body model: mammoth-moda2","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline, default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7752,"title":"[Model] Accelerate Breeze-TTS-2 non-streaming inference","body":"## Purpose\n\nAccelerate Breeze-TTS-2 non-streaming inference with batched depth decoding, compiled layers, a CUDA graph covering all 15 RVQ steps, and the Qwen3-TTS stateful codec. The throughput deployment uses 32 sequence slots, 8 GiB of backbone KV cache, and fixed 16-frame codec chunks.\n\nBuilds on #7091 and preserves @YouMuSaMa's original commit `a8ccfbab26d89a640ae9e1c623dd1af14013ed4e`. The implementation retains the `breeze_tts_2` package, adapter, pipeline, stage identifiers and existing deployment path. Speaker/reference aliases, architecture-only adapter detection and bounded cancellation cleanup remain supported. It follows main's AsyncOmniBase refactor, fixes the nucleus-sampling boundary, and preserves the existing Qwen/MOSS MTP batch interface.\n\nThe latest profiling follow-up captures first-code sampling and the depth decoder's random draws. First-code graphs group requests with the same sampling parameters while retaining the original FP32 head, CFG/history arithmetic and one-row sampler. Request-owned seeds and offsets move through graph placeholders without changing the RNG draw geometry. Padding, unconditional companions and greedy decoding do not advance request R","changed_files":[".buildkite/cuda/test-merge.yml",".buildkite/cuda/test-ready.yml","benchmarks/tts/evidence/breeze_tts_2_20260918.json","benchmarks/tts/evidence/breeze_tts_2_sampling_graphs_20260918.json","benchmarks/tts/model_configs.yaml","docs/design/feature/preprocess_phase_contract.md","docs/models/supported_models.md","docs/source/performance/breeze_tts_2_gpu_bubbles.png","examples/offline_inference/text_to_speech/breeze_tts_2/end2end.py","examples/online_serving/text_to_speech/README.md","recipes/BreezeBlue/Breeze-TTS-2.md","requirements/common.txt","tests/config/test_environment_variables.py","tests/core/sched/test_omni_cfg_ar_scheduler.py","tests/e2e/online_serving/test_breeze_tts_2.py","tests/entrypoints/openai_api/test_breeze_tts_2_adapter.py","tests/entrypoints/openai_api/test_serving_speech.py","tests/entrypoints/openai_api/test_tts_detection.py","tests/entrypoints/test_async_omni.py","tests/entrypoints/test_omni_entrypoints.py","tests/helpers/assertions.py","tests/helpers/client.py","tests/helpers/tests/test_assertions.py","tests/model_executor/models/breeze_tts_2/__init__.py","tests/model_executor/models/breeze_tts_2/test_audio_tokenizer.py","tests/model_executor/models/breeze_tts_2/test_batching.py","tests/model_executor/models/breeze_tts_2/test_codec_loading.py","tests/model_executor/models/breeze_tts_2/test_configuration.py","tests/model_executor/models/breeze_tts_2/test_instruction.py","tests/model_executor/models/breeze_tts_2/test_local_modules.py","tests/model_executor/models/breeze_tts_2/test_model_registration.py","tests/model_executor/models/breeze_tts_2/test_pipeline_registration.py","tests/model_executor/models/breeze_tts_2/test_prompt_builder.py","tests/model_executor/models/breeze_tts_2/test_reference_voice.py","tests/model_executor/models/breeze_tts_2/test_repetition_penalty.py","tests/model_executor/models/breeze_tts_2/test_stage_input_processor.py","tests/model_executor/models/breeze_tts_2/test_streaming.py","tests/model_executor/models/breeze_tts_2/test_talker_state_machine.py","tests/model_executor/models/breeze_tts_2/test_weight_loading.py","tests/model_executor/models/breeze_tts_2_helpers.py","tests/model_executor/models/qwen3_tts/test_qwen3_tts_code2wav.py","tests/model_executor/models/test_breeze_tts_2.py","tests/model_executor/models/test_breeze_tts_2_first_code_integration.py","tests/model_executor/models/test_breeze_tts_2_first_code_sampler.py","tests/model_executor/models/test_breeze_tts_2_graphs.py","tests/model_executor/models/test_breeze_tts_2_preprocess.py","tests/model_executor/models/test_breeze_tts_2_registration.py","tests/model_executor/models/test_breeze_tts_2_sampling_eos.py","tests/worker/test_omni_gpu_model_runner.py","vllm_omni/config/environment_variable_inventory.py","vllm_omni/core/sched/omni_cfg_ar_scheduler.py","vllm_omni/deploy/breeze_tts.yaml","vllm_omni/deploy/breeze_tts_2.yaml","vllm_omni/deploy/breeze_tts_2_throughput.yaml","vllm_omni/deploy/breeze_tts_throughput.yaml","vllm_omni/engine/arg_utils.py","vllm_omni/entrypoints/async_omni.py","vllm_omni/entrypoints/async_omni_base.py","vllm_omni/entrypoints/client_request_state.py","vllm_omni/entrypoints/openai/serving_speech.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:qwen3-tts","path":"repos/vllm-omni/models/qwen3-tts/rules.md","reason":"title/body model: qwen3-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/environment_variable_inventory.py","vllm_omni/deploy/breeze_tts.yaml","vllm_omni/deploy/breeze_tts_2.yaml","vllm_omni/deploy/breeze_tts_2_throughput.yaml","vllm_omni/deploy/breeze_tts_throughput.yaml"],"selected_from_description":true},{"owner":"serving","changed_files":["vllm_omni/entrypoints/async_omni.py","vllm_omni/entrypoints/openai/serving_speech.py"],"selected_from_description":true},{"owner":"distributed","changed_files":["vllm_omni/entrypoints/async_omni.py"],"selected_from_description":false},{"owner":"scheduler","changed_files":["vllm_omni/core/sched/omni_cfg_ar_scheduler.py"],"selected_from_description":false}]}} +{"pr":7750,"title":"[Bugfix] Drain the omni connector output on the main thread in the AR runner","body":"## [Bugfix] Drain the omni connector output on the main thread in the AR runner\r\n\r\nQwen3-Omni-MoE hangs on XPU during decode with async scheduling and TP > 1.\r\n\r\n### Cause\r\n\r\n`get_omni_connector_output()` broadcasts from rank 0 over the TP group's shared-memory ring when `async_chunk` is on and TP > 1. \r\n`GPUARModelRunner.sample_tokens` called it inside the deferred`output_builder()` closure, which async scheduling runs on a background thread,while `execute_model()` broadcasts on the same group from the main thread. \r\nThe broadcast order then differs per rank, and the ring pairs messages positionally, so it skews permanently and every rank blocks:\r\n\r\n```\r\nINFO [shm_broadcast.py:801] No available shared memory broadcast block found in 60 seconds.\r\n```\r\n\r\n### Fix\r\n\r\nDrain the connector on the main thread and attach the result to the built output, leaving no collectives on the builder thread. This matches `gpu_generation_model_runner`, `npu_generation_model_runner` and `npu_ar_model_runner`, which already drain on the main thread, the GPU AR runner was the only one that deferred it.\r\n\r\nStage 0 no longer needs `async_scheduling: false`.\r\n\r\n### Also fixed\r\n\r\n`minicpmo_4_5_8x4090.yaml` s","changed_files":["tests/worker/test_gpu_ar_model_runner.py","vllm_omni/deploy/qwen3_omni_moe.yaml","vllm_omni/worker/gpu_ar_model_runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: yaml","quick_map_status":"ok","read_required":false},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: scheduling","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/deploy/qwen3_omni_moe.yaml"],"selected_from_description":true},{"owner":"distributed","changed_files":["vllm_omni/worker/gpu_ar_model_runner.py"],"selected_from_description":false}]}} +{"pr":7749,"title":"[LingBot World] Speed up VAE decode, FP8 input quant, and default K/V gather","body":"LingBot World's served path spends time on the streaming Wan decoder (repeated convolution input assembly, layout conversions, separate normalization/activation launches, and 96-channel convolutions that cuDNN runs on an sm80 fallback), on inductor-generated FP8 input quantization that loops over each row two or three times, on FA3's paged attention over 1560-token frame blocks, and on a CPU RGB interleave. This PR adds decoder fast paths, an exact RGB layout copy, a one-pass FP8 input quantizer, a Triton 96-channel decoder conv, and turns on the contiguous K/V gather with history staging by default for LingBot. On the latest-main four-H200 benchmark, the decoder and RGB changes improved mean chunk-6–38 cadence from **590.94 to 548.68 ms**. On two H200s, the full stack measured **~892–896 ms** against **950 ms** for main + the decoder/RGB changes alone.\n\n## Changes\n\n- `lingbot_vae_decode_fast_path: exact` reuses sharded convolution input buffers and removes an unnecessary fp32 nearest-upsample round trip. The CPU and two-rank decoder tests require bit-identical output.\n- `lingbot_vae_decode_fast_path: fused` additionally keeps channels-last layouts through the decoder and fuses RMS","changed_files":["tests/diffusion/distributed/test_wan_decoder_fast_path.py","vllm_omni/diffusion/distributed/autoencoders/wan_decoder_fast_path.py","vllm_omni/diffusion/distributed/autoencoders/wan_decoder_kernels.py","vllm_omni/diffusion/distributed/autoencoders/wan_decoder_utils.py","vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py","vllm_omni/diffusion/models/lingbot_world/utils.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:lingbot-world","path":"repos/vllm-omni/models/lingbot-world/rules.md","reason":"title/body model: lingbot-world","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: vae","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/distributed/autoencoders/wan_decoder_fast_path.py","vllm_omni/diffusion/distributed/autoencoders/wan_decoder_kernels.py","vllm_omni/diffusion/distributed/autoencoders/wan_decoder_utils.py","vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py","vllm_omni/diffusion/models/lingbot_world/utils.py"],"selected_from_description":true}]}} +{"pr":7748,"title":"[CI][ROCm] Shorten the blocking CosyVoice test path","body":"#7706 has merged as `1c7476ec`. This branch is rebased onto that merged `main` and now contains only the CosyVoice-specific follow-up commit.\n\n## Summary\n\n- replace the blocking READY CosyVoice matrix with one async-chunk smoke test using `TORCH_SDPA`\n- make the LLM-hosted CosyVoice code2wav DiT install a local diffusion-config context, so `DIFFUSION_ATTENTION_BACKEND` controls the backend at actual model construction time\n- move the complete sync/async, Chinese/English CosyVoice suite to the non-blocking AMD nightly-selectable lane, retaining the default ROCm attention backend and the exit-134 fresh-pod retry\n- reduce the synthetic incremental HiFT sequence from 400 to 144 mel frames while explicitly requiring three post-window truncation steps\n- add Buildkite routing contracts plus a construction-time test that asserts the embedded DiT resolves `TORCH_SDPA`\n\n## Motivation\n\nOn AMD build #12225, the GPU phase took about 48m53s and the CosyVoice job was the sole critical path at 48m46s. Its first request spent 1623.2s compiling one cold AITER MHA module. Without that job in READY, the observed critical path would have been 38m59s.\n\nThe first implementation exported the SDPA environm","changed_files":[".buildkite/amd/test-amd-nightly.yml",".buildkite/amd/test-amd-ready.yml","tests/buildkite/test_amd_pipeline.py","tests/model_executor/models/cosyvoice3/test_cosyvoice3_components.py","tests/model_executor/models/cosyvoice3/test_cosyvoice3_incremental_hift.py","vllm_omni/model_executor/models/cosyvoice3/cosyvoice3_code2wav.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"changed files: vllm_omni/model_executor/models/cosyvoice3/cosyvoice3_code2wav.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/cosyvoice3/cosyvoice3_code2wav.py"],"selected_from_description":false}]}} +{"pr":7745,"title":"[Bugfix][Diffusion] Bound diffusion execute RPCs with the DP wave timeout","body":"## Purpose\n\nFixes #6964. Two concurrent requests with an out-of-range `duration` hang a MiniMax-H3 DLO deployment: the POSTs never return and the API server goes silent while the clients stay connected.\n\nThe worker does report the validation error (a single invalid request fails in 0 s). The gap is that the executor wasn't always prepared to stop waiting for that reply. Of the four request-execution `collective_rpc()` call sites in `multiproc_executor.py`, only `:565` passed a timeout unconditionally: `:607` passed one only when AllGather was active, and `:674` / `:725` passed none. With `timeout=None` the deadline in `_dequeue_one_with_failure_polling()` (`:224`) is `None`, so its `raise TimeoutError` branch is unreachable and the loop retries every 5 s forever, re-checking only `_is_failed` - a flag set when a worker *process* dies (`:461`, `:1041`), not when a live worker's reply never arrives.\n\nThe reported config uses `--dlo-no-use-allgather`, so `:607` supplied no timeout. Concurrency selects a different path rather than introducing a race: one request goes through `execute_request()`, two are routed to `execute_model_batch()` (`:674`), which had no timeout at all.\n\n## Change","changed_files":["tests/diffusion/diffusion_kv/test_worker_contract.py","tests/diffusion/test_multiproc_engine_concurrency.py","vllm_omni/diffusion/executor/multiproc_executor.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/executor/multiproc_executor.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/executor/multiproc_executor.py"],"selected_from_description":true}]}} +{"pr":7744,"title":"[Bugfix] Enable pipeline profiling for LingBot Video","body":"## Purpose\n\nFixes #7741.\n\n`LingBotVideoPipeline` ignores `--enable-diffusion-pipeline-profiler`, so video responses contain no pipeline-level timings. Wire the pipeline into the existing profiler and include a per-request timing snapshot in `DiffusionOutput`.\n\nThe profiler records `text_encoder.forward`, `transformer.forward`, `vae.encode`, and `vae.decode` when those paths execute. Transformer time accumulates across denoising/CFG calls; it is not labelled as the duration of the entire denoising loop. Profiling remains opt-in, and disabled output retains an empty timing dictionary.\n\n## Validation\n\n- LingBot CPU suite: **121 passed**, two GPU tests deselected.\n- Runner, CUDA-profiler control, and video-API CPU suites, run separately from the model suite: **169 passed**, eight tests deselected.\n- The new enabled-mode regression fails on base `2ab5d170` because pipeline timings are absent; disabled-mode coverage passes on base. The test exercises constructor wiring, repeated transformer calls, request resets, latent-only output, timing snapshots and consumption.\n- All applicable pre-commit hooks passed, including Ruff and mypy.\n- RTX 5080, dense 1.3B checkpoint: two T2V requests with","changed_files":["tests/diffusion/models/lingbot_video/test_pipeline_lingbot_video.py","vllm_omni/diffusion/models/lingbot_video/pipeline_lingbot_video.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:lingbot-video","path":"repos/vllm-omni/models/lingbot-video/rules.md","reason":"title/body model: lingbot-video","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/lingbot_video/pipeline_lingbot_video.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/lingbot_video/pipeline_lingbot_video.py"],"selected_from_description":true}]}} +{"pr":7742,"title":"[CI][Qwen3-Omni] Compare structured-config sampling defaults against vLLM normalization","body":"## Purpose\n\nTurn the merge-pipeline step `Omni · Qwen3-Omni Test` green again. `tests/e2e/offline_inference/test_qwen3_omni.py::test_structured_multistage_config_reaches_runtime` (added in #6849) has failed on every main build that ran the step since it landed: 15444, 15507, 15510 and 15523, each at the same assertion (`test_qwen3_omni.py:131`, `assert all(...)` over the sampling defaults), with the other 12 tests in the step passing.\n\nCloses #7732.\n\n## Root cause\n\nThe code2wav stage in `vllm_omni/deploy/qwen3_omni_moe.yaml` is greedy (`temperature: 0.0`) and spells the disabled `top_k` as `-1`. vLLM v0.29.0 `SamplingParams.__post_init__` normalizes greedy sampling to `top_k=0`, `top_p=1.0`, `min_p=0.0` (and quietly accepts `-1` as the legacy disable sentinel), so the runtime `SamplingParams` held by the stage client never equals the raw YAML literal `-1`. The test compared the runtime object against the YAML literals directly, which fails for that one field. The thinker and talker stages match because none of their deploy values are touched by normalization.\n\nThis is a test expectation bug; the deploy values do reach the runtime.\n\n## Change\n\n- Keep the YAML literals as the deploy-","changed_files":["tests/e2e/offline_inference/test_qwen3_omni.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, yaml, deploy","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: sampling","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7737,"title":"[CI][Perf] migrate diffusion DFX benches to vllm bench serve --omni","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nMain changes in this PR:\r\n\r\n### 1. Nightly: add Lingbot video perf case\r\n\r\n- Add `tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json` and wire it into CUDA nightly (`diffusion_lingbot_perf` in `.buildkite/cuda/test-nightly.yml` + `ci_source_file_dependencies.yml`).\r\n\r\n### 2. Migrate DFX diffusion perf cases that use OpenAI image/video APIs to `vllm bench serve --omni` (`run_benchmark.py`)\r\n\r\nMigrated by serving API:\r\n\r\n| Endpoint | Migrated configs |\r\n| --- | --- |\r\n| `/v1/videos` | `test_wan22_i2v_vllm_omni.json`, `test_hunyuanvideo15_t2v_vllm_omni.json`, `test_hunyuanvideo15_i2v_vllm_omni.json`, `test_lingbot_video_vllm_omni.json`, `test_ltx2_vllm_omni.json`, `test_minimax_h3_vllm_omni.json`, Cosmos3 video rows in `test_cosmos3_vllm_omni.json` |\r\n| `/v1/images/generations` | Cosmos3 t2i row in `test_cosmos3_vllm_omni.json` |\r\n\r\nBuildkite CUDA/NPU nightly perf steps for the migrated configs now call `run_benchmark.py` with `BENCHMARK_DIR` (instead of `run_diffusion_benchmark.py` / `DIFFUSION_BENCHMARK_DIR`).\r\n\r\n**Kept on `run_diffusion_benchmark.py` (chat / custom):**\r\n\r\n| Reason | Configs ","changed_files":[".buildkite/common/ci_source_file_dependencies.yml",".buildkite/cuda/test-nightly.yml",".buildkite/cuda/test-weekly.yml",".buildkite/npu/test-npu-nightly.yml","docs/contributing/ci/test_examples/l4_performance_tests.inc.md","docs/contributing/ci/test_execution_guide.md","docs/contributing/ci/test_writing_guide.md","recipes/LTX/LTX-2.md","tests/benchmarks/metrics/test_metrics.py","tests/benchmarks/patch/test_patch.py","tests/dfx/conftest.py","tests/dfx/perf/scripts/run_benchmark.py","tests/dfx/perf/scripts/run_diffusion_benchmark.py","tests/dfx/perf/tests/test_cosmos3_vllm_omni.json","tests/dfx/perf/tests/test_hunyuanvideo15_i2v_vllm_omni.json","tests/dfx/perf/tests/test_hunyuanvideo15_t2v_vllm_omni.json","tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json","tests/dfx/perf/tests/test_ltx2_vllm_omni.json","tests/dfx/perf/tests/test_minimax_h3_vllm_omni.json","tests/dfx/perf/tests/test_runner_metadata.py","tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json","tools/nightly/generate_nightly_perf_excel.py","tools/nightly/generate_nightly_perf_html.py","tools/nightly/run_nightly_jobs.sh","vllm_omni/benchmarks/metrics/metrics.py","vllm_omni/benchmarks/patch/patch.py","vllm_omni/diffusion/models/cosmos3/pipeline_cosmos3.py","vllm_omni/diffusion/models/hunyuan_video/pipeline_hunyuan_video_1_5.py","vllm_omni/diffusion/models/hunyuan_video/pipeline_hunyuan_video_1_5_i2v.py","vllm_omni/metrics/definitions.py"],"expected":{"status":"scope_fallback","selected_by":"title_body+changed_files","changed_files_role":"selected_fallback_routes","routes":[{"owner":"model:lingbot-video","path":"repos/vllm-omni/models/lingbot-video/rules.md","reason":"title/body model: lingbot-video","quick_map_status":"ok","read_required":false},{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"changed files: vllm_omni/diffusion/models/cosmos3/pipeline_cosmos3.py, vllm_omni/diffusion/models/hunyuan_video/pipeline_hunyuan_video_1_5.py, vllm_omni/diffusion/models/hunyuan_video/pipeline_hunyuan_video_1_5_i2v.py","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/cosmos3/pipeline_cosmos3.py","vllm_omni/diffusion/models/hunyuan_video/pipeline_hunyuan_video_1_5.py","vllm_omni/diffusion/models/hunyuan_video/pipeline_hunyuan_video_1_5_i2v.py"],"selected_from_description":true}]}} +{"pr":7725,"title":"[Model] Reduce MOSS TTS reference audio preparation overhead","body":"## Purpose\n\nCold MOSS reference encoding repeatedly converts Python waveform lists with `torch.tensor` and rebuilds the resampling filter for clips sharing the same sample-rate pair. Use a float32 NumPy array shared with PyTorch and a bounded, process-local LRU of 16 CPU resamplers. Explicit float32 filter construction preserves the existing functional resampler's samples.\n\nThis patch only changes MOSS reference preparation and its unit tests. Model encoding, batching, cache-hit behavior, scheduling, and chunk sizes remain unchanged. It is independent of MRv2.\n\n## Test Plan\n\n**vLLM Version:** 0.29.0; PyTorch 2.13.0, torchaudio 2.11.0, NumPy 2.3.5.\n\n**vLLM-Omni Commit:** based on upstream main `d0755b571`.\n\nCPU validation on the H200 host; no model weights or GPU execution required:\n\n```bash\nPYTHONPATH=. CUDA_VISIBLE_DEVICES= OMP_NUM_THREADS=1 python -m pytest -q \\\n tests/model_executor/models/moss_tts/test_reference_encoder.py \\\n -m 'core_model and cpu' --run-level core_model\n```\n\nThe existing L1 CPU model-executor CI shard already collects this file. Tests cover mono/channel-first inputs, 16/24/32/44.1/48 kHz source rates, exact waveform equality with `functional.resample`, cach","changed_files":["tests/model_executor/models/moss_tts/test_reference_encoder.py","vllm_omni/model_executor/models/moss_tts/reference_encoder.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:moss-tts","path":"repos/vllm-omni/models/moss-tts/rules.md","reason":"title/body model: moss-tts","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: model executor","quick_map_status":"ok","read_required":false},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: scheduling","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/moss_tts/reference_encoder.py"],"selected_from_description":true}]}} +{"pr":7724,"title":"[Feature][TTS] MOSS-TTS: place the reference-audio encoder on the code2wav stage's GPU","body":"## Motivation\n\nThe reference-audio encoder for the MOSS-TTS family (the upstream `AutoProcessor`'s own `audio_tokenizer`, ~1.6 B params for MOSS-TTS-Local-Transformer-v1.5) runs in the API-server process and was hard-pinned to CPU in `tts_adapters/moss_tts.py`. Every cold reference encode paid a CPU forward (~1–2 s), even though a free GPU sits right next to the code2wav stage.\n\n## What this PR does\n\nPlaces the encoder on the **code2wav stage's GPU** by default, so reference encodes run on-device:\n\n- `_codec_stage_devices()` locates the codec stage in `engine_client.stage_configs` (prefers the stage whose `model_stage` mentions `codec`, e.g. `moss_tts_local_codec`; otherwise the final stage) and reads its runtime `devices` entry. Accepts the resolved `runtime_config` object, the omegaconf `runtime` mapping, and the legacy `yaml_runtime` dict.\n- `_resolve_ref_encoder_device()` maps that to the API process: the stage worker remaps its `devices` entry through `CUDA_VISIBLE_DEVICES` and sees it as logical index 0, while replica launch restores the parent environment, so the same index in the (un-remapped) API process names the same physical GPU. Multi-device entries follow replica 0 (f","changed_files":["tests/entrypoints/openai_api/test_tts_adapter.py","vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:moss-tts","path":"repos/vllm-omni/models/moss-tts/rules.md","reason":"title/body model: moss-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, yaml, default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py"],"selected_from_description":true}]}} +{"pr":7720,"title":"[Doc][NPU] Refresh MiniMax-H3 Ascend recipes for 0.28-era configurations","body":"## Summary\n\nBoth MiniMax-H3 Ascend recipes had drifted from the current deployment\nguidance: stale hand-built install instructions, the removed\n`--quantization int8` flag, no `--task-type` partition selection, and no\nmeasured performance data. This PR realigns them with the MindIE-SD\nMiniMax-H3 deployment guide (`examples/minimax-h3/infer.md`).\n\n### MiniMax-H3-NPU.md (Atlas 800I A2 / A3, 8 cards)\n\n- Replace the hand-built environment with the official `ascend/vllm-omni`\n images plus MindIE-SD / vLLM-Omni source installs and the Ref2VA\n ffmpeg/decord dependencies\n- Document `--task-type` task-partition selection\n- Update the recommended configurations to the current lossless\n (DLO + FLASH_ATTN + LaserAttention) and lossy (RainFusion block-sparse +\n INT8 online quantization) commands, using `--diffusion-attention-config` /\n `--diffusion-quantization-config` instead of the removed flags\n- Add optional Cache-DiT and EQBSA sections, including the dense-fallback\n (`start_step` / `end_step`) tuning table\n- Add a FlashGen 4-step online LoRA section pointing at the GPU recipe\n- Add Atlas 800I A2 / A3 benchmark tables (measured on vLLM-Omni 0.28.0)\n and an Ascend-optimized-components ","changed_files":["recipes/MiniMaxAI/MiniMax-H3-NPU-950PR.md","recipes/MiniMaxAI/MiniMax-H3-NPU.md"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, lora, dit","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7719,"title":"[Diffusion][HunyuanImage3] Add request paged prefix caching","body":"\r\n\r\n## Purpose\r\n\r\nAdd cross-request prefix caching for the HunyuanImage3 single DiT stage. Requests that share the same reference image and VAE random state but use different prompts can reuse prefix KV, reducing first-step computation; partial hits and CFG dual branches are supported.\r\n\r\nThe design has three layers: the model defines the caching semantics, the Scheduler manages logical blocks, and the Worker holds the physical KV pages and runs the model.\r\n\r\n```mermaid\r\nflowchart LR\r\n A[\"Model preprocessing
token IDs + reference image identity/position\"] --> B[\"Scheduler / native KVCacheManager
block hash → hit lookup → allocate remaining blocks\"]\r\n B -->|\"block IDs + cached_prefix_len\"| C[\"Worker
install BlockTable and slot mapping\"]\r\n C --> D[\"DiT first step
compute only uncached queries, read full KV\"]\r\n D --> E[\"After request succeeds
publish stable blocks, release request references\"]\r\n E --> B\r\n```\r\n\r\n1. **The model defines the reusable prefix.** The sequence is split into a stable text/reference-image prefix and a generated-image region that changes every step; only complete blocks inside the prefix are cached.","changed_files":[".gitignore","benchmarks/accuracy/common.py","benchmarks/diffusion/README.md","docs/design/feature/prefix_caching.md","docs/user_guide/diffusion_features.md","tests/assets/hunyuan_image3/it2i.jsonl","tests/benchmarks/test_accuracy_bench_utils.py","tests/benchmarks/test_serve_cli.py","tests/config/test_omni_config.py","tests/dfx/perf/tests/test_hunyuan_image3_prefix_caching.json","tests/dfx/perf/tests/test_runner_metadata.py","tests/diffusion/diffusion_kv/test_config.py","tests/diffusion/diffusion_kv/test_initialization.py","tests/diffusion/diffusion_kv/test_kv_cache_utils.py","tests/diffusion/diffusion_kv/test_manager.py","tests/diffusion/diffusion_kv/test_native_prefetch.py","tests/diffusion/diffusion_kv/test_paged_attention_adapter.py","tests/diffusion/diffusion_kv/test_request.py","tests/diffusion/diffusion_kv/test_worker_contract.py","tests/diffusion/models/hunyuan_image3/test_diffusion_kv_request.py","tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_step_execution.py","tests/diffusion/models/hunyuan_image3/test_image_kv_cache_manager.py","tests/diffusion/test_diffusion_config_propagation.py","tests/diffusion/test_diffusion_engine_dummy_run.py","tests/diffusion/test_diffusion_model_runner.py","tests/diffusion/test_diffusion_plugin_hooks.py","tests/diffusion/test_diffusion_scheduler.py","tests/e2e/accuracy/test_hunyuan_image3.py","tests/e2e/accuracy/test_hunyuan_image3_prefix_cache_accuracy.py","vllm_omni/config/omni_config.py","vllm_omni/diffusion/data.py","vllm_omni/diffusion/diffusion_engine.py","vllm_omni/diffusion/diffusion_kv/kv_cache_utils.py","vllm_omni/diffusion/diffusion_kv/manager.py","vllm_omni/diffusion/diffusion_kv/metadata.py","vllm_omni/diffusion/diffusion_kv/model_runner_backend.py","vllm_omni/diffusion/diffusion_kv/paged_attention_adapter.py","vllm_omni/diffusion/diffusion_kv/request.py","vllm_omni/diffusion/executor/abstract.py","vllm_omni/diffusion/forward_context.py","vllm_omni/diffusion/models/hunyuan_image3/hunyuan_image3_transformer.py","vllm_omni/diffusion/models/hunyuan_image3/pipeline_hunyuan_image3.py","vllm_omni/diffusion/models/hunyuan_image3/request_layout.py","vllm_omni/diffusion/registry.py","vllm_omni/diffusion/sched/base_scheduler.py","vllm_omni/diffusion/sched/request_scheduler.py","vllm_omni/diffusion/sched/step_scheduler.py","vllm_omni/diffusion/vllm_config.py","vllm_omni/diffusion/worker/diffusion_model_runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:hunyuan-image3","path":"repos/vllm-omni/models/hunyuan-image3/rules.md","reason":"title/body model: hunyuan-image3","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion, vae, dit","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/omni_config.py"],"selected_from_description":false},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/data.py","vllm_omni/diffusion/diffusion_engine.py","vllm_omni/diffusion/diffusion_kv/kv_cache_utils.py","vllm_omni/diffusion/diffusion_kv/manager.py","vllm_omni/diffusion/diffusion_kv/metadata.py","vllm_omni/diffusion/diffusion_kv/model_runner_backend.py","vllm_omni/diffusion/diffusion_kv/paged_attention_adapter.py","vllm_omni/diffusion/diffusion_kv/request.py","vllm_omni/diffusion/executor/abstract.py","vllm_omni/diffusion/forward_context.py","vllm_omni/diffusion/models/hunyuan_image3/hunyuan_image3_transformer.py","vllm_omni/diffusion/models/hunyuan_image3/pipeline_hunyuan_image3.py","vllm_omni/diffusion/models/hunyuan_image3/request_layout.py","vllm_omni/diffusion/registry.py","vllm_omni/diffusion/sched/base_scheduler.py","vllm_omni/diffusion/sched/request_scheduler.py","vllm_omni/diffusion/sched/step_scheduler.py","vllm_omni/diffusion/vllm_config.py","vllm_omni/diffusion/worker/diffusion_model_runner.py"],"selected_from_description":true}]}} +{"pr":7714,"title":"[Frontend][Benchmark] Add duplex performance metrics to refactored full-duplex framework","body":"## Purpose\r\n\r\nThe initial duplex performance metrics PR #7242 was merged before the framework refactoring PR #7413. To limit the load of refactoring, the full restoration of the server-side duplex request-start clocks from #7242 is revised in this PR.\r\n\r\nAfter #7413, OmniInteract / Omni-DuplexEval still assembled `duplex_*` JSON, but per-response TTFT/TTFP fell back to the client `response.created` receive clock (`source: client_monotonic_receive`, typically ~0.02–0.05 ms). This change reattaches the native-turn producer so those fields again use `source: server_request_start_and_client_receive`.\r\n\r\n## Summary\r\n\r\nSuccessful OmniInteract / Omni-DuplexEval runs reported duplex metrics, but the origin did not align with #7242 :\r\n\r\n- `\"source\": \"client_monotonic_receive\"`\r\n- `\"measurement_origin.ttft\": \"response.created client receive to first non-empty text delta\"`\r\n- per-response `ttft_ms` / `ttfp_ms` in the 0.02–0.05 ms range\r\n\r\nStage-0 TPOT (`stage0_tokens`) and session `stream_*` were unaffected. The client collector still prefers `response_request_metrics` when present; the server stopped attaching it.\r\n\r\nAnd also implements stage-wise performance to both logging table and benchm","changed_files":["tests/benchmarks/metrics/test_metrics.py","tests/benchmarks/test_omniinteract.py","tests/clients/test_duplex_client.py","tests/e2e/features/fullduplex/test_omni_duplex_eval_cli.py","tests/engine/duplex/test_duplex_events.py","tests/engine/duplex/test_engine_session.py","tests/engine/duplex/test_session_runner.py","vllm_omni/benchmarks/duplex_session_metrics.py","vllm_omni/benchmarks/metrics/metrics.py","vllm_omni/clients/duplex.py","vllm_omni/clients/utils.py","vllm_omni/engine/duplex/events.py","vllm_omni/engine/duplex/realtime_events.py","vllm_omni/engine/duplex/session/engine_session.py","vllm_omni/engine/duplex/session/manager.py","vllm_omni/engine/duplex/session/model_channel.py","vllm_omni/engine/duplex_orchestrator.py","vllm_omni/metrics/definitions.py","vllm_omni/metrics/stats.py","vllm_omni/metrics/utils.py","vllm_omni/protocol/duplex/events.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, request, response","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7712,"title":"[CI/Build][ROCm] Cache AMD test images in registry","body":"## Summary\n\nRebased onto current `main` after #7771 merged. Current head: `b3ec52293884ceae949b900df118df0f45ba56f5`; rebase base: `fa506e0fe80365c90d75701e44b4308ee8eacf2a`.\n\n- build ROCm CI images with a Buildx docker-container builder\n- import and export a registry-backed BuildKit cache in the existing `rocm/vllm-omni` repository\n- key dependency caches on `Dockerfile.rocm`, its Dockerfile-specific ignore file, Python dependency metadata, all requirement files, the TorchCodec ROCm installer, and the ROCm architecture\n- install dependency-heavy layers in a source-independent `deps` stage, then add the checkout as one final `COPY --link` layer\n- build and publish the commit-tagged runtime image and cache in one `buildx build --push` invocation\n\n## Motivation\n\nThe AMD image job runs on ephemeral `amd-cpu` workers, so its former plain `docker build` had no reusable remote cache. In [AMD #12134](https://buildkite.com/vllm/vllm-omni-amd-ci/builds/12134), the image job took 20m36.599s. Its BuildKit stage timings attribute 340.1s to pulling/extracting the ROCm base (including 7.25 GB and 2.82 GB layers), 274.2s to OS packages, 239.3s to the TorchCodec build, and 145.1s to the project/ON","changed_files":[".buildkite/amd/scripts/build-ci-image.sh",".buildkite/amd/test-template-amd-omni.j2","docker/Dockerfile.rocm","docker/Dockerfile.rocm.dockerignore","tests/buildkite/test_rocm_dockerfile.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: registry","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7710,"title":"[CI/Build] Stabilize MiniCPM-o mixed-input audio consistency test","body":"**Incremental review:** [Files changed](https://github.com/vllm-project/vllm-omni/pull/7710/files) against `main`.\n\n## Purpose\n\nStabilize the MiniCPM-o 4.5 mixed-input text/audio E2E test reported in #7630. The failing CI response repeated `test` 49 times, while its audio transcript repeated it 112 times (similarity 0.675451892050252).\n\nReplace the synthetic single-word loop with a meaningful spoken sentence and request a concise summary of the image, video, and speech. Keep all three input modalities, streaming text/audio output, five requests per batch, and the existing 0.8 similarity threshold. Add a regression confirming that the original runaway repetition still fails the shared assertion.\n\nThis stabilizes the test fixture; it does not change inference or relax audio consistency checks. The CI artifacts do not establish whether the extra repetitions came from TTS or ASR.\n\nFixes #7630.\n\n## Test Plan\n\n- Assertion unit tests: 15 passed.\n- Ruff check/format, Python compilation, SPDX, test marks, forbidden imports, direct CUDA-call checks, and `git diff --check`: passed.\n- Real full BF16 MiniCPM-o-4_5 checkpoint: three consecutive E2E batches on two NVIDIA L20 GPUs, 15 requests pas","changed_files":["tests/e2e/online_serving/test_minicpmo_4_5.py","tests/helpers/tests/test_assertions.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request, response","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: checkpoint, batch","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7707,"title":"[CI] Fix Structured Diffusion Config Wan Test","body":"## Purpose\r\nFixes https://github.com/vllm-project/vllm-omni/issues/7704\r\n\r\n`get_diffusion_od_config` isn't a valid method on the `Omni` class. There's one on the engine class [here](https://github.com/vllm-project/vllm-omni/blob/main/vllm_omni/engine/omni_engine_base.py#L346), which is probably what was originally intended, but this is a misnomer and gives a simple namespace with some of the model's metadata rather than the actual `OmniDiffusionConfig` 😞 \r\n\r\nThe minimal fix is to pull the real `OmniDiffusionConfig` off of the stage clients. You can verify the test now passes with the command below:\r\n```\r\npytest -s -v tests/e2e/offline_inference/test_wan22_t2v.py tests/e2e/online_serving/test_wan22_t2v.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model' -k test_structured_diffusion_config_reaches_runtime\r\n```\r\n\r\nCC @linyueqian @yenuo26 PTAL. Thanks!\r\n\r\nPassing build link: https://buildkite.com/vllm/vllm-omni/builds/15454/canvas","changed_files":["tests/e2e/offline_inference/test_wan22_t2v.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7706,"title":"[CI/Build][ROCm] Stabilize shared AMD test lanes","body":"## Purpose\n\nStabilize the shared AMD/ROCm pipelines after unrelated or unusually expensive tests were routed into broad CPU lanes and repeatedly produced failures or timeouts.\n\nThe repeated baseline symptoms have four code causes:\n\n- MAGI-2 native compile coverage used `torch.cuda.is_available()`. PyTorch ROCm exposes the CUDA-compatible API, so this CUDA-only test ran on ROCm and failed against the versioned FlashAttention backend.\n- CUDA-only and exhaustive CPU-reference CosyVoice tests ran in the ROCm Model Executor shards. Cold AITER compilation plus the full small-chunk matrix made one shard a persistent straggler.\n- `test_flash_attn.py` had a module-wide CPU marker, so four real accelerator tests ran inside Simple Diffusion and cold-built AITER kernels there.\n- the NVIDIA-only Qwen3-Omni embedding suite was selected by the ROCm R2-01 marker expression and produced setup errors instead of a platform skip.\n\nExact-head AMD build #12224 exposed two additional blocking baseline failures:\n\n- the Qwen3-Omni runtime-config test expected deploy YAML `top_k=-1`, although vLLM `SamplingParams` correctly normalizes that disabled greedy-sampling sentinel to runtime `top_k=0`;\n- CosyVoice3","changed_files":[".buildkite/amd/test-amd-merge.yml",".buildkite/amd/test-amd-ready.yml",".buildkite/amd/test-template-amd-omni.j2",".buildkite/cuda/test-ready.yml","tests/buildkite/test_amd_pipeline.py","tests/diffusion/attention/test_flash_attn.py","tests/diffusion/models/magi2/test_native_compile_cuda.py","tests/e2e/offline_inference/test_qwen3_omni.py","tests/model_executor/models/cosyvoice3/test_cosyvoice3_components.py","tests/model_executor/models/cosyvoice3/test_cosyvoice3_components_cuda.py","tests/model_executor/models/cosyvoice3/test_cosyvoice3_incremental_hift.py","tests/model_executor/models/qwen3_omni/test_qwen3_omni_embed.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:cosyvoice3","path":"repos/vllm-omni/models/cosyvoice3/rules.md","reason":"title/body model: cosyvoice3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, yaml, deploy","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: model executor, sampling","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7695,"title":"[Core][Model] Fit PersonaPlex into the Unified Full-duplex Framework (RFC #7181 PR 2)","body":"\nRFC #7181, PR 2. Ports PersonaPlex onto the `DuplexModelPlugin` contract so a PersonaPlex deployment is served by `DuplexOmni` over `/v1/realtime?duplex=1` again, re-enabling the PersonaPlex duplex suites that #7413 had to skip (#7636 Issue 16). The other #7636 follow-up issues are handled in a separate PR.\n\n## Purpose\n\n**Framework extensions (model-neutral; MiniCPM-o behaviour unchanged)**\n- `DuplexModelPlugin.silence_unit_payload()`: the runner's turn-continuation unit and the startup warmup use the plugin's own format/rate/length instead of a fixed 16 kHz payload (PersonaPlex units are 1920 samples @ 24 kHz).\n- `SessionEmitter.auto_responds()` is implied when a model takes no client commits (`supports_client_commit=False`), so a stock Realtime client works on a lockstep model without `extra_body.auto_response`.\n- `DefaultDuplexModelSessionState`, `DuplexDataPlaneContext` and a default `validate_client_extra_body` remove the per-model copies of shared session code.\n\n**Shared toolbox** at `vllm_omni/model_executor/common/`: `request_outputs.py` (RequestOutput / multimodal_output readers, cumulative-delta helpers), `audio/pcm.py` (pcm_f32le decode/coun","changed_files":["docs/design/fullduplex-personaplex.md","docs/models/supported_models.md","docs/serving/realtime_duplex_api.md","examples/online_serving/personaplex/README.md","recipes/NVIDIA/PersonaPlex.md","tests/e2e/online_serving/personaplex_realtime_duplex.py","tests/e2e/online_serving/test_personaplex_duplex.py","tests/engine/duplex/test_duplex_plugin.py","tests/engine/duplex/test_session_runner_personaplex.py","tests/engine/test_duplex_import_boundary.py","tests/entrypoints/duplex/test_duplex_warmup.py","tests/model_executor/executor_common/__init__.py","tests/model_executor/executor_common/audio/__init__.py","tests/model_executor/executor_common/audio/test_pcm.py","tests/model_executor/executor_common/duplex/__init__.py","tests/model_executor/executor_common/duplex/test_data_plane.py","tests/model_executor/executor_common/duplex/test_payload.py","tests/model_executor/executor_common/duplex/test_pcm_buffer.py","tests/model_executor/executor_common/test_request_outputs.py","tests/model_executor/models/personaplex/duplex/conftest.py","tests/model_executor/models/personaplex/duplex/test_plugin.py","tests/model_executor/models/personaplex/duplex/test_stage0_runtime.py","tests/model_executor/models/personaplex/duplex/test_unified_runtime.py","vllm_omni/config/stage_config.py","vllm_omni/diffusion/models/qwen_image/pipeline_qwen_image.py","vllm_omni/engine/duplex/plugin.py","vllm_omni/engine/duplex/session/emitter.py","vllm_omni/engine/duplex/session/model_channel.py","vllm_omni/entrypoints/duplex/warmup.py","vllm_omni/model_executor/common/__init__.py","vllm_omni/model_executor/common/audio/__init__.py","vllm_omni/model_executor/common/audio/pcm.py","vllm_omni/model_executor/common/duplex/__init__.py","vllm_omni/model_executor/common/duplex/data_plane.py","vllm_omni/model_executor/common/duplex/payload.py","vllm_omni/model_executor/common/duplex/pcm_buffer.py","vllm_omni/model_executor/common/request_outputs.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/data_plane.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py","vllm_omni/model_executor/models/personaplex/duplex/__init__.py","vllm_omni/model_executor/models/personaplex/duplex/capabilities.py","vllm_omni/model_executor/models/personaplex/duplex/config.py","vllm_omni/model_executor/models/personaplex/duplex/data_plane.py","vllm_omni/model_executor/models/personaplex/duplex/input.py","vllm_omni/model_executor/models/personaplex/duplex/plugin.py","vllm_omni/model_executor/models/personaplex/duplex/runtime_extension.py","vllm_omni/model_executor/models/personaplex/duplex/serving_adapter.py","vllm_omni/model_executor/models/personaplex/duplex/stage0.py","vllm_omni/model_executor/models/personaplex/pipeline.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:personaplex","path":"repos/vllm-omni/models/personaplex/rules.md","reason":"title/body model: personaplex","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request, response","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/stage_config.py"],"selected_from_description":true},{"owner":"model-executor","changed_files":["vllm_omni/model_executor/common/__init__.py","vllm_omni/model_executor/common/audio/__init__.py","vllm_omni/model_executor/common/audio/pcm.py","vllm_omni/model_executor/common/duplex/__init__.py","vllm_omni/model_executor/common/duplex/data_plane.py","vllm_omni/model_executor/common/duplex/payload.py","vllm_omni/model_executor/common/duplex/pcm_buffer.py","vllm_omni/model_executor/common/request_outputs.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/data_plane.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py","vllm_omni/model_executor/models/personaplex/duplex/__init__.py","vllm_omni/model_executor/models/personaplex/duplex/capabilities.py","vllm_omni/model_executor/models/personaplex/duplex/config.py","vllm_omni/model_executor/models/personaplex/duplex/data_plane.py","vllm_omni/model_executor/models/personaplex/duplex/input.py","vllm_omni/model_executor/models/personaplex/duplex/plugin.py","vllm_omni/model_executor/models/personaplex/duplex/runtime_extension.py","vllm_omni/model_executor/models/personaplex/duplex/serving_adapter.py","vllm_omni/model_executor/models/personaplex/duplex/stage0.py","vllm_omni/model_executor/models/personaplex/pipeline.py"],"selected_from_description":true},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/qwen_image/pipeline_qwen_image.py"],"selected_from_description":false}]}} +{"pr":7694,"title":"[Bugfix] Update ComfyUI serving-mode mock after helper rename","body":"## Purpose\n\nFixes #7692.\n\n#7675 renamed `_is_duplex_model` to `_should_serve_duplex`, but the shared ComfyUI test fixture still patched the old name, causing setup to fail with `AttributeError`. Update the mock target to keep these tests on the non-duplex mock engine.\n\n## Test Plan\n\n```bash\npython -m pytest tests/e2e/features/comfyui/test_comfyui_integration.py tests/entrypoints/openai_api/test_duplex_api_server.py -q -m 'core_model and cpu'\npre-commit run --files tests/e2e/features/comfyui/test_comfyui_integration.py\n```\n\n**vLLM Version:** 0.29.0+cpu\n\n**vLLM-Omni Commit:** 7ef49f8b\n\n## Test Result\n\n- 60 passed, 42 warnings (CPU, mocked model engines).\n- All applicable pre-commit checks passed, including mypy.\n","changed_files":["tests/e2e/features/comfyui/test_comfyui_integration.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, server, openai","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7693,"title":"[Bugfix][MiniMax-H3] Use Hopper-safe modulation precision","body":"## Summary\n\nFix intermittent MiniMax-H3 I2VA accuracy failures on H100/SM90 by matching the unfused BF16 dtype boundaries in the two fused RMSNorm + AdaLN modulation kernels on Hopper only.\n\n## Diagnosis\n\n- The H100 I2VA failure is isolated to the repeated image-conditioned DiT trajectory; Ref2VA remains passing.\n- Build 15562 with the separate VAE Q/K launches reverted from #7167 still failed, so the Q/K launch path is not the cause of this CI failure. That change has been reverted from this PR.\n- The fused modulation kernels kept RMSNorm/AdaLN intermediates in FP32, while the unfused path rounds through BF16. On SM90, the repeated precision difference can move the 50-step I2VA result below the CI threshold.\n- B300/SM103 keeps the existing FP32 path: enabling the BF16 path globally regressed the local B300 I2VA run, so this fix is intentionally architecture-scoped.\n\n## Fix\n\n- Select the BF16 affine boundaries only when the CUDA compute capability major is 9.\n- Keep the existing modulation path on other architectures.\n- Register the shared modulation source in the MiniMax-H3 CI dependency map so this change runs the I2VA/Ref2VA accuracy job.\n\n## Validation\n\n- B300, 4-GPU HSDP + 4-w","changed_files":[".buildkite/common/ci_source_file_dependencies.yml","tests/diffusion/models/minimax_h3/test_minimax_h3_modulation.py","vllm_omni/diffusion/layers/indexed_modulation.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: vae, dit","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/layers/indexed_modulation.py"],"selected_from_description":true}]}} +{"pr":7685,"title":"[Core][Diffusion] Add pause_generation(mode=\"keep\") for diffusion stages","body":"## Purpose\n\nFixes #7684.\n\nDiffusion stages have worker-level sleep/wake but no scheduler-level pause: `AsyncOmni.pause_generation` only closes frontend admission for them, `DiffusionEngine` keeps scheduling whatever is queued, and the only way to make a busy replica quiet before a sleep or a weight update is to abort everything, which discards batches that have already executed.\n\nThis adds `pause_generation(mode=\"keep\")` for diffusion stages running request-level execution:\n\n- `DiffusionEngine` answers `pause_scheduler(mode=\"keep\")` / `resume_scheduler` itself (queued, async and bootstrap entry points). The gate closes at submit time, ends a pending batching wait, and no new batch is scheduled until resume. The ACK is an all-rank `synchronize_device` RPC issued at the next batch boundary; on the multi-process worker it first drains the asynchronous D2H/SHM output thread. Same RPC name and role as vLLM `EngineCore._finish_pause`; like `AsyncLLM.pause_generation`, the ACK precedes the delivery of the last batch's outputs.\n- Requests aborted while paused still get their terminal (`BaseScheduler.pending_finished_request_ids()`), since `schedule()` cannot run while paused.\n- `AsyncOmni`","changed_files":["docs/features/sleep_mode.md","tests/diffusion/distributed/test_pause_keep_two_ranks.py","tests/diffusion/test_async_output_worker.py","tests/diffusion/test_diffusion_engine_rpc_routing.py","tests/diffusion/test_diffusion_scheduler.py","tests/diffusion/test_diffusion_worker_synchronize_device.py","tests/distributed/omni_connectors/test_kv_async_prefetch.py","tests/e2e/features/custom_pipeline/test_diffusion_pause_keep.py","tests/e2e/features/helpers/custom_pipeline.py","tests/engine/test_orchestrator_error_handling.py","tests/entrypoints/test_async_omni_pause_sleep_routing.py","vllm_omni/diffusion/diffusion_engine.py","vllm_omni/diffusion/sched/base_scheduler.py","vllm_omni/diffusion/worker/diffusion_worker.py","vllm_omni/distributed/omni_connectors/kv_transfer_manager.py","vllm_omni/engine/orchestrator.py","vllm_omni/entrypoints/async_omni.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request, sleep, wake","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: batch","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/async_omni.py"],"selected_from_description":true},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/diffusion_engine.py","vllm_omni/diffusion/sched/base_scheduler.py","vllm_omni/diffusion/worker/diffusion_worker.py"],"selected_from_description":true},{"owner":"distributed","changed_files":["vllm_omni/distributed/omni_connectors/kv_transfer_manager.py","vllm_omni/entrypoints/async_omni.py"],"selected_from_description":true}]}} +{"pr":7676,"title":"[Bugfix] Fix Parallel State Initialization For MoE + Diffusion","body":"## Purpose\r\nFix for https://github.com/vllm-project/vllm-omni/issues/7616.\r\n\r\nThere seems to be a latent bug in the Omni parallel state initialization that surfaced after the `0.29` rebase, since the default for `VLLM_ALLREDUCE_USE_FLASHINFER` was flipped in [this PR](https://github.com/vllm-project/vllm/pull/52998). I think this issue bubbles up through this test because when we init the distributed state of models with `use_moe_parallel_mapping`, TP path will call `init_vllm_model_parallel_group`, which is currently trying to leverage the native vLLM coordinator before the distributed environment in vLLM (i.e., the world / node related vars, not the torch process groups) is initialized.\r\n\r\nThis PR initializes the vLLM parallel state after the initial omni set up so that we avoid potentially calling the Omni initialization + trying to use MP and crashing due to vLLM not having the corresponding initialization. It also destroys the parallel state for vLLM in the teardown (which also destroys the process groups).\r\n\r\n## Test Plan\r\nOn main, you can run the test below to see it explode with `assert _NODE_COUNT is not None, \"distributed environment is not initialized\"`:\r\n```\r\nLLM_ALLRED","changed_files":["tests/diffusion/distributed/test_parallel_state_sp_groups.py","vllm_omni/diffusion/distributed/parallel_state.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false},{"owner":"distributed","path":"repos/vllm-omni/components/distributed/_index.md","reason":"title/body: distributed","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"diffusion","changed_files":["vllm_omni/diffusion/distributed/parallel_state.py"],"selected_from_description":true}]}} +{"pr":7675,"title":"[Frontend] Allow explicit turn-based serving","body":"# [Frontend] Allow explicit turn-based serving for duplex-capable models\r\n\r\n## Purpose\r\n\r\nAllow pipelines that provide a `duplex_plugin` to select `session_mode: turn` or `session_mode: duplex` at startup. Preserve the existing MiniCPM-o duplex default, add a small turn-based profile, and fail fast when a duplex-capable deployment omits or misspells the mode. Add coverage to the existing serving tests and document the two modes.\r\n\r\nAddresses item 20 of #7636.\r\n\r\n## Test Plan\r\n\r\n```bash\r\npytest -sv tests/entrypoints tests/engine -m 'core_model and cpu'\r\npytest -sv tests/entrypoints/openai_api/test_duplex_api_server.py -m 'core_model and cpu'\r\n```\r\n\r\nGPU validation should run the existing MiniCPM-o online serving test with the new turn deployment parameter in the appropriate CI environment.\r\n\r\n**vLLM Version:** 0.29.0+cpu\r\n\r\n## Test Result\r\n\r\nCPU serving-mode coverage passed. The broader local relevant regression previously passed with 478 tests and 18 warnings. The final fixture-only follow-up passed 15 mode-selection/configuration cases. GPU/H100 end-to-end generation was not run locally.\r\n","changed_files":["docs/configuration/stage_configs.md","docs/design/fullduplex.md","docs/serving/full_duplex_api.md","docs/serving/realtime_duplex_api.md","tests/e2e/online_serving/test_minicpmo_4_5.py","tests/entrypoints/openai_api/test_duplex_api_server.py","vllm_omni/config/stage_config.py","vllm_omni/deploy/minicpmo_4_5.yaml","vllm_omni/deploy/minicpmo_4_5_turn.yaml","vllm_omni/entrypoints/openai/api_server.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration, default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, server, openai","quick_map_status":"truncated","read_required":true}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/stage_config.py","vllm_omni/deploy/minicpmo_4_5.yaml","vllm_omni/deploy/minicpmo_4_5_turn.yaml"],"selected_from_description":true},{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/api_server.py"],"selected_from_description":true}]}} +{"pr":7674,"title":"[XPU][HunyuanImage3] Fix HunyuanImage-3.0 text-to-image on XPU","body":"## [XPU][HunyuanImage3] Fix HunyuanImage-3.0 text-to-image on XPU\r\n\r\nBoth HunyuanImage-3.0 checkpoints failed to serve on XPU. Four independent\r\nissues were in the way; each one blocked startup or generation on its own.\r\n\r\n### 1. Diffusion warmup breaks the latent patch grid\r\n\r\n`vllm_omni/diffusion/models/hunyuan_image3/pipeline_hunyuan_image3.py`\r\n\r\n```\r\nValueError: Hunyuan generated-image hidden states do not match the latent\r\npatch grid: got 768 tokens, expected 1024 (32x32)\r\n```\r\n\r\nThe generic warmup request carries a blank PIL image. For this two-stage\r\nAR+diffusion model that routes the request down the image-edit path, where the\r\ntoken block cannot match the latent patch grid. Set `dummy_run_num_frames = 0`\r\nto skip the diffusion warmup, as `sana_wm` and `lingbot_world` already do.\r\n\r\n### 2. XPU deploy block oversubscribes GPU memory\r\n\r\n`vllm_omni/deploy/hunyuan_image_3_moe.yaml`\r\n\r\n```\r\nValueError: Free memory on device xpu:0 is less than desired GPU memory\r\nutilization\r\nRuntimeError: level_zero backend failed with error: 40\r\n(UR_RESULT_ERROR_OUT_OF_RESOURCES)\r\n```\r\n\r\nThe `xpu` block placed both stages on all eight cards at `0.95` and `0.9`, so\r\nstage 0 had no room left for","changed_files":["vllm_omni/deploy/hunyuan_image_3_moe.yaml","vllm_omni/diffusion/models/hunyuan_image3/hunyuan_image3_transformer.py","vllm_omni/diffusion/models/hunyuan_image3/pipeline_hunyuan_image3.py","vllm_omni/model_executor/models/hunyuan_image3/hunyuan_image3.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:hunyuan-image3","path":"repos/vllm-omni/models/hunyuan-image3/rules.md","reason":"title/body model: hunyuan-image3","quick_map_status":"ok","read_required":false},{"owner":"model:lingbot-world","path":"repos/vllm-omni/models/lingbot-world/rules.md","reason":"title/body model: lingbot-world","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: yaml, deploy, pipeline","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/deploy/hunyuan_image_3_moe.yaml"],"selected_from_description":true},{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/hunyuan_image3/hunyuan_image3.py"],"selected_from_description":false},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/models/hunyuan_image3/hunyuan_image3_transformer.py","vllm_omni/diffusion/models/hunyuan_image3/pipeline_hunyuan_image3.py"],"selected_from_description":true}]}} +{"pr":7670,"title":"[Model][PersonaPlex] add per-row RingKV offsets and active rows","body":"\r\nPLEASE FILL IN THE PR DESCRIPTION HERE.\r\n\r\n## Purpose\r\n\r\nW1 standalone enabler described in #7389.\r\n\r\nThis PR makes PersonaPlex Mimi streaming state row-aware so a compact batched codec can let one row skip a tick without advancing or contaminating its state:\r\n\r\n- Make `_RingKV.end_offset` and Mimi transformer position progression per-row.\r\n- Thread an `active: Tensor[B]` mask through Mimi encode/decode so inactive rows preserve their KV, transformer offset, and convolution carry state.\r\n- Add mixed-active pause/resume tests that compare active rows.\r\n\r\n## Test Plan\r\n\r\n**vLLM Version:** `0.29.0`\r\n**vLLM-Omni Commit:** `6c5bd318bb70075dc6f39b141f6178408b58e4e8`\r\n\r\n- [x] CPU tests: `pytest -q --run-level=core_model -m \"core_model and cpu\" tests/model_executor/models/personaplex/test_ring_per_row_offsets.py\r\n` — 4 passed\r\n- [x] GPU mixed-offset parity: `pytest -q --run-level=core_model -m \"core_model and cuda\" tests/model_executor/models/personaplex/test_ring_per_row_offsets.py\r\n` - 4 passed\r\n- [x] Existing PersonaPlex tests: `pytest -q --run-level=core_model tests/model_executor/models/personaplex --ignore=tests/model_executor/models/personaplex/test_ri","changed_files":["tests/model_executor/models/personaplex/test_ring_per_row_offsets.py","vllm_omni/model_executor/models/personaplex/personaplex_mimi.py","vllm_omni/model_executor/models/personaplex/personaplex_temporal.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:personaplex","path":"repos/vllm-omni/models/personaplex/rules.md","reason":"title/body model: personaplex","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: model executor","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/personaplex/personaplex_mimi.py","vllm_omni/model_executor/models/personaplex/personaplex_temporal.py"],"selected_from_description":true}]}} +{"pr":7667,"title":"[Bugfix] Fix Breeze-TTS-2 repeated silence and audio EOS handling","body":"## Purpose\n\nBreeze-TTS-2 can repeat codec tokens for tens of seconds before reaching audio EOS. In a reproduced English request, the baseline spoke only “Hello” and then generated 29.84 seconds of trailing quiet. The port omitted the generated-codebook-0 repetition penalty used by the [official inference example](https://github.com/breezeblue-ai/breeze-tts/blob/008f769016b0a24711becd7a4925030bc93f608c/infer.py).\n\n- Apply the sign-aware repetition penalty to generated codebook-0 history, excluding text/reference conditioning. Default to the upstream value of `1.1` and support speech API overrides through `extra_params.repetition_penalty`.\n- Keep the scheduler's selected token consistent with the codec frame already generated. Ignore text-tokenizer EOS `1`, which is a valid audio code, while retaining explicit audio EOS `2051`; use `min_tokens=0` to prevent masking a selected token.\n- Match the streaming decoder's expected chunk size to the producer's 8-frame chunks, and retain original batch indices when empty terminal requests are filtered out.\n- Add regression coverage and document the current greedy/CFG-1 scope relative to the official examples.\n\nThe scheduler logits now represen","changed_files":["recipes/BreezeBlue/Breeze-TTS-2.md","tests/model_executor/models/breeze_tts_2/test_instruction.py","tests/model_executor/models/breeze_tts_2/test_repetition_penalty.py","tests/model_executor/models/breeze_tts_2/test_sampling_eos.py","tests/model_executor/models/breeze_tts_2/test_streaming.py","vllm_omni/deploy/breeze_tts_2.yaml","vllm_omni/entrypoints/openai/tts_adapters/breeze_tts_2.py","vllm_omni/model_executor/models/breeze_tts_2/modeling_breeze_tts_2_codec.py","vllm_omni/model_executor/models/breeze_tts_2/modeling_breeze_tts_2_talker.py","vllm_omni/model_executor/models/breeze_tts_2/pipeline.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: request, speech api","quick_map_status":"truncated","read_required":true},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: tokenizer, batch","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/deploy/breeze_tts_2.yaml"],"selected_from_description":true},{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/tts_adapters/breeze_tts_2.py"],"selected_from_description":true},{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/breeze_tts_2/modeling_breeze_tts_2_codec.py","vllm_omni/model_executor/models/breeze_tts_2/modeling_breeze_tts_2_talker.py","vllm_omni/model_executor/models/breeze_tts_2/pipeline.py"],"selected_from_description":true}]}} +{"pr":7657,"title":"[CI][MiniCPM-o] Lower the Daily-Omni accuracy gate to 0.77","body":"## What\r\n\r\nLower `_MIN_DAILY_OMNI_ACCURACY` from `0.78` to `0.77` in the MiniCPM-o 4.5 Daily-Omni accuracy test, and update the module docstring to match.\r\n\r\n## Why\r\n\r\nThe gate is flaky on NPU: it fails intermittently with `assert 1 == 0` in nightly CI (#6887).\r\n\r\nRoot cause is threshold headroom, not a serving regression. Recent independent NPU measurements:\r\n\r\n| source | environment | Daily-Omni |\r\n|---|---|---|\r\n| repro x2, 0 HTTP failures / 0 parse failures / 0 missing-gold (#6887) | official NPU CI image, 910B3/A3 | 932/1197 = 77.86% |\r\n| independent baseline reproduction (#6657) | Ascend 910C | 77.93% |\r\n\r\n- The gate at 0.78 requires 934/1197 correct - 1 item above the observed mean (~933) and inside the observed 932~940 spread - so the test coin-flips: **3 failures in the latest 7 dated NPU nightlies** as of 2026-09-08, while per-item error spread looks unremarkable.\r\n- The 2026-09-10 ~ 2026-09-12 NPU nightly reds were environment failures (#7400, image misalignment after the vLLM 0.29 rebase, fixed by #7433) and carry no accuracy signal.\r\n\r\n## Change\r\n\r\n`0.78 -> 0.77` (922/1197), i.e. 10 items (~0.85pp) below the observed minimum (932). This removes the coin-flip margin whi","changed_files":["tests/e2e/accuracy/minicpmo_4_5/test_minicpmo_4_5.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving, http","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"pr":7655,"title":"[Misc] Remove Dynin-Omni and dots.tts support","body":"\nPLEASE FILL IN THE PR DESCRIPTION HERE.\n\n## Purpose\n\nRemove first-party support for two models that should no longer ship in vLLM-Omni:\n\n- `snu-aidas/Dynin-Omni` (`DyninOmniForConditionalGeneration`)\n- `rednote-hilab/dots.tts-soar` / `dots-studio/dots.tts-soar` (`DotsTTSForConditionalGeneration`)\n\nThis deletes their model implementations, deploy YAMLs, stage processors, TTS adapter/HF config registrations, e2e/unit tests, examples, and recipe/docs entries, then updates the remaining registries and support tables so the models are no longer advertised. Touched shared files (`hub_prefetch.py`, `omni_connector_payload_transport.py`, streaming-helper tests) also get the mypy annotations needed after those files entered the diff.\n\n## Test Plan\n\n**vLLM Version:** aligned with current `vllm-omni` `main`\n\n**vLLM-Omni Commit:** `56b25b793`\n\nNo new tests: this PR only removes two models and their dedicated coverage. Remaining edited tests should still pass without those architectures.\n\n```bash\npython -m mypy --python-version 3.12 --follow-imports skip \\\n vllm_omni/diffusion/model_loader/hub_prefetch.py \\\n vllm_omni/distributed/omni_connectors/model_runner/omni","changed_files":["README.md","docs/README.md","docs/models/supported_models.md","examples/model_display_names.yml","examples/offline_inference/dynin_omni/README.md","examples/offline_inference/dynin_omni/end2end.py","examples/offline_inference/text_to_speech/README.md","examples/offline_inference/text_to_speech/dots_tts/end2end.py","examples/online_serving/dynin_omni/README.md","examples/online_serving/dynin_omni/openai_chat_completion_client_for_multimodal_generation.py","examples/online_serving/text_to_speech/README.md","recipes/rednote-hilab/dots.tts.md","tests/config/test_environment_variables.py","tests/config/test_omni_config.py","tests/e2e/offline_inference/test_dots_tts_expansion.py","tests/e2e/offline_inference/test_dynin_omni_expansion.py","tests/e2e/online_serving/test_dots_tts_expansion.py","tests/e2e/online_serving/test_dynin_omni_expansion.py","tests/engine/test_arg_utils.py","tests/entrypoints/openai_api/test_serving_speech.py","tests/entrypoints/openai_api/test_tts_adapter.py","tests/entrypoints/openai_api/test_tts_detection.py","tests/model_executor/models/dots_tts/__init__.py","tests/model_executor/models/dots_tts/test_talker.py","tests/model_executor/models/dynin_omni/test_dynin_omni_token2audio.py","tests/model_executor/stage_input_processors/test_qwen3_omni_streaming_helpers.py","vllm_omni/config/environment_variable_inventory.py","vllm_omni/config/pipeline_registry.py","vllm_omni/deploy/dots_tts.yaml","vllm_omni/deploy/dynin_omni.yaml","vllm_omni/deploy/dynin_omni_ci.yaml","vllm_omni/deploy/dynin_omni_multiconnector.yaml","vllm_omni/diffusion/model_loader/hub_prefetch.py","vllm_omni/distributed/omni_connectors/model_runner/omni_connector_payload_transport.py","vllm_omni/entrypoints/openai/tts_adapters/__init__.py","vllm_omni/entrypoints/openai/tts_adapters/dots_tts.py","vllm_omni/model_executor/models/dots_tts/__init__.py","vllm_omni/model_executor/models/dots_tts/dots_tts_dit.py","vllm_omni/model_executor/models/dots_tts/dots_tts_patch_encoder.py","vllm_omni/model_executor/models/dots_tts/dots_tts_prompt.py","vllm_omni/model_executor/models/dots_tts/dots_tts_speaker_encoder.py","vllm_omni/model_executor/models/dots_tts/dots_tts_talker.py","vllm_omni/model_executor/models/dots_tts/dots_tts_vocoder.py","vllm_omni/model_executor/models/dots_tts/pipeline.py","vllm_omni/model_executor/models/dynin_omni/__init__.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_common.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_token2audio.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_token2image.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_token2text.py","vllm_omni/model_executor/models/dynin_omni/pipeline.py","vllm_omni/model_executor/models/qwen3_omni/qwen3_omni.py","vllm_omni/model_executor/models/registry.py","vllm_omni/model_executor/stage_input_processors/dynin_omni.py","vllm_omni/transformers_utils/configs/__init__.py","vllm_omni/transformers_utils/configs/dots_tts.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:dots-tts","path":"repos/vllm-omni/models/dots-tts/rules.md","reason":"title/body model: dots-tts","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: config, deploy","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: loader","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/environment_variable_inventory.py","vllm_omni/config/pipeline_registry.py","vllm_omni/deploy/dots_tts.yaml","vllm_omni/deploy/dynin_omni.yaml","vllm_omni/deploy/dynin_omni_ci.yaml","vllm_omni/deploy/dynin_omni_multiconnector.yaml"],"selected_from_description":true},{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/tts_adapters/__init__.py","vllm_omni/entrypoints/openai/tts_adapters/dots_tts.py"],"selected_from_description":false},{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/dots_tts/__init__.py","vllm_omni/model_executor/models/dots_tts/dots_tts_dit.py","vllm_omni/model_executor/models/dots_tts/dots_tts_patch_encoder.py","vllm_omni/model_executor/models/dots_tts/dots_tts_prompt.py","vllm_omni/model_executor/models/dots_tts/dots_tts_speaker_encoder.py","vllm_omni/model_executor/models/dots_tts/dots_tts_talker.py","vllm_omni/model_executor/models/dots_tts/dots_tts_vocoder.py","vllm_omni/model_executor/models/dots_tts/pipeline.py","vllm_omni/model_executor/models/dynin_omni/__init__.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_common.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_token2audio.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_token2image.py","vllm_omni/model_executor/models/dynin_omni/dynin_omni_token2text.py","vllm_omni/model_executor/models/dynin_omni/pipeline.py","vllm_omni/model_executor/models/qwen3_omni/qwen3_omni.py","vllm_omni/model_executor/models/registry.py","vllm_omni/model_executor/stage_input_processors/dynin_omni.py"],"selected_from_description":true},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/model_loader/hub_prefetch.py"],"selected_from_description":true},{"owner":"distributed","changed_files":["vllm_omni/distributed/omni_connectors/model_runner/omni_connector_payload_transport.py"],"selected_from_description":true}]}} +{"pr":7654,"title":"[Bugfix][MiniCPM-o] Size the duplex HD slice reservation from the frame","body":"## Purpose\n\nIssue 15 of #7636.\n\n`_duplex_vision_tokens` gives the base frame of every stacked pair three 66-token\nblocks (`vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py:110`), but\nhow many blocks MiniCPM-o actually makes depends on the frame's size. At the\nofficial `max_slice_nums=2` there are only two answers:\n`MiniCPMVImageProcessor.get_sliced_grid` takes\n`min(ceil(w * h / scale_resolution**2), max_slice_nums)` and returns no grid at\nall below 2, so the frame is either one tile or three blocks. Anything that fits\nin one tile was getting three.\n\nThe number is not only a scheduling hint. `build_duplex_data_plane_prompt` turns\nit into `[scheduler_token_id] * budget`, and `preprocess`\n(`minicpmo_4_5_omni.py:392`) fits the unit's embeddings into exactly that many\nprompt positions:\n\n- More slots than embeddings: the gap is filled with pad-token embeddings in\n front of the unit, and the model reads them. On `main` a 448x448 pair puts 132\n of them before every unit.\n- Fewer slots than embeddings: part of the unit is cut. The warning at\n `minicpmo_4_5_omni.py:411` only fires while the whole prompt is still shorter\n than one unit. After that it is the start of the unit ","changed_files":["tests/model_executor/models/minicpmo_4_5/duplex/test_scheduler_token_budget.py","vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minicpm-o-4-5","path":"repos/vllm-omni/models/minicpm-o-4-5/rules.md","reason":"title/body model: minicpm-o-4-5","quick_map_status":"ok","read_required":false},{"owner":"model-executor","path":"repos/vllm-omni/components/model-executor/rules.md","reason":"title/body: model executor","quick_map_status":"ok","read_required":false},{"owner":"scheduler","path":"repos/vllm-omni/components/scheduler/rules.md","reason":"title/body: scheduler, scheduling","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"model-executor","changed_files":["vllm_omni/model_executor/models/minicpmo_4_5/duplex/plugin.py"],"selected_from_description":true}]}} +{"pr":7652,"title":"[Bugfix][Engine] Preserve text_encoder_tp_size in OmniEngineArgs","body":"## Summary\n\n- declare `text_encoder_tp_size: int | None = None` on `OmniEngineArgs` so `from_cli_args` field filtering keeps `--text-encoder-tp-size` for library callers\n- add regression tests for the `from_cli_args` → `create_default_diffusion` → `DiffusionParallelConfig` flow\n\n## Why\n\n`OmniEngineArgs.from_cli_args` filters the argparse namespace by dataclass fields. Neither `OmniEngineArgs` nor `OrchestratorArgs` declares `text_encoder_tp_size`, so the parsed value is silently dropped before the default diffusion stage is synthesized — `DiffusionParallelConfig.text_encoder_tp_size` resets to 1 and the encoder stays unsharded (#7564, the verl-omni library path).\n\nScope note, per the module owner's confirmation in the issue: for the MiniMax-H3 registered pipeline, the `stage_cli_aliases` redirect of this flag to stage-0 AR `tensor_parallel_size` is intended behavior and is unchanged here. The field is declared on `OmniEngineArgs` only (not `OrchestratorArgs`) so the shared-fields invariant is preserved; the serve path was already routing the flat kwarg correctly through `DiffusionParallelConfig.from_stage_overrides`.\n\n## Testing\n\n- new: `test_from_cli_args_preserves_text_encoder_tp","changed_files":["tests/engine/test_arg_utils.py","vllm_omni/engine/arg_utils.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:minimax-h3","path":"repos/vllm-omni/models/minimax-h3/rules.md","reason":"title/body model: minimax-h3","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: pipeline, default","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: diffusion","quick_map_status":"ok","read_required":false}],"scope_validation":[]}} +{"pr":7651,"title":"[LingBot World] Shard the streaming VAE decode across the Ulysses ranks","body":"## Purpose\n\nShard the LingBot-World streaming VAE decode across the Ulysses ranks along the width, using the existing halo-exchanging Wan decoder shard. No new option: a multi-rank deployment is what asks for it.\n\nThe DiT already runs on `sequence_parallel_size` ranks, and until now each of them decoded the whole frame so that its own `post_decode` could carry on. With the shard each rank decodes a quarter of it (at four ranks) and every rank keeps the assembled frame, because the gather is an all-gather anyway. The streaming decoder composes with it as is (`feat_cache`, `feat_idx`, `first_chunk` pass straight through to the patched decoder) and the per-session temporal cache shrinks by the same factor (1801 -> 450 MiB per rank at 480x832).\n\n`vae_patch_parallel_size` stays rejected for this pipeline, as before: it would make the registry force tiling and set up the tiled distributed executor underneath a decoder that is already sharded.\n\n## Measured: served path, cumulative on top of the lossless set in #7648\n\n| arm (cumulative) | steady median ms | std | p99 | delta | RTF @12 fps | RTF @16 fps | stall |\n|---|---:|---:|---:|---:|---:|---:|---:|\n| lossless set from #7648 (stock is 1","changed_files":["tests/diffusion/distributed/test_wan_spatial_shard.py","tests/diffusion/models/lingbot_world/test_pipeline_lingbot_world.py","vllm_omni/deploy/lingbot_world_v2_stepwise.yaml","vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:lingbot-world","path":"repos/vllm-omni/models/lingbot-world/rules.md","reason":"title/body model: lingbot-world","quick_map_status":"ok","read_required":false},{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: registry, pipeline","quick_map_status":"ok","read_required":false},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: vae, dit, ulysses","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/deploy/lingbot_world_v2_stepwise.yaml"],"selected_from_description":true},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py"],"selected_from_description":true}]}} +{"pr":7649,"title":"[Voxtral] Follow up to raise BadRequest for ref_audio","body":"## Purpose\r\n\r\nFollow up to https://github.com/vllm-project/vllm-omni/pull/7012#discussion_r4021683613 to harden the encoder_loaded check to handle dicts and bare bool and not assume that the RPC always returns a list. \r\n\r\n## Test Plan\r\nRan ```tests/e2e/online_serving/test_voxtral_tts_expansion.py``` and all entrypoint tests\r\n\r\n**vLLM Version:** 0.29.0\r\n\r\n**vLLM-Omni Commit:** 249e84d21\r\n\r\n## Test Result\r\n\r\n- ```pytest -sv tests/e2e/online_serving/test_voxtral_tts_expansion.py```\r\n8 passed, 14 warnings in 745.69s (0:12:25)\r\n\r\n- ```pytest -sv tests/entrypoints/ -m 'core_model and cuda' --run-level 'core_model'```\r\n3 passed, 1798 deselected, 24 warnings in 230.18s (0:03:50)\r\n","changed_files":["vllm_omni/entrypoints/openai/tts_adapters/voxtral.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:voxtral-tts","path":"repos/vllm-omni/models/voxtral-tts/rules.md","reason":"title/body model: voxtral-tts","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: serving","quick_map_status":"truncated","read_required":true},{"owner":"distributed","path":"repos/vllm-omni/components/distributed/_index.md","reason":"title/body: rpc","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"serving","changed_files":["vllm_omni/entrypoints/openai/tts_adapters/voxtral.py"],"selected_from_description":true}]}} +{"pr":7648,"title":"[LingBot World] Eleven bit-identical removals of repeated work in the realtime path","body":"## Purpose\n\nEleven changes to the LingBot-World realtime path that leave the output bit-identical. Each removes work that reproduces a result an earlier forward already had, recomputes something that cannot change within an AR block or a session, or converts data on the wrong side of a device boundary.\n\n| change | what it removes |\n| --- | --- |\n| Decode a repeated source image once | re-decoding the same source image every tick |\n| Build the camera modulation once per AR block | the camera injector in all 40 blocks on 4 of 5 forwards |\n| Drop both cross-attention all-to-alls under Ulysses | 2 collectives per layer per forward |\n| Restage only the K/V that moved within an AR block | re-gathering the visible window every forward (opt-in, `reuse_history_staging`) |\n| Copy the block-table metadata to the device asynchronously | three synchronous pageable H2D copies per AR block |\n| Deliver realtime chunks as uint8 frames straight from the device | a float32 host copy, a numpy multiply/round/cast and twelve PIL images per chunk, then the endpoint's coercion back to uint8 |\n| Stop re-encoding the condition once the encoder is at a fixed point | three causal-encoder passes per block that","changed_files":["docs/configuration/environment_variables.md","tests/config/test_environment_variables.py","tests/diffusion/ar_diffusion/test_kv_cache.py","tests/diffusion/ar_diffusion/test_paged_attention.py","tests/diffusion/distributed/test_comm.py","tests/diffusion/distributed/test_wan_spatial_shard.py","tests/diffusion/models/lingbot_world/test_lingbot_world_attention.py","tests/diffusion/models/lingbot_world/test_lingbot_world_transformer.py","tests/diffusion/models/lingbot_world/test_pipeline_lingbot_world.py","tests/diffusion/test_diffusion_step_pipeline.py","vllm_omni/config/environment_variable_inventory.py","vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py","vllm_omni/diffusion/models/interface.py","vllm_omni/diffusion/models/lingbot_world/dmd_block.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py","vllm_omni/diffusion/models/lingbot_world/transformer.py","vllm_omni/diffusion/worker/input_batch.py","vllm_omni/experimental/ar_diffusion/capability.py","vllm_omni/experimental/ar_diffusion/kv_cache/config.py","vllm_omni/experimental/ar_diffusion/kv_cache/manager.py","vllm_omni/experimental/ar_diffusion/kv_cache/paged.py","vllm_omni/experimental/ar_diffusion/kv_cache/paged_attention.py","vllm_omni/experimental/ar_diffusion/runner.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"model:lingbot-world","path":"repos/vllm-omni/models/lingbot-world/rules.md","reason":"title/body model: lingbot-world","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: endpoint","quick_map_status":"truncated","read_required":true},{"owner":"diffusion","path":"repos/vllm-omni/components/diffusion/rules.md","reason":"title/body: ulysses","quick_map_status":"ok","read_required":false}],"scope_validation":[{"owner":"configuration","changed_files":["vllm_omni/config/environment_variable_inventory.py"],"selected_from_description":false},{"owner":"diffusion","changed_files":["vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py","vllm_omni/diffusion/models/interface.py","vllm_omni/diffusion/models/lingbot_world/dmd_block.py","vllm_omni/diffusion/models/lingbot_world/pipeline.py","vllm_omni/diffusion/models/lingbot_world/transformer.py","vllm_omni/diffusion/worker/input_batch.py"],"selected_from_description":true}]}} +{"pr":7647,"title":"[Frontend] Remove obsolete experimental duplex client parameters","body":"## Purpose\n\nAddresses **Issue 23** of #7636: align the experimental duplex client with the session protocol merged in #7413. This does not close the umbrella RFC or claim to fix soft-interrupt timeouts.\n\n- Remove obsolete `native_duplex` and client-selected `session_id` arguments from the URL/configuration helpers, and update their remaining driver caller.\n- Remove obsolete fields from supplied URLs/session extras without mutating the caller's dictionary. New session IDs are server-assigned; the existing `session.resume` path is unchanged.\n- Preserve supported query fields, URL precedence, `autostart`, `auto_response` (including `False`), and the existing `force_listen_count=0` default behavior.\n\nNo server, engine, model, benchmark lifecycle, or endpoint changes.\n\n## Test Plan\n\n**vLLM Version:** 0.29.0, matching the current CI target. Python 3.12; isolated environment inside the remote Docker container on `hk01dgx012`; CPU-only tests (`CUDA_VISIBLE_DEVICES=\"\"`).\n\n**vLLM-Omni Commit:** `62e2c284cd8841222c9f9ac87f398279593fc24e`, based on main `d554e6b6`. The tested source files match this commit.\n\nFrom the repository root:\n\n```bash\nPYTHONPATH=. python -m pytest -q \\\n tests/e2e/feat","changed_files":["tests/e2e/features/fullduplex/test_client.py","tests/e2e/online_serving/run_minicpmo_realtime_duplex_server_vad.py","vllm_omni/experimental/fullduplex/client.py"],"expected":{"status":"ready","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[{"owner":"configuration","path":"repos/vllm-omni/components/configuration/rules.md","reason":"title/body: configuration, default","quick_map_status":"ok","read_required":false},{"owner":"serving","path":"repos/vllm-omni/components/serving/rules.md","reason":"title/body: server, endpoint, response","quick_map_status":"truncated","read_required":true}],"scope_validation":[]}} +{"repo":"afd-plugin","title":"t","body":"b","changed_files":["afd_plugin/config.py","README.md"],"expected":{"status":"ready","selected_by":"adapter_changed_files","changed_files_role":"route_and_scope_validation","routes":[{"owner":"plugin-boundary","path":"repos/afd-plugin/components/plugin-boundary/rules.md","relative_path":"repos/afd-plugin/components/plugin-boundary/rules.md","reason":"changed file: afd_plugin/config.py","changed_file":"afd_plugin/config.py","repo":"afd-plugin","quick_map_status":"unavailable","read_required":true}],"scope_validation":[{"owner":"plugin-boundary","changed_files":["afd_plugin/config.py"],"selected_from_description":false}],"unmatched_changed_files":["README.md"],"unmatched_policy":"Unmatched changed paths remain reviewer-visible and must not be treated as covered."}} +{"repo":"vllm-project/afd-plugin","title":"t","body":"b","changed_files":["afd_plugin/v1/worker/ffn_worker.py"],"expected":{"status":"ready","selected_by":"adapter_changed_files","changed_files_role":"route_and_scope_validation","routes":[{"owner":"ffn-runtime","path":"repos/afd-plugin/components/ffn-runtime/architecture.md","relative_path":"repos/afd-plugin/components/ffn-runtime/architecture.md","reason":"changed file: afd_plugin/v1/worker/ffn_worker.py","changed_file":"afd_plugin/v1/worker/ffn_worker.py","repo":"afd-plugin","quick_map_status":"unavailable","read_required":true}],"scope_validation":[{"owner":"ffn-runtime","changed_files":["afd_plugin/v1/worker/ffn_worker.py"],"selected_from_description":false}],"unmatched_changed_files":[],"unmatched_policy":"Unmatched changed paths remain reviewer-visible and must not be treated as covered."}} +{"repo":"unknown-repo","title":"t","body":"b","changed_files":["x.py"],"expected":{"status":"unsupported_exact_router","selected_by":"title_body","changed_files_role":"scope_validation_only","routes":[],"scope_validation":[]}} diff --git a/test/test_knowledge_view_routing.py b/test/test_knowledge_view_routing.py new file mode 100644 index 00000000..19ff5dfb --- /dev/null +++ b/test/test_knowledge_view_routing.py @@ -0,0 +1,313 @@ +"""Repo-neutral Direct routing over a per-request KnowledgeView. + +The golden file was recorded from the router BEFORE the vllm-omni owner table +moved out of `direct_routing.py` into `knowledge/repos/vllm-omni/_routes.yaml`: +150 merged vllm-omni PRs (title, first 1,200 body chars, changed files) plus +adapter-only and unsupported repos. Any drift in status, owners, order, reasons +or scope validation is a regression of the extraction, not a style change. +""" + +from __future__ import annotations + +import json +import shutil +from pathlib import Path + +import pytest + +from infermatrix_copilot import direct_routing +from infermatrix_copilot.direct_routing import ( + _direct_knowledge_routes, + direct_review_plan, + load_routes, +) +from infermatrix_copilot.knowledge_view import ( + KNOWLEDGE_ROOT_ENV, + KnowledgeView, + KnowledgeViewError, + _load_view, + build_manifest, +) + +GOLDEN = Path(__file__).parent / "fixtures" / "direct_routing" / "golden_routes.jsonl" + + +def _normalized(result: dict, view: KnowledgeView) -> dict: + out = json.loads(json.dumps(result)) + for route in out.get("routes", []): + route["path"] = view.relative(route["path"]) + route.pop("quick_map", None) + return out + + +def _cases(): + return [json.loads(line) for line in GOLDEN.read_text(encoding="utf-8").splitlines()] + + +def test_golden_routes_unchanged_by_extraction(): + view = KnowledgeView.current() + cases = _cases() + assert len(cases) >= 150 + drifted = [] + for case in cases: + got = _direct_knowledge_routes( + case.get("repo", "vllm-omni"), + title=case["title"], body=case["body"], + changed_files=case["changed_files"], view=view, + ) + if _normalized(got, view) != case["expected"]: + drifted.append(case.get("pr") or case.get("repo")) + assert not drifted, f"routing drifted for: {drifted[:10]}" + + +def test_router_names_no_repository(): + source = Path(direct_routing.__file__).read_text(encoding="utf-8") + for literal in ("vllm-omni", "vllm_omni", "afd-plugin", "afd_plugin"): + assert literal not in source + + +def test_repo_is_required(): + with pytest.raises(ValueError, match="repo is required"): + _direct_knowledge_routes("", title="scheduler", changed_files=[]) + + +def test_aliases_come_from_adapters(): + assert direct_routing._normalize_repo("vllm-project/vllm-omni") == "vllm-omni" + assert direct_routing._normalize_repo("vllm_omni") == "vllm-omni" + assert direct_routing._normalize_repo("vllm-project/afd-plugin") == "afd-plugin" + + +def test_path_like_repo_is_unsupported_not_an_escape(): + result = _direct_knowledge_routes("../../etc", title="x", changed_files=["a.py"]) + assert result["status"] == "unsupported_exact_router" + + +def _copy_tree(tmp_path: Path) -> Path: + src = KnowledgeView.current().root + dst = tmp_path / "knowledge" + shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__")) + return dst + + +def _second_repo(root: Path) -> None: + repo = root / "repos" / "demo-repo" + (repo / "core").mkdir(parents=True) + (repo / "_index.md").write_text("# demo\n", encoding="utf-8") + (repo / "core" / "rules.md").write_text( + "# Core rules\n\n## Direct quick map\n\n- DEMO-1a — keep the queue bounded\n", + encoding="utf-8", + ) + (repo / "_routes.yaml").write_text( + "schema_version: 1\n" + "owners:\n" + " - owner: core\n" + " path: repos/demo-repo/core/rules.md\n" + " signals: [queue]\n" + " scope_prefixes: [demo/core/]\n", + encoding="utf-8", + ) + + +def test_second_repo_routes_through_its_own_table(tmp_path, monkeypatch): + root = _copy_tree(tmp_path) + _second_repo(root) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(root)) + _load_view.cache_clear() + result = _direct_knowledge_routes( + "demo-repo", title="Bound the queue", changed_files=["demo/core/x.py"]) + assert result["status"] == "ready" + assert [route["owner"] for route in result["routes"]] == ["core"] + assert "DEMO-1a" in result["routes"][0]["quick_map"] + + +def test_routes_table_fails_closed_on_missing_page(tmp_path, monkeypatch): + root = _copy_tree(tmp_path) + _second_repo(root) + (root / "repos" / "demo-repo" / "core" / "rules.md").unlink() + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(root)) + _load_view.cache_clear() + with pytest.raises(FileNotFoundError): + load_routes("demo-repo") + + +def _snapshot(tmp_path: Path, name: str, retire: bool) -> Path: + base = tmp_path / "snapshots" / name + root = base / "knowledge" + shutil.copytree(KnowledgeView.current().root, root, + ignore=shutil.ignore_patterns("__pycache__")) + if retire: + (root / "repos" / "vllm-omni" / "_routes.yaml").write_text( + (root / "repos" / "vllm-omni" / "_routes.yaml").read_text(encoding="utf-8") + .replace("signals: [scheduler, scheduling,", "signals: [scheduling,"), + encoding="utf-8", + ) + (base / "MANIFEST.json").write_text(json.dumps(build_manifest(root, name)), encoding="utf-8") + return base + + +def test_activation_switch_takes_effect_on_next_request(tmp_path, monkeypatch): + old = _snapshot(tmp_path, "a" * 40, retire=False) + new = _snapshot(tmp_path, "b" * 40, retire=True) + active = tmp_path / "active" + active.symlink_to(old) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(active)) + _load_view.cache_clear() + + first = direct_review_plan("vllm-omni", title="scheduler fix", changed_files=[]) + assert first["diagnostics"]["knowledge_snapshot"] == "a" * 40 + assert any(r["owner"] == "scheduler" for r in first["knowledge_routes"]) + + tmp_link = tmp_path / "active.tmp" + tmp_link.symlink_to(new) + tmp_link.replace(active) # the atomic swap the knowledge service performs + + second = direct_review_plan("vllm-omni", title="scheduler fix", changed_files=[]) + assert second["diagnostics"]["knowledge_snapshot"] == "b" * 40 + assert not any(r["owner"] == "scheduler" for r in second["knowledge_routes"]) + + +def test_snapshot_tampering_fails_closed(tmp_path, monkeypatch): + base = _snapshot(tmp_path, "c" * 40, retire=False) + page = base / "knowledge" / "repos" / "vllm-omni" / "components" / "scheduler" / "rules.md" + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(base)) + _load_view.cache_clear() + view = KnowledgeView.current() + page.write_text(page.read_text(encoding="utf-8") + "\ninjected\n", encoding="utf-8") + with pytest.raises(KnowledgeViewError, match="does not match manifest"): + view.path("repos/vllm-omni/components/scheduler/rules.md") + + +def test_snapshot_with_unlisted_file_is_rejected(tmp_path, monkeypatch): + base = _snapshot(tmp_path, "d" * 40, retire=False) + (base / "knowledge" / "extra.md").write_text("x", encoding="utf-8") + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(base)) + _load_view.cache_clear() + with pytest.raises(KnowledgeViewError, match="differs from manifest"): + KnowledgeView.current() + + +def test_sdk_completion_pins_the_plan_snapshot(tmp_path, monkeypatch): + from infermatrix_copilot.sdk.v1 import ( + ChangedPath, DirectClient, DirectCompletionRequest, DirectReviewRequest, + RepositoryRef, + ) + + old = _snapshot(tmp_path, "e" * 40, retire=False) + new = _snapshot(tmp_path, "f" * 40, retire=True) + active = tmp_path / "active" + active.symlink_to(old) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(active)) + _load_view.cache_clear() + + client = DirectClient() + head = "1" * 40 + plan = client.plan(DirectReviewRequest( + review_id="r1", repository=RepositoryRef(alias="vllm-omni"), pr_number=1, + expected_head_sha=head, title="scheduler fix", body="", + changed_paths=(ChangedPath(path="vllm_omni/core/sched/a.py"),), + )) + assert plan.diagnostics["knowledge_snapshot"] == "e" * 40 + + tmp_link = tmp_path / "active.tmp" + tmp_link.symlink_to(new) + tmp_link.replace(active) + + decision = client.validate(DirectCompletionRequest( + review_context_id=plan.review_context_id, expected_head_sha=head, + evidence_head_sha=head, subtraction_signal="none", + existing_feedback_status="not_applicable", + )) + assert "provider resources changed after the review context was issued" not in decision.missing + + +def test_sdk_document_reads_follow_the_plan_snapshot(tmp_path, monkeypatch): + from infermatrix_copilot.sdk.v1 import ( + ChangedPath, DirectClient, DirectReviewRequest, RepositoryRef, + ) + + old = _snapshot(tmp_path, "7" * 40, retire=False) + new = _snapshot(tmp_path, "8" * 40, retire=True) + active = tmp_path / "active" + active.symlink_to(old) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(active)) + _load_view.cache_clear() + client = DirectClient() + plan = client.plan(DirectReviewRequest( + review_id="r2", repository=RepositoryRef(alias="vllm-omni"), pr_number=2, + expected_head_sha="2" * 40, title="scheduler fix", body="", + changed_paths=(ChangedPath(path="vllm_omni/core/sched/a.py"),), + )) + tmp_link = tmp_path / "active.tmp" + tmp_link.symlink_to(new) + tmp_link.replace(active) + + doc = "repos/vllm-omni/_routes.yaml" + pinned = client.read_document( + doc, max_bytes=65536, review_context_id=plan.review_context_id) + current = client.read_document(doc, max_bytes=65536) + assert "signals: [scheduler, scheduling," in pinned.content + assert "signals: [scheduler, scheduling," not in current.content + with pytest.raises(Exception, match="not issued"): + client.read_document(doc, review_context_id="sha256:" + "0" * 64) + + +def test_mcp_document_reader_refuses_tampered_snapshot(tmp_path, monkeypatch): + from infermatrix_copilot.knowledge_docs import KnowledgeDocs + from infermatrix_copilot.thin_mcp_server import _docs + + base = _snapshot(tmp_path, "9" * 40, retire=False) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(base)) + _load_view.cache_clear() + rel = "repos/vllm-omni/components/scheduler/rules.md" + docs = _docs("vllm-omni") + assert isinstance(docs, KnowledgeDocs) + assert docs.read(rel)["content"] + page = base / "knowledge" / rel + page.write_text(page.read_text(encoding="utf-8") + "\ninjected\n", encoding="utf-8") + with pytest.raises(KnowledgeViewError, match="does not match manifest"): + _docs("vllm-omni").read(rel) + with pytest.raises(KnowledgeViewError, match="does not match manifest"): + _docs("vllm-omni").search("scheduler") + + +@pytest.mark.parametrize("mutation", ["modify", "delete"]) +def test_sdk_completion_detects_post_plan_change_to_pinned_tree(tmp_path, monkeypatch, mutation): + from infermatrix_copilot.sdk.v1 import ( + ChangedPath, DirectClient, DirectCompletionRequest, DirectReviewRequest, + RepositoryRef, + ) + + base = _snapshot(tmp_path, ("5" if mutation == "modify" else "6") * 40, retire=False) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(base)) + _load_view.cache_clear() + client = DirectClient() + head = "3" * 40 + plan = client.plan(DirectReviewRequest( + review_id="r3", repository=RepositoryRef(alias="vllm-omni"), pr_number=3, + expected_head_sha=head, title="scheduler fix", body="", + changed_paths=(ChangedPath(path="vllm_omni/core/sched/a.py"),), + )) + page = base / "knowledge" / "repos" / "vllm-omni" / "components" / "scheduler" / "rules.md" + if mutation == "modify": + page.write_text(page.read_text(encoding="utf-8") + "\nchanged\n", encoding="utf-8") + else: + page.unlink() + decision = client.validate(DirectCompletionRequest( + review_context_id=plan.review_context_id, expected_head_sha=head, + evidence_head_sha=head, subtraction_signal="none", + existing_feedback_status="not_applicable", + )) + assert not decision.review_complete + assert "provider resources changed after the review context was issued" in decision.missing + + +def test_routes_table_deleted_from_loaded_snapshot_fails_closed(tmp_path, monkeypatch): + base = _snapshot(tmp_path, "4" * 40, retire=False) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(base)) + _load_view.cache_clear() + direct_routing._load_routes.cache_clear() + KnowledgeView.current() # loaded while intact + (base / "knowledge" / "repos" / "vllm-omni" / "_routes.yaml").unlink() + with pytest.raises(KnowledgeViewError, match="listed in manifest is missing"): + _direct_knowledge_routes( + "vllm-omni", title="scheduler", changed_files=["vllm_omni/core/sched/a.py"]) diff --git a/test/test_repo_vocabulary.py b/test/test_repo_vocabulary.py index f68fe25d..0ccd9adc 100644 --- a/test/test_repo_vocabulary.py +++ b/test/test_repo_vocabulary.py @@ -48,13 +48,9 @@ # class 1b: the delegation-by-design files carry EXACT counts (shrink-only # ceilings, like test_repo_neutral_core's) rather than a blanket skip — # they import/invoke the PARENT and die in PR7 -_FORBIDDEN_CEILINGS = { - # Direct's review-routing table (spec-freshness thin-mcp, merged from - # main 2026-08): vllm_omni/model_executor/ scope prefixes — repo table - # pending extraction to adapters/vllm_omni/. It moved out of - # thin_mcp_server.py into direct_routing.py so contract.py, the module - # consumers import, could stay repo-neutral; the debt moved with it. - "direct_routing.py": 2, +_FORBIDDEN_CEILINGS: dict[str, int] = { + # Direct's review-routing table used to sit here at 2; it now lives in + # knowledge/repos//_routes.yaml and the ceiling is gone. } # class 2: files ALLOWED to use the standalone `omni`/`vllm` parity @@ -84,12 +80,9 @@ # child-env behavior; adapter-configurable prefixes are the recorded # post-cutover refinement "testing/env_plan.py": 2, - # thin MCP server (merged from main 2026-08): embedded vllm-omni - # review-routing/doc table + instructions prose — same pending adapter - # extraction as the _FORBIDDEN ceiling above. The routing table moved to - # direct_routing.py so contract.py could be a repo-neutral public surface; - # 60 became 52 + 8, relocated rather than grown. - "direct_routing.py": 52, + # thin MCP server: tool defaults + instructions prose. The routing table + # that once shared this entry (52 in direct_routing.py) moved to + # knowledge/repos//_routes.yaml. "thin_mcp_server.py": 8, } diff --git a/test/test_thin_mcp_server.py b/test/test_thin_mcp_server.py index 2209fa23..7f6fec9f 100644 --- a/test/test_thin_mcp_server.py +++ b/test/test_thin_mcp_server.py @@ -788,13 +788,20 @@ def test_every_routed_page_yields_a_quick_map(): server the way a second copy of the heading regex would.""" from pathlib import Path - from infermatrix_copilot.thin_mcp_server import ( - _DIRECT_OWNER_ROUTES, _KNOWLEDGE, _direct_quick_map, _knowledge_path, - ) - - pages = [_knowledge_path(str(r["path"])) for r in _DIRECT_OWNER_ROUTES] - pages += [str(p) for p in - sorted((_KNOWLEDGE / "repos" / "vllm-omni" / "models").glob("*/rules.md"))] + from infermatrix_copilot.direct_routing import load_routes + from infermatrix_copilot.knowledge_view import KnowledgeView + from infermatrix_copilot.thin_mcp_server import _direct_quick_map, _knowledge_path + + root = KnowledgeView.current().root + pages = [] + for repo_dir in sorted((root / "repos").iterdir()): + table = load_routes(repo_dir.name) if repo_dir.is_dir() else None + if not table: + continue + pages += [_knowledge_path(str(r["path"])) for r in table["owners"]] + if table["models"]: + pages += [str(p) for p in sorted( + (root / table["models"]["dir"]).glob(f"*/{table['models']['page']}"))] assert pages, "no routed pages found — the enumeration itself is broken" # Pages whose Direct section exceeds the 3500-char cap. They still behave diff --git a/test/test_v2_p0.py b/test/test_v2_p0.py index 1b120fcd..348a7ade 100644 --- a/test/test_v2_p0.py +++ b/test/test_v2_p0.py @@ -317,16 +317,6 @@ def test_adapter_zero_declares_risk_tiers(): "intent.py": 2, # parse_* default_repo parameter defaults "rebase/monitor.py": 1, # locked-pipeline delegation (by design) "task_spec.py": 1, # TaskSpec.repo default - # KNOWN DEBT, not a design allowance: _DIRECT_OWNER_ROUTES/_REPO_ALIASES embed the - # served repo's owner table inline. Recorded at today's exact count so it can only - # shrink; the fix is externalizing them to adapters//routing.yaml. - # This was invisible until config.py dropped to its ceiling — the assertion below - # stops at the first offending file, so one leak masks every later one. - # The tables moved to direct_routing.py when Direct got a public home in - # contract.py; the debt moved with them (29 -> 24 + 4) rather than growing, - # and putting them in contract.py — the module consumers import — was - # exactly what this ratchet exists to prevent. - "direct_routing.py": 24, "thin_mcp_server.py": 4, } _LEAK = re.compile(r"vllm[_\- ]?omni|/rebase/", re.IGNORECASE)