[Bagel]: Support multistage img2img (#1669)

Signed-off-by: princepride <wangzhipeng628@gmail.com>
Signed-off-by: 汪志鹏 <wangzhipeng628@gmail.com>
This commit is contained in:
汪志鹏 authored and GitHub committed 2026-03-09 22:23:50 +08:00
1 parent 5628d41242
commit 7271116973
16 files changed
+1280 -164

No files matched your search

+69 -97
View File
@@ -1,8 +1,7 @@
import argparse
import os
from typing import cast
from vllm_omni.inputs.data import OmniDiffusionSamplingParams, OmniPromptType
from vllm_omni.inputs.data import OmniPromptType
def parse_args():
@@ -58,6 +57,7 @@ def parse_args():
choices=[1, 2, 3],
help="CFG parallel size: 1=batched (single GPU), 2=parallel with 2 branches (text CFG only), 3=parallel (3 GPUs).",
)
parser.add_argument("--seed", type=int, default=None, help="Random seed for generation.")
args = parser.parse_args()
return args
@@ -88,108 +88,80 @@ def main():
from PIL import Image
if args.modality == "img2img":
from vllm_omni.entrypoints.omni_diffusion import OmniDiffusion
from vllm_omni.entrypoints.omni import Omni
print(f"[Info] Running in {args.modality} mode (Stage 1 only, cfg_parallel_size={args.cfg_parallel_size})")
omni_kwargs = {}
if args.stage_configs_path:
omni_kwargs["stage_configs_path"] = args.stage_configs_path
client = OmniDiffusion(
model=model_name,
parallel_config={"cfg_parallel_size": args.cfg_parallel_size},
)
omni_kwargs.update(
{
"log_stats": args.log_stats,
"init_sleep_seconds": args.init_sleep_seconds,
"batch_timeout": args.batch_timeout,
"init_timeout": args.init_timeout,
"shm_threshold_bytes": args.shm_threshold_bytes,
"worker_backend": args.worker_backend,
"ray_address": args.ray_address,
}
)
if args.image_path:
if os.path.exists(args.image_path):
loaded_image = Image.open(args.image_path).convert("RGB")
prompts = [
{
"prompt": cast(str, p),
"multi_modal_data": {"image": loaded_image},
}
for p in prompts
]
else:
print(f"[Warning] Image path {args.image_path} does not exist.")
omni = Omni(model=model_name, **omni_kwargs)
result = client.generate(
prompts,
OmniDiffusionSamplingParams(
seed=52,
need_kv_receive=False,
num_inference_steps=args.steps,
extra_args={
"cfg_text_scale": args.cfg_text_scale,
"cfg_img_scale": args.cfg_img_scale,
},
),
)
# Ensure result is a list for iteration
if not isinstance(result, list):
omni_outputs = [result]
else:
omni_outputs = result
else:
from vllm_omni.entrypoints.omni import Omni
omni_kwargs = {}
if args.stage_configs_path:
omni_kwargs["stage_configs_path"] = args.stage_configs_path
omni_kwargs.update(
{
"log_stats": args.log_stats,
"init_sleep_seconds": args.init_sleep_seconds,
"batch_timeout": args.batch_timeout,
"init_timeout": args.init_timeout,
"shm_threshold_bytes": args.shm_threshold_bytes,
"worker_backend": args.worker_backend,
"ray_address": args.ray_address,
formatted_prompts = []
for p in prompts:
if args.modality == "img2img":
if not args.image_path or not os.path.exists(args.image_path):
raise ValueError(f"img2img requires --image-path pointing to an existing file, got: {args.image_path}")
loaded_image = Image.open(args.image_path).convert("RGB")
final_prompt_text = f"<|fim_middle|><|im_start|>{p}<|im_end|>"
prompt_dict = {
"prompt": final_prompt_text,
"multi_modal_data": {"img2img": loaded_image},
"modalities": ["img2img"],
}
)
omni = Omni(model=model_name, **omni_kwargs)
formatted_prompts = []
for p in args.prompts:
if args.modality == "img2text":
if args.image_path:
loaded_image = Image.open(args.image_path).convert("RGB")
final_prompt_text = f"<|im_start|>user\n<|image_pad|>\n{p}<|im_end|>\n<|im_start|>assistant\n"
prompt_dict = {
"prompt": final_prompt_text,
"multi_modal_data": {"image": loaded_image},
"modalities": ["text"],
}
formatted_prompts.append(prompt_dict)
elif args.modality == "text2text":
final_prompt_text = f"<|im_start|>user\n{p}<|im_end|>\n<|im_start|>assistant\n"
prompt_dict = {"prompt": final_prompt_text, "modalities": ["text"]}
formatted_prompts.append(prompt_dict)
else:
# text2img
final_prompt_text = f"<|im_start|>{p}<|im_end|>"
prompt_dict = {"prompt": final_prompt_text, "modalities": ["image"]}
if args.negative_prompt is not None:
prompt_dict["negative_prompt"] = args.negative_prompt
formatted_prompts.append(prompt_dict)
params_list = omni.default_sampling_params_list
if args.modality == "text2img":
params_list[0].max_tokens = 1 # type: ignore # The first stage is a SamplingParam (vllm)
if len(params_list) > 1:
diffusion_params = params_list[1]
diffusion_params.num_inference_steps = args.steps # type: ignore
extra = {
"cfg_text_scale": args.cfg_text_scale,
"cfg_img_scale": args.cfg_img_scale,
if args.negative_prompt is not None:
prompt_dict["negative_prompt"] = args.negative_prompt
formatted_prompts.append(prompt_dict)
elif args.modality == "img2text":
if args.image_path:
loaded_image = Image.open(args.image_path).convert("RGB")
final_prompt_text = f"<|im_start|>user\n<|image_pad|>\n{p}<|im_end|>\n<|im_start|>assistant\n"
prompt_dict = {
"prompt": final_prompt_text,
"multi_modal_data": {"image": loaded_image},
"modalities": ["text"],
}
if args.negative_prompt is not None:
extra["negative_prompt"] = args.negative_prompt
diffusion_params.extra_args = extra # type: ignore
formatted_prompts.append(prompt_dict)
elif args.modality == "text2text":
final_prompt_text = f"<|im_start|>user\n{p}<|im_end|>\n<|im_start|>assistant\n"
prompt_dict = {"prompt": final_prompt_text, "modalities": ["text"]}
formatted_prompts.append(prompt_dict)
else:
final_prompt_text = f"<|im_start|>{p}<|im_end|>"
prompt_dict = {"prompt": final_prompt_text, "modalities": ["image"]}
if args.negative_prompt is not None:
prompt_dict["negative_prompt"] = args.negative_prompt
formatted_prompts.append(prompt_dict)
omni_outputs = list(omni.generate(prompts=formatted_prompts, sampling_params_list=params_list))
params_list = omni.default_sampling_params_list
if args.modality in ("text2img", "img2img"):
params_list[0].max_tokens = 1 # type: ignore
if len(params_list) > 1:
diffusion_params = params_list[1]
diffusion_params.num_inference_steps = args.steps # type: ignore
diffusion_params.cfg_parallel_size = args.cfg_parallel_size # type: ignore
if args.seed is not None:
diffusion_params.seed = args.seed # type: ignore
extra = {
"cfg_text_scale": args.cfg_text_scale,
"cfg_img_scale": args.cfg_img_scale,
}
if args.negative_prompt is not None:
extra["negative_prompt"] = args.negative_prompt
diffusion_params.extra_args = extra # type: ignore
omni_outputs = list(omni.generate(prompts=formatted_prompts, sampling_params_list=params_list))
for i, req_output in enumerate(omni_outputs):
images = getattr(req_output, "images", None)
@@ -6,6 +6,7 @@ from vllm_omni.distributed.omni_connectors.kv_transfer_manager import (
OmniKVCacheConfig,
OmniKVTransferManager,
)
from vllm_omni.distributed.omni_connectors.utils.kv_utils import normalize_layer_kv
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
pytestmark = [pytest.mark.core_model, pytest.mark.cpu, pytest.mark.cache]
@@ -192,8 +193,7 @@ def test_normalize_layer_kv_rejects_invalid_inputs(kv_config, common_constants,
else:
layer_kv = (torch.randn(2, block_size, num_heads, head_dim), "not-a-tensor")
manager = OmniKVTransferManager(kv_config)
normalized = manager._normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=0)
normalized = normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=0)
assert normalized is None
@@ -13,7 +13,7 @@ stage_args:
model_arch: BagelForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.35
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: text
@@ -46,7 +46,7 @@ stage_args:
max_batch_size: 1
engine_args:
model_stage: dit
gpu_memory_utilization: 0.55
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: image
@@ -13,7 +13,7 @@ stage_args:
model_arch: BagelForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.35
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: text
@@ -45,7 +45,7 @@ stage_args:
max_batch_size: 1
engine_args:
model_stage: dit
gpu_memory_utilization: 0.55
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: image
@@ -332,6 +332,9 @@ class BagelPipeline(nn.Module):
else:
gen_context["ropes"] = [seq_len]
if req.sampling_params.kv_metadata and "image_shape" in req.sampling_params.kv_metadata:
image_shape = tuple(req.sampling_params.kv_metadata["image_shape"])
cfg_text_kv = getattr(req.sampling_params, "cfg_text_past_key_values", None)
if cfg_text_kv is not None:
logger.info("CFG enabled with multi-KV: using injected cfg_text KV Cache")
@@ -12,6 +12,7 @@ from vllm.logger import init_logger
from .factory import OmniConnectorFactory
from .utils.config import ConnectorSpec
from .utils.kv_utils import normalize_layer_kv
logger = init_logger(__name__)
@@ -200,8 +201,12 @@ class OmniKVTransferManager:
logger.warning(f"Request {req_id} has no block IDs, skipping")
continue
custom_metadata = data.get("custom_metadata")
# Extract KV cache from GPU blocks -> CPU tensors
kv_data = self._extract_kv_cache(req_id, block_ids, seq_len, kv_caches, block_size, cache_dtype)
kv_data = self._extract_kv_cache(
req_id, block_ids, seq_len, kv_caches, block_size, cache_dtype, custom_metadata
)
if kv_data:
# Resolve global request ID if available
transfer_req_id = request_id_resolver(req_id) if request_id_resolver else req_id
@@ -224,6 +229,7 @@ class OmniKVTransferManager:
kv_caches: list[LayerKV],
block_size: int,
cache_dtype: str,
custom_metadata: dict[str, Any] | None = None,
) -> KVCacheTransferData | None:
"""Extract KV cache from GPU blocks for a single request.
@@ -234,6 +240,7 @@ class OmniKVTransferManager:
kv_caches: List of KV cache (tensor or tuple) per layer
block_size: Size of each cache block
cache_dtype: Data type of the cache
custom_metadata: Optional custom metadata to include
Note: If key/value block counts differ, extraction uses only the overlapping
block range. Extra key/value blocks are ignored, so returned KV may be partial.
@@ -246,7 +253,7 @@ class OmniKVTransferManager:
value_cache: list[torch.Tensor | None] = [None] * num_layers
for layer_idx, layer_kv in enumerate(kv_caches):
kv_pair = self._normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=layer_idx)
kv_pair = normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=layer_idx)
if kv_pair is None:
continue
key_blocks, value_blocks = kv_pair
@@ -289,57 +296,10 @@ class OmniKVTransferManager:
"num_layers": num_layers,
"dtype": str(cache_dtype),
"seq_len": seq_len,
**(custom_metadata or {}),
},
)
def _normalize_layer_kv(
self,
layer_kv: LayerKV,
req_id: str,
layer_idx: int,
) -> tuple[torch.Tensor, torch.Tensor] | None:
"""Normalize one layer KV cache to a `(key_blocks, value_blocks)` tuple.
Args:
layer_kv: The raw KV cache (tensor or tuple) for the layer
req_id: Request ID for logging
layer_idx: Layer index for logging
Returns:
Tuple of (key_blocks, value_blocks) if valid, None otherwise
"""
if isinstance(layer_kv, torch.Tensor):
if layer_kv.ndim < 3 or layer_kv.shape[0] != 2:
logger.warning(
f"Layer {layer_idx} for request {req_id} has invalid stacked KV shape: "
f"expected [2, blocks, block_size, ...], got {tuple(layer_kv.shape)}"
)
return None
key_blocks = layer_kv[0]
value_blocks = layer_kv[1]
elif isinstance(layer_kv, tuple):
if len(layer_kv) != 2:
logger.warning(
f"Layer {layer_idx} for request {req_id} has KV pair length {len(layer_kv)} (expected 2)"
)
return None
key_blocks, value_blocks = layer_kv
if not isinstance(key_blocks, torch.Tensor) or not isinstance(value_blocks, torch.Tensor):
logger.warning(f"Layer {layer_idx} for request {req_id} has non-tensor KV pair entries")
return None
else:
logger.warning(f"Layer {layer_idx} for request {req_id} has unsupported KV type {type(layer_kv).__name__}")
return None
# ensure key/value blocks are at least 2D for block indexing
if key_blocks.ndim < 2 or value_blocks.ndim < 2:
logger.warning(
f"Layer {layer_idx} for request {req_id} has invalid KV block shape: "
f"got key={tuple(key_blocks.shape)} value={tuple(value_blocks.shape)}"
)
return None
return key_blocks, value_blocks
def _transfer_kv_cache(self, kv_data: KVCacheTransferData, transfer_req_id: str) -> None:
"""Transfer KV cache data to downstream stage via OmniConnector.
@@ -496,6 +456,8 @@ class OmniKVTransferManager:
if "metadata" in data:
req.kv_metadata = data["metadata"]
if hasattr(req, "sampling_params") and req.sampling_params is not None:
req.sampling_params.kv_metadata = data["metadata"]
# Legacy compatibility method
def receive_kv_cache(self, req: Any, target_device: torch.device | None = None) -> bool:
@@ -0,0 +1,85 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Utility helpers for KV cache manipulation."""
import torch
from vllm.logger import init_logger
logger = init_logger(__name__)
LayerKV = torch.Tensor | tuple[torch.Tensor, torch.Tensor]
def normalize_layer_kv(
layer_kv: LayerKV,
*,
req_id: str = "",
layer_idx: int = -1,
) -> tuple[torch.Tensor, torch.Tensor] | None:
"""Normalize one layer KV cache to a ``(key_blocks, value_blocks)`` tuple.
In vLLM, different attention backends return paged-attention KV blocks
with different layouts. For example:
* **FlashAttention** (``vllm/v1/attention/backends/flash_attn.py``)
returns shape ``(2, num_blocks, block_size, num_kv_heads, head_size)``
– the key/value dimension is at **dim 0**.
* **FlashInfer** (``vllm/v1/attention/backends/flashinfer.py``)
returns shape ``(num_blocks, 2, block_size, num_kv_heads, head_size)``
– the key/value dimension is at **dim 1**.
This utility handles both layouts (and the tuple case) so that
downstream code can work with any backend.
Supported layouts:
* **Stacked tensor** ``[2, num_blocks, block_size, n_heads, head_dim]`` –
dim-0 selects key / value.
* **Stacked tensor** ``[num_blocks, 2, block_size, n_heads, head_dim]`` –
dim-1 selects key / value.
* **Tuple** ``(key_tensor, value_tensor)`` – returned as-is after
validation.
Args:
layer_kv: The raw KV cache (tensor or tuple) for the layer.
req_id: Request ID used only for diagnostic log messages.
layer_idx: Layer index used only for diagnostic log messages.
Returns:
``(key_blocks, value_blocks)`` if *layer_kv* is valid, ``None``
otherwise.
"""
if isinstance(layer_kv, torch.Tensor):
if layer_kv.ndim >= 3 and layer_kv.shape[0] == 2:
key_blocks = layer_kv[0]
value_blocks = layer_kv[1]
elif layer_kv.ndim >= 3 and layer_kv.shape[1] == 2:
key_blocks = layer_kv[:, 0]
value_blocks = layer_kv[:, 1]
else:
logger.warning(
f"Layer {layer_idx} for request {req_id} has invalid stacked KV shape: "
f"expected [2, ...] or [..., 2, ...] at dim 0/1, got {tuple(layer_kv.shape)}"
)
return None
elif isinstance(layer_kv, tuple):
if len(layer_kv) != 2:
logger.warning(f"Layer {layer_idx} for request {req_id} has KV pair length {len(layer_kv)} (expected 2)")
return None
key_blocks, value_blocks = layer_kv
if not isinstance(key_blocks, torch.Tensor) or not isinstance(value_blocks, torch.Tensor):
logger.warning(f"Layer {layer_idx} for request {req_id} has non-tensor KV pair entries")
return None
else:
logger.warning(f"Layer {layer_idx} for request {req_id} has unsupported KV type {type(layer_kv).__name__}")
return None
# ensure key/value blocks are at least 2D for block indexing
if key_blocks.ndim < 2 or value_blocks.ndim < 2:
logger.warning(
f"Layer {layer_idx} for request {req_id} has invalid KV block shape: "
f"got key={tuple(key_blocks.shape)} value={tuple(value_blocks.shape)}"
)
return None
return key_blocks, value_blocks
+5 -1
View File
@@ -1,4 +1,8 @@
from .bagel.bagel import OmniBagelForConditionalGeneration
from .qwen3_omni import Qwen3OmniMoeForConditionalGeneration
from .registry import OmniModelRegistry # noqa: F401
__all__ = ["Qwen3OmniMoeForConditionalGeneration"]
__all__ = [
"Qwen3OmniMoeForConditionalGeneration",
"OmniBagelForConditionalGeneration",
]
@@ -0,0 +1,3 @@
from .bagel import OmniBagelForConditionalGeneration
__all__ = ["OmniBagelForConditionalGeneration"]
File diff suppressed because it is too large. Load diff
@@ -119,6 +119,11 @@ _OMNI_MODELS = {
"glm_image_ar",
"GlmImageForConditionalGeneration",
),
"OmniBagelForConditionalGeneration": (
"bagel",
"bagel",
"OmniBagelForConditionalGeneration",
),
"HunyuanImage3ForCausalMM": (
"hunyuan_image3",
"hunyuan_image3",
@@ -6,15 +6,14 @@ stage_args:
prompt_expand_func: vllm_omni.model_executor.stage_input_processors.bagel.expand_cfg_prompts
runtime:
devices: "0"
# 2 = 1 user prompt + 1 CFG companion (text-unconditional).
# For multi-prompt batches this should scale as batch_size × 2.
max_batch_size: 2
# 3 = 1 user prompt + 2 CFG companions (text-unconditional + image-unconditional).
max_batch_size: 3
engine_args:
model_stage: thinker
model_arch: BagelForConditionalGeneration
model_arch: OmniBagelForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.35
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: text
@@ -46,7 +45,7 @@ stage_args:
max_batch_size: 1
engine_args:
model_stage: dit
gpu_memory_utilization: 0.55
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: image
@@ -11,7 +11,7 @@ stage_args:
model_arch: BagelForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.35
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: text
@@ -45,7 +45,7 @@ stage_args:
max_batch_size: 1
engine_args:
model_stage: dit
gpu_memory_utilization: 0.55
gpu_memory_utilization: 0.45
enforce_eager: true
trust_remote_code: true
engine_output_type: image
@@ -20,6 +20,7 @@ from typing import Any
logger = logging.getLogger(__name__)
CFG_TEXT_SUFFIX = "__cfg_text"
CFG_IMG_SUFFIX = "__cfg_img"
@dataclass
@@ -73,7 +74,37 @@ def expand_cfg_prompts(
),
]
# img2img: more complex (3 distinct caches). Reserve for future.
if "img2img" in modalities:
IMG2IMG_PLACEHOLDER = "<|fim_middle|>"
cfg_text_dict: dict[str, Any] = {
"prompt": f"{IMG2IMG_PLACEHOLDER}{neg_prompt}",
"modalities": ["img2img"],
}
mm_data = prompt.get("multi_modal_data")
if mm_data:
cfg_text_dict["multi_modal_data"] = mm_data
original_text = prompt.get("prompt", "")
cfg_img_text = original_text.replace(IMG2IMG_PLACEHOLDER, "")
cfg_img_dict: dict[str, Any] = {
"prompt": cfg_img_text,
"modalities": ["img2img"],
}
return [
ExpandedPrompt(
prompt=cfg_text_dict,
role="cfg_text",
request_id_suffix=CFG_TEXT_SUFFIX,
),
ExpandedPrompt(
prompt=cfg_img_dict,
role="cfg_img",
request_id_suffix=CFG_IMG_SUFFIX,
),
]
return []
+16
View File
@@ -1,6 +1,8 @@
import sys
from functools import cached_property as _cached_property
from aenum import extend_enum
from vllm.config import ModelConfig as _ModelConfig
from vllm.inputs.data import TokensPrompt as _OriginalTokensPrompt
from vllm.model_executor.layers.rotary_embedding import (
MRotaryEmbedding as _OriginalMRotaryEmbedding,
@@ -43,6 +45,20 @@ except ImportError:
# GlmImageTextConfig not available, skip patching
pass
# Patch ModelConfig.is_mm_prefix_lm to include Bagel (bidirectional attention
# for multimodal prefix positions, same as Gemma3/Molmo2/PaliGemma).
_orig_is_mm_prefix_lm = _ModelConfig.__dict__["is_mm_prefix_lm"].func
@_cached_property
def _patched_is_mm_prefix_lm(self) -> bool:
return _orig_is_mm_prefix_lm(self) or getattr(self.hf_config, "model_type", None) == "bagel"
_patched_is_mm_prefix_lm.__set_name__(_ModelConfig, "is_mm_prefix_lm")
_ModelConfig.is_mm_prefix_lm = _patched_is_mm_prefix_lm
# Extend RequestStatus enum with omni-specific statuses
if not hasattr(RequestStatus, "WAITING_FOR_CHUNK"):
# The value - 1 is intentionally chosen to ensure it is treated
+21 -1
View File
@@ -98,9 +98,25 @@ class GPUARModelRunner(OmniGPUModelRunner):
if self.execute_model_state is not None:
raise RuntimeError("State error: sample_tokens() must be called after execute_model() returns None.")
if not getattr(self, "_warmup_state_cleared", False):
self._warmup_state_cleared = True
if hasattr(self.model, "_clear_warmup_state"):
self.model._clear_warmup_state()
# [Omni] Handle KV transfer BEFORE updating states (which removes finished requests)
finished_reqs = getattr(scheduler_output, "finished_requests_needing_kv_transfer", {})
if finished_reqs and hasattr(self.model, "get_kv_transfer_metadata"):
for req_id, data in finished_reqs.items():
try:
model_meta = self.model.get_kv_transfer_metadata(req_id)
if model_meta:
existing = data.get("custom_metadata") or {}
existing.update(model_meta)
data["custom_metadata"] = existing
except Exception as e:
logger.warning(f"Failed to get custom metadata from model for {req_id}: {e}")
self.kv_extracted_req_ids = self.kv_transfer_manager.handle_finished_requests_kv_transfer(
finished_reqs=getattr(scheduler_output, "finished_requests_needing_kv_transfer", {}),
finished_reqs=finished_reqs,
kv_caches=self.kv_caches,
block_size=self.cache_config.block_size,
cache_dtype=str(self.cache_config.cache_dtype),
@@ -290,6 +306,10 @@ class GPUARModelRunner(OmniGPUModelRunner):
sampler=self.sampler,
)
# [Omni] Map pending ropes metadata to req_ids.
if hasattr(self.model, "flush_pending_metadata"):
self.model.flush_pending_metadata(list(req_ids))
with record_function_or_nullcontext("gpu_model_runner: postprocess"):
if self.use_aux_hidden_state_outputs:
# True when EAGLE 3 is used.