Merge remote-tracking branch 'remotes/origin/main' into dev/vllm-align
This commit is contained in:
commit
367ce6328c
10 files changed
+732
-304
No files matched your search
@@ -34,7 +34,14 @@ def parse_args():
|
||||
help="Path to input image for img2img.",
|
||||
)
|
||||
|
||||
# Omni runtime init args
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=str,
|
||||
default=".",
|
||||
help="Output directory to save images.",
|
||||
)
|
||||
|
||||
# OmniLLM init args
|
||||
parser.add_argument("--log-stats", action="store_true", default=False)
|
||||
parser.add_argument("--init-sleep-seconds", type=int, default=20)
|
||||
parser.add_argument("--batch-timeout", type=int, default=5)
|
||||
@@ -70,6 +77,7 @@ def parse_args():
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
os.makedirs(args.output, exist_ok=True)
|
||||
model_name = args.model
|
||||
prompts: list[OmniPromptType] = []
|
||||
try:
|
||||
@@ -172,13 +180,13 @@ def main():
|
||||
img_idx = 0
|
||||
for req_output in omni_outputs:
|
||||
images = getattr(req_output, "images", None)
|
||||
|
||||
if not images:
|
||||
continue
|
||||
|
||||
for j, img in enumerate(images):
|
||||
save_path = f"output_{img_idx}_{j}.png"
|
||||
save_path = os.path.join(args.output, f"output_{img_idx}_{j}.png")
|
||||
img.save(save_path)
|
||||
print(f"[Info] Saved image to {save_path}")
|
||||
img_idx += 1
|
||||
|
||||
print(omni_outputs)
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
"""Tests for CUDAGraphWrapper.__getattr__ performance optimization.
|
||||
|
||||
This module tests that the patched CUDAGraphWrapper avoids expensive __repr__
|
||||
calls when hasattr() is used for non-existent attributes. The original vLLM
|
||||
implementation includes {self.runnable} in the AttributeError message, which
|
||||
triggers model tree traversal and can take ~6ms on large models.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from vllm_omni.worker.gpu_model_runner import CUDAGraphWrapper
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class SlowReprModel(nn.Module):
|
||||
"""A mock model with artificially slow __repr__ to detect unwanted calls."""
|
||||
|
||||
def __init__(self, repr_delay_ms: float = 10.0):
|
||||
super().__init__()
|
||||
self.linear = nn.Linear(16, 16)
|
||||
self.repr_delay_ms = repr_delay_ms
|
||||
self.repr_call_count = 0
|
||||
|
||||
def forward(self, x):
|
||||
return self.linear(x)
|
||||
|
||||
def __repr__(self):
|
||||
self.repr_call_count += 1
|
||||
# Simulate expensive repr by sleeping
|
||||
time.sleep(self.repr_delay_ms / 1000.0)
|
||||
return f"SlowReprModel(delay={self.repr_delay_ms}ms)"
|
||||
|
||||
|
||||
class MockCUDAGraphWrapper:
|
||||
"""A minimal mock that mimics CUDAGraphWrapper structure for CPU testing."""
|
||||
|
||||
def __init__(self, runnable):
|
||||
# Store in __dict__ directly to avoid triggering __getattr__
|
||||
object.__setattr__(self, "runnable", runnable)
|
||||
|
||||
def __getattr__(self, key: str):
|
||||
# This is the optimized implementation we're testing
|
||||
runnable = object.__getattribute__(self, "runnable")
|
||||
if hasattr(runnable, key):
|
||||
return getattr(runnable, key)
|
||||
# Key optimization: DO NOT include {self.runnable} in error message
|
||||
# as it triggers expensive __repr__ on large models
|
||||
raise AttributeError(f"Attribute {key} not exists in the runnable of cudagraph wrapper")
|
||||
|
||||
|
||||
def test_hasattr_nonexistent_does_not_trigger_repr():
|
||||
"""Verify that hasattr for non-existent attributes doesn't call __repr__."""
|
||||
model = SlowReprModel(repr_delay_ms=100.0) # Very slow repr
|
||||
wrapper = MockCUDAGraphWrapper(model)
|
||||
|
||||
# Reset counter
|
||||
model.repr_call_count = 0
|
||||
|
||||
# Call hasattr for non-existent attribute multiple times
|
||||
for _ in range(10):
|
||||
result = hasattr(wrapper, "nonexistent_attribute_xyz")
|
||||
assert result is False
|
||||
|
||||
# __repr__ should never have been called
|
||||
assert model.repr_call_count == 0, (
|
||||
f"__repr__ was called {model.repr_call_count} times when checking "
|
||||
"for non-existent attributes. This indicates the AttributeError "
|
||||
"message contains {self.runnable} which triggers expensive repr."
|
||||
)
|
||||
|
||||
|
||||
def test_hasattr_nonexistent_is_fast():
|
||||
"""Verify that hasattr for non-existent attributes is fast (<1ms per call)."""
|
||||
model = SlowReprModel(repr_delay_ms=100.0)
|
||||
wrapper = MockCUDAGraphWrapper(model)
|
||||
|
||||
num_iterations = 100
|
||||
start = time.perf_counter()
|
||||
for _ in range(num_iterations):
|
||||
hasattr(wrapper, "nonexistent_attribute_xyz")
|
||||
elapsed_ms = (time.perf_counter() - start) * 1000
|
||||
|
||||
avg_ms = elapsed_ms / num_iterations
|
||||
# If __repr__ were being called, each would take ~100ms
|
||||
# We expect <1ms per call with the fix
|
||||
assert avg_ms < 1.0, (
|
||||
f"hasattr for non-existent attribute took {avg_ms:.2f}ms on average. "
|
||||
"Expected <1ms. This suggests __repr__ is being triggered."
|
||||
)
|
||||
|
||||
|
||||
def test_hasattr_existing_attribute_works():
|
||||
"""Verify that hasattr for existing attributes returns True and works correctly."""
|
||||
model = SlowReprModel()
|
||||
wrapper = MockCUDAGraphWrapper(model)
|
||||
|
||||
# 'forward' exists on nn.Module
|
||||
assert hasattr(wrapper, "forward") is True
|
||||
|
||||
# 'linear' exists on our model
|
||||
assert hasattr(wrapper, "linear") is True
|
||||
|
||||
# Can actually access the attribute
|
||||
linear = wrapper.linear
|
||||
assert isinstance(linear, nn.Linear)
|
||||
|
||||
|
||||
def test_getattr_existing_attribute_returns_value():
|
||||
"""Verify that getattr for existing attributes returns the correct value."""
|
||||
model = SlowReprModel()
|
||||
wrapper = MockCUDAGraphWrapper(model)
|
||||
|
||||
# Access forward method
|
||||
forward_method = wrapper.forward
|
||||
assert callable(forward_method)
|
||||
|
||||
# Access linear layer
|
||||
linear = wrapper.linear
|
||||
assert isinstance(linear, nn.Linear)
|
||||
assert linear.in_features == 16
|
||||
assert linear.out_features == 16
|
||||
|
||||
|
||||
def test_getattr_nonexistent_raises_attribute_error():
|
||||
"""Verify that getattr for non-existent attributes raises AttributeError."""
|
||||
model = SlowReprModel()
|
||||
wrapper = MockCUDAGraphWrapper(model)
|
||||
|
||||
with pytest.raises(AttributeError) as exc_info:
|
||||
_ = wrapper.nonexistent_attribute
|
||||
|
||||
# Verify error message format (should NOT contain model repr)
|
||||
error_msg = str(exc_info.value)
|
||||
assert "nonexistent_attribute" in error_msg
|
||||
assert "cudagraph wrapper" in error_msg
|
||||
# Should NOT contain the slow repr output
|
||||
assert "SlowReprModel(delay=" not in error_msg
|
||||
|
||||
|
||||
def test_attribute_error_message_does_not_contain_runnable_repr():
|
||||
"""Explicitly verify the error message doesn't trigger runnable repr."""
|
||||
model = SlowReprModel(repr_delay_ms=100.0)
|
||||
wrapper = MockCUDAGraphWrapper(model)
|
||||
model.repr_call_count = 0
|
||||
|
||||
try:
|
||||
_ = wrapper.nonexistent_attr
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# __repr__ should not have been called during error construction
|
||||
assert model.repr_call_count == 0, (
|
||||
"AttributeError message construction triggered __repr__. The error message should not include {self.runnable}."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
||||
def test_real_cudagraph_wrapper_hasattr_performance():
|
||||
"""Test the actual CUDAGraphWrapper from vllm_omni (requires CUDA)."""
|
||||
from vllm.config import CUDAGraphMode
|
||||
|
||||
model = SlowReprModel(repr_delay_ms=50.0).cuda()
|
||||
model.repr_call_count = 0
|
||||
|
||||
# Create actual CUDAGraphWrapper
|
||||
try:
|
||||
wrapper = CUDAGraphWrapper(model, runtime_mode=CUDAGraphMode.NONE)
|
||||
except Exception:
|
||||
pytest.skip("Could not create CUDAGraphWrapper")
|
||||
|
||||
# Test hasattr performance
|
||||
num_iterations = 50
|
||||
start = time.perf_counter()
|
||||
for _ in range(num_iterations):
|
||||
hasattr(wrapper, "nonexistent_xyz")
|
||||
elapsed_ms = (time.perf_counter() - start) * 1000
|
||||
|
||||
avg_ms = elapsed_ms / num_iterations
|
||||
assert avg_ms < 1.0, f"Real CUDAGraphWrapper hasattr took {avg_ms:.2f}ms avg. Expected <1ms with the optimization."
|
||||
assert model.repr_call_count == 0, f"__repr__ called {model.repr_call_count} times"
|
||||
@@ -897,13 +897,15 @@ class HeliosPipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPip
|
||||
_, ph, pw = patch_size
|
||||
block_size = ph * pw
|
||||
|
||||
device = generator.device if generator is not None else self.device
|
||||
|
||||
cov = torch.eye(block_size) * (1 + gamma) - torch.ones(block_size, block_size) * gamma
|
||||
cov += torch.eye(block_size) * 1e-8
|
||||
cov = cov.float() # Upcast to fp32 for numerical stability — cholesky is unreliable in fp16/bf16.
|
||||
|
||||
L = torch.linalg.cholesky(cov)
|
||||
L = torch.linalg.cholesky(cov).to(device)
|
||||
block_number = batch_size * channel * num_frames * (height // ph) * (width // pw)
|
||||
z = torch.randn(block_number, block_size, generator=generator)
|
||||
z = torch.randn(block_number, block_size, generator=generator, device=device)
|
||||
noise = z @ L.T
|
||||
|
||||
noise = noise.view(batch_size, channel, num_frames, height // ph, width // pw, ph, pw)
|
||||
|
||||
@@ -685,6 +685,7 @@ class OvisImagePipeline(nn.Module, CFGParallelMixin, DiffusionPipelineProfilerMi
|
||||
|
||||
negative_text_ids = None
|
||||
if do_classifier_free_guidance:
|
||||
negative_prompt = negative_prompt if negative_prompt is not None else ""
|
||||
negative_prompt_embeds, negative_text_ids = self.encode_prompt(
|
||||
prompt=negative_prompt,
|
||||
prompt_embeds=negative_prompt_embeds,
|
||||
|
||||
@@ -41,6 +41,7 @@ from vllm.multimodal.processing import (
|
||||
)
|
||||
from vllm.transformers_utils.processors.bagel import BagelProcessor
|
||||
|
||||
from vllm_omni.diffusion.distributed.utils import get_local_device
|
||||
from vllm_omni.diffusion.models.bagel.autoencoder import (
|
||||
AutoEncoderParams,
|
||||
DiagonalGaussian,
|
||||
@@ -439,7 +440,7 @@ class OmniBagelForConditionalGeneration(BagelForConditionalGeneration):
|
||||
self._end_of_image_id = int(_tok.convert_tokens_to_ids("<|vision_end|>"))
|
||||
|
||||
self._vae_token_mask: torch.Tensor | None = None
|
||||
|
||||
self.device = get_local_device()
|
||||
self._install_mot_modules(config)
|
||||
|
||||
def _install_mot_modules(self, config):
|
||||
@@ -627,7 +628,7 @@ class OmniBagelForConditionalGeneration(BagelForConditionalGeneration):
|
||||
)
|
||||
pos_embed = self.latent_pos_embed([vae_position_ids])
|
||||
packed_timesteps = torch.tensor([timestep], device=padded_latent.device)
|
||||
with torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
||||
with torch.amp.autocast(self.device.type, dtype=torch.bfloat16):
|
||||
timestep_embeds = self.time_embedder(packed_timesteps.to(padded_latent))
|
||||
vae_embeds = self.vae2llm(latent) + timestep_embeds + pos_embed
|
||||
|
||||
|
||||
@@ -159,12 +159,6 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
# suppress tokens by setting their probability to ~1e-9 (finite very small)
|
||||
self.suppressed_tokens = self._get_talker_suppressed_tokens()
|
||||
self.requires_raw_input_tokens = True
|
||||
# Keys that should stay on GPU in model_intermediate_buffer to avoid CPU↔GPU round-trips
|
||||
self.gpu_resident_buffer_keys: set[str] = {
|
||||
"last_talker_hidden",
|
||||
"trailing_text_hidden",
|
||||
"tts_pad_embed_projected",
|
||||
}
|
||||
|
||||
elif self.model_stage == "code2wav":
|
||||
self.enable_update_additional_information = True
|
||||
@@ -229,16 +223,14 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
|
||||
# ==================== Forward Pass ====================
|
||||
def _get_talker_suppressed_tokens(self):
|
||||
"""Return a boolean mask on GPU for suppressed token positions."""
|
||||
vocab_size = self.config.talker_config.text_config.vocab_size
|
||||
mask = torch.zeros(vocab_size, dtype=torch.bool)
|
||||
start = vocab_size - 1024
|
||||
eos_id = self.config.talker_config.codec_eos_token_id
|
||||
for i in range(start, vocab_size):
|
||||
if i != eos_id:
|
||||
mask[i] = True
|
||||
# Will be moved to the correct device on first use
|
||||
return mask
|
||||
return [
|
||||
i
|
||||
for i in range(
|
||||
self.config.talker_config.text_config.vocab_size - 1024,
|
||||
self.config.talker_config.text_config.vocab_size,
|
||||
)
|
||||
if i != self.config.talker_config.codec_eos_token_id
|
||||
]
|
||||
|
||||
def get_mrope_input_positions(
|
||||
self,
|
||||
@@ -586,7 +578,7 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
Postprocess the talker hidden states.
|
||||
"""
|
||||
update_dict = {}
|
||||
update_dict["last_talker_hidden"] = hidden_states[-1, :].detach()
|
||||
update_dict["last_talker_hidden"] = hidden_states[-1, :].detach().to("cpu").contiguous()
|
||||
return update_dict
|
||||
|
||||
def talker_preprocess(self, input_ids: torch.Tensor, input_embeds: torch.Tensor, **info_dict: dict):
|
||||
@@ -640,9 +632,9 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
if inputs_embeds.shape[-1] == 2048:
|
||||
inputs_embeds = self.text_projection(inputs_embeds)
|
||||
code_predictor_codes, summed_embeddings = self.talker.code_predictor_forward(
|
||||
input_ids, inputs_embeds, last_talker_hidden=last_talker_hidden
|
||||
input_ids, inputs_embeds.clone(), last_talker_hidden=last_talker_hidden
|
||||
)
|
||||
inputs_embeds = summed_embeddings
|
||||
inputs_embeds = summed_embeddings.clone()
|
||||
inputs_embeds = (inputs_embeds + text_step).reshape(-1, self.talker_config.text_config.hidden_size)
|
||||
return inputs_embeds, code_predictor_codes.squeeze(-1)
|
||||
|
||||
@@ -766,7 +758,7 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
# compatible with old shape [1,S,D]
|
||||
rem_tail = trailing_text_hidden.squeeze(0)
|
||||
if rem_tail.shape[0] > 0:
|
||||
update_dict["trailing_text_hidden"] = rem_tail.detach()
|
||||
update_dict["trailing_text_hidden"] = rem_tail.detach().to("cpu").contiguous()
|
||||
# Also persist projected tts_pad for decode fallback if needed
|
||||
if isinstance(tts_pad_thinker, torch.Tensor):
|
||||
pad_in = tts_pad_thinker
|
||||
@@ -775,7 +767,7 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
if pad_in.ndim == 1:
|
||||
pad_in = pad_in.view(1, 1, -1)
|
||||
pad_proj = self.talker.text_projection(pad_in.to(self._module_device(self.talker)))
|
||||
update_dict["tts_pad_embed_projected"] = pad_proj.detach()
|
||||
update_dict["tts_pad_embed_projected"] = pad_proj.detach().to("cpu").contiguous()
|
||||
except Exception:
|
||||
pass
|
||||
self._talker_cache_thinker_decode_embeds(info_dict, update_dict)
|
||||
@@ -934,7 +926,7 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
if isinstance(q_tail, torch.Tensor) and q_tail.numel() > 0:
|
||||
use_vec = q_tail[0:1, :]
|
||||
new_q_tail = (
|
||||
q_tail[1:, :].detach()
|
||||
q_tail[1:, :].detach().to("cpu").contiguous()
|
||||
if q_tail.shape[0] > 1
|
||||
else self.tts_pad_embed.to(input_embeds.device, dtype=input_embeds.dtype)
|
||||
)
|
||||
@@ -1130,10 +1122,16 @@ class Qwen3OmniMoeForConditionalGeneration(
|
||||
# implemented by assigning their logits to log(1e-9).
|
||||
|
||||
if getattr(self, "model_stage", None) == "talker" and isinstance(logits, torch.Tensor):
|
||||
# Move mask to device once (lazy), then reuse every step
|
||||
if self.suppressed_tokens.device != logits.device:
|
||||
self.suppressed_tokens = self.suppressed_tokens.to(logits.device)
|
||||
logits.masked_fill_(self.suppressed_tokens.unsqueeze(0), -1e9)
|
||||
try:
|
||||
logits_cpu = logits.cpu()
|
||||
logits_cpu[:, self.suppressed_tokens] = -1e9
|
||||
logits = logits_cpu.to(logits.device)
|
||||
except Exception as e:
|
||||
print(f"Error in logits suppression: {e}")
|
||||
print(f"logits.shape: {logits.shape}")
|
||||
print(f"self.suppressed_tokens: {self.suppressed_tokens}")
|
||||
raise e
|
||||
logits[:, self.suppressed_tokens] = -1e9
|
||||
return logits
|
||||
|
||||
def sample(
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
"""Qwen3-Omni Code Predictor -- optimized re-prefill, no KV cache.
|
||||
"""Qwen3-Omni Code Predictor with MTP (Multi-Token Prediction) support.
|
||||
|
||||
* SDPA attention (F.scaled_dot_product_attention) -- no HF backend fallback
|
||||
* Persistent pre-allocated buffers (_proj_buf, _pos_ids) -- zero per-call alloc
|
||||
* Inline top-k sampling -- no LogitsProcessorList / custom-op overhead
|
||||
* torch.compile on inner transformer by default
|
||||
* No @support_torch_compile / static_forward_context / namedtuple
|
||||
This module implements the code predictor component for Qwen3-Omni talker models.
|
||||
|
||||
The code predictor generates residual RVQ (Residual Vector Quantization) codes
|
||||
autoregressively, predicting layers 1 to N based on layer-0 codes from the talker.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from vllm.config import VllmConfig
|
||||
from transformers import Cache, PretrainedConfig
|
||||
from transformers.generation.logits_process import (
|
||||
LogitsProcessorList,
|
||||
TopKLogitsWarper,
|
||||
TopPLogitsWarper,
|
||||
)
|
||||
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
|
||||
from vllm.compilation.decorators import support_torch_compile
|
||||
from vllm.config import CacheConfig, ModelConfig, VllmConfig, get_current_vllm_config
|
||||
from vllm.forward_context import get_forward_context
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.layernorm import RMSNorm
|
||||
from vllm.model_executor.layers.linear import (
|
||||
@@ -21,53 +32,47 @@ from vllm.model_executor.layers.linear import (
|
||||
from vllm.model_executor.layers.quantization import QuantizationConfig
|
||||
from vllm.model_executor.layers.rotary_embedding import get_rope
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
|
||||
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
from vllm.utils.torch_utils import direct_register_custom_op
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Standalone Attention (SDPA, no KV cache, no HF backend fallback)
|
||||
# ===================================================================
|
||||
# ============================================================================
|
||||
# Code Predictor Attention Layer
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class Qwen3OmniCodePredictorAttention(nn.Module):
|
||||
"""Multi-head self-attention for code predictor.
|
||||
|
||||
Uses ``F.scaled_dot_product_attention`` directly. No KV cache -- the code
|
||||
predictor always re-prefills the full (short) sequence each AR step.
|
||||
|
||||
Input : [B, seq_len, hidden_size]
|
||||
Output: [B, seq_len, hidden_size]
|
||||
"""
|
||||
"""Multi-head self-attention for code predictor with vLLM optimization."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config,
|
||||
layer_idx: int,
|
||||
vllm_config: VllmConfig = None,
|
||||
quant_config: QuantizationConfig | None = None,
|
||||
prefix: str = "",
|
||||
):
|
||||
super().__init__()
|
||||
cp_cfg = config.code_predictor_config
|
||||
self.num_heads = cp_cfg.num_attention_heads
|
||||
self.num_kv_heads = cp_cfg.num_key_value_heads
|
||||
|
||||
self.num_heads = config.code_predictor_config.num_attention_heads
|
||||
self.num_key_value_heads = config.code_predictor_config.num_key_value_heads
|
||||
self.head_dim = getattr(
|
||||
cp_cfg,
|
||||
config.code_predictor_config,
|
||||
"head_dim",
|
||||
cp_cfg.hidden_size // cp_cfg.num_attention_heads,
|
||||
config.code_predictor_config.hidden_size // config.code_predictor_config.num_attention_heads,
|
||||
)
|
||||
self.hidden_size = cp_cfg.hidden_size
|
||||
self.q_size = self.num_heads * self.head_dim
|
||||
self.kv_size = self.num_kv_heads * self.head_dim
|
||||
self.scaling = self.head_dim**-0.5
|
||||
self._use_gqa = self.num_kv_heads != self.num_heads
|
||||
self.hidden_size = config.code_predictor_config.hidden_size
|
||||
|
||||
if self.num_heads % self.num_key_value_heads != 0:
|
||||
raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
|
||||
|
||||
self.num_key_value_groups = self.num_heads // self.num_key_value_heads
|
||||
|
||||
self.qkv_proj = QKVParallelLinear(
|
||||
hidden_size=self.hidden_size,
|
||||
head_size=self.head_dim,
|
||||
total_num_heads=self.num_heads,
|
||||
total_num_kv_heads=self.num_kv_heads,
|
||||
total_num_kv_heads=self.num_key_value_heads,
|
||||
bias=False,
|
||||
quant_config=quant_config,
|
||||
prefix=f"{prefix}.qkv_proj",
|
||||
@@ -83,55 +88,120 @@ class Qwen3OmniCodePredictorAttention(nn.Module):
|
||||
)
|
||||
self.rotary_emb = get_rope(
|
||||
self.head_dim,
|
||||
max_position=cp_cfg.max_position_embeddings,
|
||||
max_position=config.code_predictor_config.max_position_embeddings,
|
||||
rope_parameters=None,
|
||||
dual_chunk_attention_config=None,
|
||||
)
|
||||
self.q_norm = RMSNorm(self.head_dim, eps=cp_cfg.rms_norm_eps)
|
||||
self.k_norm = RMSNorm(self.head_dim, eps=cp_cfg.rms_norm_eps)
|
||||
|
||||
self.q_size = self.num_heads * self.head_dim
|
||||
self.kv_size = self.num_key_value_heads * self.head_dim
|
||||
|
||||
# Query/Key normalization
|
||||
self.q_norm = RMSNorm(self.head_dim, eps=config.code_predictor_config.rms_norm_eps)
|
||||
self.k_norm = RMSNorm(self.head_dim, eps=config.code_predictor_config.rms_norm_eps)
|
||||
self.is_causal = True
|
||||
self.config = config
|
||||
|
||||
self.attention_backends = ["flash_attention_2", "xformers", "eager", "sdpa"]
|
||||
cudagraph_mode = get_current_vllm_config().compilation_config.cudagraph_mode
|
||||
if "flash_attention_2" in ALL_ATTENTION_FUNCTIONS and cudagraph_mode.has_full_cudagraphs():
|
||||
logger.warning(
|
||||
f"CUDAGraphMode.{cudagraph_mode.name} is currently not supported "
|
||||
f"with flash attention for Qwen3-Omni talker MTP."
|
||||
f"removing flash attention from attention_backends"
|
||||
)
|
||||
self.attention_backends.remove("flash_attention_2")
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
position_ids: torch.Tensor,
|
||||
past_key_values: Cache | None = None,
|
||||
cache_position: torch.LongTensor | None = None,
|
||||
use_cache: bool = False,
|
||||
position_ids: torch.LongTensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
bsz, seq_len, _ = hidden_states.shape
|
||||
|
||||
# Flatten to 2-D so vLLM rotary_emb gets [num_tokens, size]
|
||||
qkv, _ = self.qkv_proj(hidden_states.reshape(bsz * seq_len, -1))
|
||||
qkv, _ = self.qkv_proj(hidden_states)
|
||||
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
||||
|
||||
# QK-norm -> RoPE (both 2-D)
|
||||
q = self.q_norm(q.view(-1, self.num_heads, self.head_dim)).view(q.shape)
|
||||
k = self.k_norm(k.view(-1, self.num_kv_heads, self.head_dim)).view(k.shape)
|
||||
# Reshape for attention
|
||||
q = q.reshape(bsz, seq_len, self.num_heads, self.head_dim)
|
||||
k = k.reshape(bsz, seq_len, self.num_key_value_heads, self.head_dim)
|
||||
v = v.reshape(bsz, seq_len, self.num_key_value_heads, self.head_dim)
|
||||
|
||||
# Apply normalization
|
||||
q = self.q_norm(q).contiguous()
|
||||
k = self.k_norm(k).contiguous()
|
||||
q = q.reshape(-1, self.q_size)
|
||||
k = k.reshape(-1, self.kv_size)
|
||||
|
||||
# Apply RoPE
|
||||
q, k = self.rotary_emb(position_ids, q, k)
|
||||
|
||||
# [B, heads, seq, head_dim] for SDPA
|
||||
q = q.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
||||
k = k.view(bsz, seq_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
|
||||
v = v.view(bsz, seq_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
|
||||
# Reshape for attention
|
||||
q = q.reshape(bsz, seq_len, self.num_heads, self.head_dim)
|
||||
k = k.reshape(bsz, seq_len, self.num_key_value_heads, self.head_dim)
|
||||
|
||||
attn_out = F.scaled_dot_product_attention(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
scale=self.scaling,
|
||||
is_causal=True,
|
||||
enable_gqa=self._use_gqa,
|
||||
)
|
||||
v_heads = v.transpose(1, 2).contiguous()
|
||||
q_heads = q.transpose(1, 2).contiguous()
|
||||
k_heads = k.transpose(1, 2).contiguous()
|
||||
|
||||
attn_out = attn_out.transpose(1, 2).reshape(bsz * seq_len, -1)
|
||||
output, _ = self.o_proj(attn_out)
|
||||
return output.view(bsz, seq_len, -1)
|
||||
if past_key_values is not None:
|
||||
sin, cos = self.rotary_emb.get_cos_sin(seq_len)
|
||||
# sin and cos are specific to RoPE models; cache_position needed for the static cache
|
||||
cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
|
||||
k_heads, v_heads = past_key_values.update(k_heads, v_heads, self.layer_idx, cache_kwargs)
|
||||
|
||||
# Try attention backends in order of preference, with runtime error handling
|
||||
# This handles cases where the backend is registered but not actually available
|
||||
attn_output = None
|
||||
last_error = None
|
||||
|
||||
for backend_name in self.attention_backends:
|
||||
if backend_name not in ALL_ATTENTION_FUNCTIONS:
|
||||
continue
|
||||
|
||||
try:
|
||||
attention_interface = ALL_ATTENTION_FUNCTIONS[backend_name]
|
||||
attn_output, _ = attention_interface(
|
||||
self,
|
||||
q_heads,
|
||||
k_heads,
|
||||
v_heads,
|
||||
None,
|
||||
dropout=0.0 if not self.training else getattr(self, "attention_dropout", 0.0),
|
||||
scaling=self.head_dim**-0.5,
|
||||
sliding_window=None,
|
||||
use_cache=use_cache,
|
||||
position_ids=position_ids[:seq_len].unsqueeze(0),
|
||||
output_hidden_states=True,
|
||||
output_attentions=False,
|
||||
)
|
||||
break
|
||||
except (ValueError, ImportError, RuntimeError, AttributeError) as e:
|
||||
# Store error and try next backend
|
||||
last_error = e
|
||||
continue
|
||||
|
||||
if attn_output is None:
|
||||
raise RuntimeError(
|
||||
f"All attention backends failed. Last error: {last_error}. "
|
||||
"Please install flash-attn, or ensure PyTorch's scaled_dot_product_attention is available."
|
||||
)
|
||||
attn_output = attn_output.reshape(*(hidden_states.shape[:-1]), -1).contiguous()
|
||||
|
||||
attn_output, _ = self.o_proj(attn_output)
|
||||
return attn_output
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# MLP
|
||||
# ===================================================================
|
||||
# ============================================================================
|
||||
# Code Predictor MLP Layer
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class Qwen3OmniCodePredictorMLP(nn.Module):
|
||||
"""SiLU-gated MLP for code predictor."""
|
||||
"""Feed-forward network for code predictor with fused gate/up projection."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -151,6 +221,7 @@ class Qwen3OmniCodePredictorMLP(nn.Module):
|
||||
prefix=f"{prefix}.gate_up_proj",
|
||||
disable_tp=True,
|
||||
)
|
||||
|
||||
self.down_proj = RowParallelLinear(
|
||||
input_size=intermediate_size,
|
||||
output_size=hidden_size,
|
||||
@@ -167,23 +238,35 @@ class Qwen3OmniCodePredictorMLP(nn.Module):
|
||||
return down
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Decoder Layer
|
||||
# ===================================================================
|
||||
# ============================================================================
|
||||
# MTP Layer (Multi-Token Prediction Layer)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class Qwen3OmniCodePredictorDecoderLayer(nn.Module):
|
||||
"""Transformer decoder layer (SDPA, no KV cache)."""
|
||||
class Qwen3OmniCodePredictorMTPLayer(nn.Module):
|
||||
"""MTP layer for speculative decoding - predicts next residual code layer."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config,
|
||||
config: PretrainedConfig,
|
||||
prefix: str,
|
||||
model_config: ModelConfig,
|
||||
layer_idx: int,
|
||||
cache_config: CacheConfig | None = None,
|
||||
quant_config: QuantizationConfig | None = None,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.layer_idx = layer_idx
|
||||
self.config = config
|
||||
|
||||
self.self_attn = Qwen3OmniCodePredictorAttention(
|
||||
config,
|
||||
layer_idx,
|
||||
vllm_config=type(
|
||||
"VllmConfig",
|
||||
(),
|
||||
{"cache_config": cache_config, "quant_config": quant_config, "model_config": model_config},
|
||||
)(),
|
||||
quant_config=quant_config,
|
||||
prefix=f"{prefix}.self_attn",
|
||||
)
|
||||
@@ -192,247 +275,260 @@ class Qwen3OmniCodePredictorDecoderLayer(nn.Module):
|
||||
quant_config=quant_config,
|
||||
prefix=f"{prefix}.mlp",
|
||||
)
|
||||
cp_cfg = config.code_predictor_config
|
||||
self.input_layernorm = RMSNorm(cp_cfg.hidden_size, eps=cp_cfg.rms_norm_eps)
|
||||
self.post_attention_layernorm = RMSNorm(cp_cfg.hidden_size, eps=cp_cfg.rms_norm_eps)
|
||||
self.input_layernorm = RMSNorm(
|
||||
config.code_predictor_config.hidden_size, eps=config.code_predictor_config.rms_norm_eps
|
||||
)
|
||||
self.post_attention_layernorm = RMSNorm(
|
||||
config.code_predictor_config.hidden_size, eps=config.code_predictor_config.rms_norm_eps
|
||||
)
|
||||
|
||||
def forward(
|
||||
def mtp_block(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
position_ids: torch.Tensor,
|
||||
past_key_values: Cache | None = None,
|
||||
cache_position: torch.LongTensor | None = None,
|
||||
use_cache: bool = False,
|
||||
position_ids: torch.LongTensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
# Self-attention with residual
|
||||
residual = hidden_states
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
hidden_states = self.self_attn(hidden_states, position_ids)
|
||||
hidden_states = self.self_attn(hidden_states, past_key_values, cache_position, use_cache, position_ids)
|
||||
hidden_states = residual + hidden_states
|
||||
|
||||
# MLP with residual
|
||||
residual = hidden_states
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
hidden_states = self.mlp(hidden_states)
|
||||
hidden_states = residual + hidden_states
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Base Transformer Model (re-prefill, no KV cache)
|
||||
# ===================================================================
|
||||
|
||||
|
||||
class Qwen3OmniCodePredictorBaseModel(nn.Module):
|
||||
"""Inner transformer for code predictor.
|
||||
"""
|
||||
Base model for code predictor - matches HF Qwen3OmniMoeTalkerCodePredictorModel structure.
|
||||
|
||||
Signature: ``forward(inputs_embeds, position_ids) -> hidden_states``
|
||||
-- plain Tensor in, plain Tensor out (no namedtuple).
|
||||
This is a simple transformer that processes inputs_embeds and outputs hidden states.
|
||||
"""
|
||||
|
||||
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
|
||||
super().__init__()
|
||||
config = vllm_config.model_config.hf_config.code_predictor_config
|
||||
self.config = config
|
||||
|
||||
self.config = config
|
||||
self.vocab_size = config.vocab_size
|
||||
self.num_code_groups = config.num_code_groups
|
||||
|
||||
# Codec embeddings (for layers 1-num_code_groups-1)
|
||||
self.codec_embedding = nn.ModuleList(
|
||||
[VocabParallelEmbedding(config.vocab_size, config.hidden_size) for _ in range(config.num_code_groups - 1)]
|
||||
[
|
||||
VocabParallelEmbedding(
|
||||
config.vocab_size,
|
||||
config.hidden_size,
|
||||
)
|
||||
for _ in range(config.num_code_groups - 1)
|
||||
]
|
||||
)
|
||||
|
||||
# Decoder layers
|
||||
self.layers = nn.ModuleList(
|
||||
[
|
||||
Qwen3OmniCodePredictorDecoderLayer(
|
||||
Qwen3OmniCodePredictorMTPLayer(
|
||||
vllm_config.model_config.hf_config,
|
||||
f"{prefix}.layers.{idx}",
|
||||
model_config=vllm_config.model_config,
|
||||
layer_idx=idx,
|
||||
cache_config=vllm_config.cache_config,
|
||||
quant_config=vllm_config.quant_config,
|
||||
prefix=f"{prefix}.layers.{idx}",
|
||||
)
|
||||
for idx in range(config.num_hidden_layers)
|
||||
]
|
||||
)
|
||||
|
||||
self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
inputs_embeds: torch.Tensor,
|
||||
position_ids: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
attention_mask: torch.Tensor | None = None,
|
||||
position_ids: torch.LongTensor | None = None,
|
||||
past_key_values: Any | None = None,
|
||||
use_cache: bool | None = False,
|
||||
cache_position: torch.LongTensor | None = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""
|
||||
Forward pass matching HF structure.
|
||||
|
||||
Args:
|
||||
inputs_embeds: [batch, seq_len, hidden_size]
|
||||
position_ids: Optional position IDs tensor
|
||||
past_key_values: Optional cached key-value pairs
|
||||
use_cache: Whether to use cache
|
||||
cache_position: Optional cache position tensor
|
||||
**kwargs: Additional keyword arguments
|
||||
|
||||
Returns:
|
||||
Named tuple with .last_hidden_state and .past_key_values attributes
|
||||
"""
|
||||
batch_size, seq_len, _ = inputs_embeds.shape
|
||||
# Forward through decoder layers
|
||||
hidden_states = inputs_embeds
|
||||
|
||||
for layer in self.layers:
|
||||
hidden_states = layer(hidden_states, position_ids)
|
||||
hidden_states = layer.mtp_block(hidden_states, past_key_values, cache_position, use_cache, position_ids)
|
||||
|
||||
# Final norm
|
||||
hidden_states = self.norm(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
# Return in HF-compatible format
|
||||
Output = namedtuple("Output", ["last_hidden_state", "past_key_values"])
|
||||
return Output(last_hidden_state=hidden_states, past_key_values=None) # [batch, num_code_groups-1, hidden_size]
|
||||
|
||||
def get_input_embeddings(self):
|
||||
"""Return codec embeddings for HF compatibility."""
|
||||
return self.codec_embedding
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Code Predictor Wrapper (optimized re-prefill, persistent buffers)
|
||||
# ===================================================================
|
||||
def code_predictor_sample(
|
||||
logits: torch.Tensor,
|
||||
layer_name: str,
|
||||
) -> torch.Tensor:
|
||||
forward_context = get_forward_context()
|
||||
self = forward_context.no_compile_layers[layer_name]
|
||||
logits = self.logits_processors(None, logits[:, -1])
|
||||
probs = F.softmax(logits, dim=-1)
|
||||
code = torch.multinomial(probs.squeeze(1), num_samples=1) # [batch, 1]
|
||||
return code
|
||||
|
||||
|
||||
def code_predictor_sample_fake(
|
||||
logits: torch.Tensor,
|
||||
layer_name: str,
|
||||
) -> torch.Tensor:
|
||||
return torch.empty((logits.shape[0], 1), dtype=torch.int64, device=logits.device)
|
||||
|
||||
|
||||
direct_register_custom_op(
|
||||
op_name="qwen3_omni_code_predictor_sample",
|
||||
op_func=code_predictor_sample,
|
||||
fake_impl=code_predictor_sample_fake,
|
||||
)
|
||||
|
||||
|
||||
@support_torch_compile
|
||||
class Qwen3OmniMoeTalkerCodePredictor(nn.Module):
|
||||
"""Optimized code predictor -- re-prefill approach, no KV cache.
|
||||
"""
|
||||
Code predictor wrapper matching HF structure.
|
||||
|
||||
Each AR step forwards the full growing sequence (len 2 -> num_code_groups+1)
|
||||
through the transformer. The extra O(T^2) FLOPs are negligible for
|
||||
short sequences, and this avoids all KV-cache management overhead.
|
||||
|
||||
Optimizations:
|
||||
1. Pre-allocated embedding buffer -- no torch.cat per step.
|
||||
2. Pre-allocated position_ids -- no torch.arange per step.
|
||||
3. Inline top-k sampling -- no LogitsProcessorList / custom op.
|
||||
4. Cached module references -- bypass ModuleList indexing.
|
||||
5. torch.compile on inner transformer.
|
||||
Structure:
|
||||
- self.model: Qwen3OmniCodePredictorBaseModel (transformer)
|
||||
- self.lm_head: ModuleList of output heads
|
||||
"""
|
||||
|
||||
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
|
||||
super().__init__()
|
||||
|
||||
config = vllm_config.model_config.hf_config
|
||||
self.config = config
|
||||
talker_code_predictor_config = vllm_config.model_config.hf_config
|
||||
self.quant_config = vllm_config.quant_config
|
||||
self.prefix = prefix
|
||||
|
||||
self.num_code_groups = config.code_predictor_config.num_code_groups
|
||||
self._hidden_size = config.code_predictor_config.hidden_size
|
||||
self.config = talker_code_predictor_config
|
||||
self.vocab_size = self.config.code_predictor_config.vocab_size
|
||||
self.num_code_groups = self.config.code_predictor_config.num_code_groups
|
||||
|
||||
self.model = Qwen3OmniCodePredictorBaseModel(
|
||||
vllm_config=vllm_config,
|
||||
prefix=prefix,
|
||||
)
|
||||
# Base transformer model (matches HF structure)
|
||||
self.model = Qwen3OmniCodePredictorBaseModel(vllm_config=vllm_config, prefix=prefix)
|
||||
|
||||
# One lm_head per residual layer (layers 1 .. G-1)
|
||||
# Output heads for each residual layer (1-num_layers-1)
|
||||
self.lm_head = nn.ModuleList(
|
||||
[
|
||||
nn.Linear(
|
||||
config.code_predictor_config.hidden_size,
|
||||
config.code_predictor_config.vocab_size,
|
||||
self.config.code_predictor_config.hidden_size,
|
||||
self.config.code_predictor_config.vocab_size,
|
||||
bias=False,
|
||||
)
|
||||
for _ in range(self.num_code_groups - 1)
|
||||
]
|
||||
)
|
||||
|
||||
# Sampling hyperparams (inlined)
|
||||
self._top_k = 50
|
||||
|
||||
# Persistent buffers (lazily initialised on first forward)
|
||||
self._proj_buf: torch.Tensor | None = None
|
||||
self._pos_ids: torch.Tensor | None = None
|
||||
|
||||
# Cached plain-list refs (set once)
|
||||
self._lm_heads: list | None = None
|
||||
self._codec_embeds: list | None = None
|
||||
|
||||
# Model forward (optionally compiled)
|
||||
self._model_fwd: object | None = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lazy-init helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _ensure_buffers(self, bsz: int, device: torch.device, dtype: torch.dtype) -> None:
|
||||
max_seq = self.num_code_groups + 1
|
||||
if (
|
||||
self._proj_buf is not None
|
||||
and self._proj_buf.shape[0] >= bsz
|
||||
and self._proj_buf.device == device
|
||||
and self._proj_buf.dtype == dtype
|
||||
):
|
||||
return
|
||||
self._proj_buf = torch.zeros(bsz, max_seq, self._hidden_size, dtype=dtype, device=device)
|
||||
self._pos_ids = torch.arange(max_seq, dtype=torch.long, device=device)
|
||||
|
||||
def _ensure_cached_refs(self) -> None:
|
||||
if self._lm_heads is not None:
|
||||
return
|
||||
self._lm_heads = list(self.lm_head)
|
||||
self._codec_embeds = list(self.model.codec_embedding)
|
||||
|
||||
def _ensure_model_fwd(self) -> None:
|
||||
if self._model_fwd is not None:
|
||||
return
|
||||
if not current_omni_platform.supports_torch_inductor():
|
||||
logger.warning_once("code_predictor: torch.compile disabled")
|
||||
self._model_fwd = self.model.forward
|
||||
return
|
||||
self._model_fwd = torch.compile(
|
||||
self.model.forward,
|
||||
mode="default",
|
||||
dynamic=True,
|
||||
self.logits_processors = LogitsProcessorList(
|
||||
[
|
||||
TopKLogitsWarper(top_k=50),
|
||||
TopPLogitsWarper(top_p=0.8),
|
||||
]
|
||||
)
|
||||
logger.info("code_predictor: torch.compile enabled")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Forward -- re-prefill + persistent buffers + inline sampling
|
||||
# ------------------------------------------------------------------
|
||||
compilation_config = get_current_vllm_config().compilation_config
|
||||
if prefix in compilation_config.static_forward_context:
|
||||
raise ValueError(f"Duplicate layer name: {prefix}")
|
||||
compilation_config.static_forward_context[prefix] = self
|
||||
self.layer_name = prefix
|
||||
|
||||
@torch.inference_mode()
|
||||
def forward(
|
||||
self,
|
||||
layer0_code: torch.Tensor,
|
||||
layer0_embed: torch.Tensor,
|
||||
last_talker_hidden: torch.Tensor,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Predict residual codebooks 1..G-1 autoregressively via re-prefill.
|
||||
"""
|
||||
Forward pass for code predictor.
|
||||
|
||||
Args:
|
||||
layer0_code: [bsz, 1] int64
|
||||
layer0_embed: [bsz, 1, hidden_size]
|
||||
last_talker_hidden: [bsz, 1, hidden_size]
|
||||
layer0_code:
|
||||
Code index for code-group (layer) 0.
|
||||
Shape: [batch_size, 1], dtype typically int64.
|
||||
|
||||
last_talker_hidden:
|
||||
|
||||
Shape: [batch_size, hidden_size].
|
||||
|
||||
Returns:
|
||||
all_codes: [bsz, num_code_groups, 1]
|
||||
proj_buf: [bsz, num_code_groups + 1, hidden_size]
|
||||
pos 0 = last_talker_hidden (NOT a codec embed)
|
||||
pos 1 = layer0_embed
|
||||
pos 2.. = codec_embedding[i](predicted_code_i)
|
||||
pos_all_layers:
|
||||
Predicted codes for all code groups, including `layer0_code`.
|
||||
Shape: [batch_size, num_code_groups, 1].
|
||||
|
||||
current_input:
|
||||
The final input embedding sequence after appending embeddings of all
|
||||
predicted codes (one token per predicted layer).
|
||||
Shape: [batch_size, num_code_groups + 2, hidden_size].
|
||||
"""
|
||||
bsz = int(layer0_code.shape[0])
|
||||
device = layer0_code.device
|
||||
dtype = last_talker_hidden.dtype
|
||||
num_groups = self.num_code_groups
|
||||
top_k = self._top_k
|
||||
pos_codes = [layer0_code] # Start with layer 0: [batch, 1]
|
||||
try:
|
||||
current_input = torch.cat([last_talker_hidden, layer0_embed], dim=1) # [batch, 2, hidden_size]
|
||||
except Exception as e:
|
||||
print(f"Error in current_input: {e}")
|
||||
print(f"last_talker_hidden shape: {last_talker_hidden.shape}")
|
||||
print(f"prev_embed shape: {layer0_embed.shape}")
|
||||
raise e
|
||||
batch_size = current_input.shape[0]
|
||||
|
||||
# Lazy init
|
||||
self._ensure_buffers(bsz, device, dtype)
|
||||
self._ensure_model_fwd()
|
||||
self._ensure_cached_refs()
|
||||
# Predict all residual layers (layers 1 to num_code_groups-1) autoregressively
|
||||
for layer_idx in range(self.num_code_groups - 1):
|
||||
seq_len = layer_idx + 2
|
||||
# Compute position_ids dynamically to avoid torch.compile specializing batch_size
|
||||
position_ids = torch.arange(seq_len, device=current_input.device, dtype=torch.int64).repeat(batch_size)
|
||||
# Forward through code_predictor model
|
||||
outputs = self.model(
|
||||
inputs_embeds=current_input,
|
||||
attention_mask=None,
|
||||
position_ids=position_ids,
|
||||
past_key_values=None,
|
||||
use_cache=False,
|
||||
cache_position=None,
|
||||
)
|
||||
hidden_state = outputs.last_hidden_state # [batch, 2, hidden_size]
|
||||
|
||||
proj_buf = self._proj_buf
|
||||
pos_ids = self._pos_ids
|
||||
model_fwd = self._model_fwd
|
||||
lm_heads = self._lm_heads
|
||||
codec_embeds = self._codec_embeds
|
||||
|
||||
# Output codes
|
||||
all_codes = torch.empty(bsz, num_groups, 1, dtype=torch.int64, device=device)
|
||||
all_codes[:, 0] = layer0_code
|
||||
|
||||
# Fill buffer positions 0 & 1
|
||||
proj_buf[:bsz, 0:1, :] = last_talker_hidden
|
||||
proj_buf[:bsz, 1:2, :] = layer0_embed
|
||||
|
||||
# Autoregressive loop: predict layers 1..G-1
|
||||
for step in range(1, num_groups):
|
||||
seq_len = step + 1
|
||||
projected = proj_buf[:bsz, :seq_len, :]
|
||||
step_pos_ids = pos_ids[:seq_len] if bsz == 1 else pos_ids[:seq_len].repeat(bsz)
|
||||
|
||||
hidden_out = model_fwd(projected, step_pos_ids)
|
||||
|
||||
# Inline top-k sampling
|
||||
logits = lm_heads[step - 1](hidden_out[:, -1, :])
|
||||
if top_k > 0:
|
||||
topk_vals, _ = logits.topk(top_k, dim=-1)
|
||||
logits = logits.masked_fill(logits < topk_vals[:, -1:], float("-inf"))
|
||||
probs = F.softmax(logits, dim=-1)
|
||||
code = torch.multinomial(probs, num_samples=1) # [bsz, 1]
|
||||
|
||||
all_codes[:, step] = code
|
||||
|
||||
# Embed predicted code -> next buffer position
|
||||
new_embed = codec_embeds[step - 1](code)
|
||||
proj_buf[:bsz, step + 1 : step + 2, :] = new_embed
|
||||
|
||||
return all_codes, proj_buf[:bsz]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Weight loading
|
||||
# ------------------------------------------------------------------
|
||||
# Use the corresponding lm_head for this layer
|
||||
logits = self.lm_head[layer_idx](hidden_state[:, -1:, :])
|
||||
code = torch.ops.vllm.qwen3_omni_code_predictor_sample(logits, self.layer_name)
|
||||
pos_codes.append(code)
|
||||
# Update prev_embed for next layer (if not last layer)
|
||||
# layer_idx=0 predicts layer 1, embed with codec_embedding[1]
|
||||
new_embed = self.model.codec_embedding[layer_idx](code) # [batch, 1, hidden_size]
|
||||
current_input = torch.cat([current_input, new_embed], dim=1) # [batch, 3~n, hidden_size]
|
||||
pos_all_layers = torch.stack(pos_codes, dim=1) # [batch, num_code_groups, 1]
|
||||
return pos_all_layers, current_input
|
||||
|
||||
def load_weights(self, weights: list[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights with mapping for fused QKV and gate_up projections.
|
||||
|
||||
@@ -128,70 +128,111 @@ class Qwen3OmniMoeTalkerForConditionalGeneration(
|
||||
self.code_predictor = Qwen3OmniMoeTalkerCodePredictor(
|
||||
vllm_config=vllm_config, prefix=maybe_prefix(prefix, "code_predictor")
|
||||
)
|
||||
max_batch_size = max(
|
||||
vllm_config.scheduler_config.max_num_seqs, vllm_config.compilation_config.max_cudagraph_capture_size
|
||||
)
|
||||
self.layer0_embed_buffer = torch.zeros(
|
||||
(max_batch_size, 1, self.config.text_config.hidden_size),
|
||||
dtype=vllm_config.model_config.dtype,
|
||||
)
|
||||
|
||||
def code_predictor_forward(
|
||||
self,
|
||||
input_ids: torch.Tensor,
|
||||
inputs_embeds: torch.Tensor | None = None,
|
||||
*,
|
||||
temperature: float = 1.0,
|
||||
top_k: int = 50, # Match transformers default
|
||||
top_p: float = 0.8, # Match transformers default
|
||||
generation_steps: int | None = None,
|
||||
last_talker_hidden: torch.Tensor | None = None,
|
||||
**_: object,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Generate full RVQ codes + summed embeddings (single-loop, no KV cache).
|
||||
"""
|
||||
Generate full RVQ codec codes for the provided sequence.
|
||||
|
||||
The code predictor uses re-prefill: each AR step re-forwards the full
|
||||
(short) sequence through the transformer. The returned ``proj_buf``
|
||||
already contains all codec embeddings at positions 1..G,
|
||||
so summed_embeddings = proj_buf[:, 1:, :].sum(dim=1) — no second
|
||||
loop or re-embedding needed.
|
||||
The code predictor consumes the layer-0 codec codes produced by the talker
|
||||
alongside the talker's hidden states, and autoregressively predicts the remaining
|
||||
residual layers (to num_codec_groups).
|
||||
|
||||
Returns:
|
||||
result_codes: [batch, num_code_groups, seq_len]
|
||||
summed_embeddings: [batch, seq_len, hidden_size]
|
||||
tuple containing:
|
||||
- residual_codes: A tensor of shape [batch, num_code_groups, seq_len] containing
|
||||
the complete set of codec codes
|
||||
- summed_embeddings: A tensor of shape [batch, seq_len, hidden_size]
|
||||
Sum of all layer embeddings at each position (like Transformers)
|
||||
"""
|
||||
if input_ids is None:
|
||||
raise ValueError("`input_ids` (layer-0 codes) must be provided.")
|
||||
raise ValueError("`input_ids` containing layer-0 codec codes must be provided.")
|
||||
if inputs_embeds is None:
|
||||
raise ValueError("`inputs_embeds` (talker hidden states) must be provided.")
|
||||
raise ValueError("`inputs_embeds` containing talker hidden states must be provided.")
|
||||
|
||||
if inputs_embeds.ndim == 2:
|
||||
inputs_embeds = inputs_embeds.unsqueeze(0)
|
||||
if input_ids.ndim == 1:
|
||||
input_ids = input_ids.unsqueeze(0)
|
||||
|
||||
# Ensure the tensors are contiguous for the autoregressive sampling loop
|
||||
inputs_embeds = inputs_embeds.contiguous()
|
||||
input_ids = input_ids.contiguous()
|
||||
|
||||
# Generate full codec codes using MTP
|
||||
# This will be the parallel prediction implementation
|
||||
batch_size, seq_len = input_ids.shape
|
||||
device = input_ids.device
|
||||
embed_fn = self.language_model.model.codec_embedding
|
||||
hidden_size = self.config.code_predictor_config.hidden_size
|
||||
|
||||
result_codes = torch.empty(
|
||||
batch_size,
|
||||
self.num_code_groups,
|
||||
seq_len,
|
||||
dtype=torch.int64,
|
||||
device=device,
|
||||
)
|
||||
summed_embeddings = torch.empty(
|
||||
batch_size,
|
||||
seq_len,
|
||||
hidden_size,
|
||||
dtype=inputs_embeds.dtype,
|
||||
device=device,
|
||||
)
|
||||
# For now, use sequential generation (TODO: implement parallel)
|
||||
# Result will be [batch, num_code_groups, seq_len]
|
||||
# - all_codes_per_position will collect [batch, num_code_groups, 1] for each position
|
||||
all_codes_per_position = []
|
||||
middle_hidden_states = [] # Collect hidden states for each position
|
||||
|
||||
# Generate residual layers for each position
|
||||
for pos in range(seq_len):
|
||||
layer0_code = input_ids[:, pos : pos + 1]
|
||||
layer0_embed = embed_fn(layer0_code)
|
||||
layer0_code = input_ids[:, pos : pos + 1] # [batch, 1]
|
||||
|
||||
pos_all_layers, proj_buf = self.code_predictor(
|
||||
layer0_code,
|
||||
layer0_embed,
|
||||
last_talker_hidden,
|
||||
# Initial input: [last_talker_hidden, layer0_embed]
|
||||
layer0_embed = self.embed_input_ids(layer0_code)
|
||||
self.layer0_embed_buffer[:batch_size].copy_(layer0_embed)
|
||||
pos_all_layers, current_input = self.code_predictor(
|
||||
layer0_code, self.layer0_embed_buffer[:batch_size], last_talker_hidden
|
||||
)
|
||||
|
||||
result_codes[:, :, pos : pos + 1] = pos_all_layers
|
||||
# proj_buf layout: [0]=talker_hidden, [1..G]=codec embeds
|
||||
summed_embeddings[:, pos, :] = proj_buf[:, 1:, :].sum(dim=1)
|
||||
# Stack all layers for this position: [batch, num_code_groups, 1]
|
||||
all_codes_per_position.append(pos_all_layers)
|
||||
middle_hidden_states.append(current_input[:, 2:-1, :])
|
||||
|
||||
# Concatenate across positions: [batch, num_code_groups, seq_len]
|
||||
result_codes = torch.cat(all_codes_per_position, dim=2)
|
||||
|
||||
# Build summed embeddings for each position (like Transformers)
|
||||
# This combines layer-0 embed, mid layers hidden states, and last layer embed
|
||||
all_summed_embeddings = []
|
||||
|
||||
for pos in range(seq_len):
|
||||
# Layer 0 embedding
|
||||
layer0_code = result_codes[:, 0, pos : pos + 1] # [batch, 1]
|
||||
layer0_embed = self.embed_input_ids(layer0_code) # [batch, 1, hidden_size]
|
||||
|
||||
# mid layers hidden states (from CodePredictor)
|
||||
mid_residual_hiddens = middle_hidden_states[pos] # [batch, num_code_groups-2, hidden_size]
|
||||
mid_list = list(mid_residual_hiddens.split(1, dim=1))
|
||||
|
||||
# last layer embedding
|
||||
last_layer_code = result_codes[:, -1, pos : pos + 1] # [batch, 1]
|
||||
last_residual_hidden = self.code_predictor.model.codec_embedding[-1](last_layer_code)
|
||||
|
||||
# Concatenate all layers: [batch, num_code_groups, hidden_size]
|
||||
pos_codec_hiddens = torch.cat(
|
||||
[layer0_embed] + mid_list + [last_residual_hidden],
|
||||
dim=1,
|
||||
)
|
||||
|
||||
# Sum across layers: [batch, 1, hidden_size] (like Transformers)
|
||||
pos_summed = pos_codec_hiddens.sum(dim=1, keepdim=True)
|
||||
all_summed_embeddings.append(pos_summed)
|
||||
|
||||
# Concatenate across positions: [batch, seq_len, hidden_size]
|
||||
summed_embeddings = torch.cat(all_summed_embeddings, dim=1).squeeze(1)
|
||||
|
||||
return result_codes, summed_embeddings
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# stage config for running bagel-7b-mot with architecture of OmniLLM.
|
||||
|
||||
stage_args:
|
||||
- stage_id: 0
|
||||
stage_type: llm
|
||||
prompt_expand_func: vllm_omni.model_executor.stage_input_processors.bagel.expand_cfg_prompts
|
||||
runtime:
|
||||
devices: "0"
|
||||
# 3 = 1 user prompt + 2 CFG companions (text-unconditional + image-unconditional).
|
||||
max_batch_size: 3
|
||||
engine_args:
|
||||
model_stage: thinker
|
||||
model_arch: OmniBagelForConditionalGeneration
|
||||
worker_type: ar
|
||||
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
|
||||
gpu_memory_utilization: 0.9
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
engine_output_type: text
|
||||
distributed_executor_backend: "mp"
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 16384
|
||||
tensor_parallel_size: 1
|
||||
quantization: fp8
|
||||
omni_kv_config:
|
||||
need_send_cache: true
|
||||
kv_transfer_criteria:
|
||||
type: prefill_finished #or special token generated
|
||||
final_output: true
|
||||
final_output_type: text
|
||||
is_comprehension: true
|
||||
default_sampling_params:
|
||||
temperature: 0.4
|
||||
top_p: 0.9
|
||||
top_k: 1
|
||||
max_tokens: 2048
|
||||
seed: 52
|
||||
detokenize: True
|
||||
repetition_penalty: 1.05
|
||||
|
||||
- stage_id: 1
|
||||
stage_type: diffusion
|
||||
cfg_kv_collect_func: vllm_omni.model_executor.stage_input_processors.bagel.collect_cfg_kv_caches
|
||||
runtime:
|
||||
devices: "1"
|
||||
max_batch_size: 1
|
||||
engine_args:
|
||||
model_stage: dit
|
||||
gpu_memory_utilization: 0.9
|
||||
enforce_eager: true
|
||||
trust_remote_code: true
|
||||
engine_output_type: image
|
||||
distributed_executor_backend: "mp"
|
||||
enable_prefix_caching: false
|
||||
max_num_batched_tokens: 32768
|
||||
tensor_parallel_size: 1
|
||||
omni_kv_config:
|
||||
need_recv_cache: true
|
||||
engine_input_source: [0]
|
||||
|
||||
final_output: true
|
||||
final_output_type: image
|
||||
is_comprehension: false
|
||||
default_sampling_params:
|
||||
seed: 52
|
||||
|
||||
# Runtime edges
|
||||
runtime:
|
||||
enabled: true
|
||||
defaults:
|
||||
window_size: -1
|
||||
max_inflight: 1
|
||||
|
||||
# Distributed connectors configuration (optional)
|
||||
# More connectors will be supported in the future.
|
||||
connectors:
|
||||
shared_memory_connector:
|
||||
name: SharedMemoryConnector
|
||||
extra:
|
||||
shm_threshold_bytes: 65536 # 64KB threshold
|
||||
|
||||
|
||||
edges:
|
||||
- from: 0
|
||||
to: 1
|
||||
window_size: -1
|
||||
@@ -1,8 +1,9 @@
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from vllm.compilation.cuda_graph import CUDAGraphWrapper
|
||||
from vllm.compilation.cuda_graph import CUDAGraphWrapper as _OriginalCUDAGraphWrapper
|
||||
from vllm.config import CUDAGraphMode
|
||||
from vllm.distributed.parallel_state import get_pp_group
|
||||
from vllm.forward_context import set_forward_context
|
||||
@@ -36,6 +37,22 @@ else:
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class CUDAGraphWrapper(_OriginalCUDAGraphWrapper):
|
||||
def __getattr__(self, key: str) -> Any:
|
||||
# allow accessing the attributes of the runnable.
|
||||
if hasattr(self.runnable, key):
|
||||
return getattr(self.runnable, key)
|
||||
raise AttributeError(f"Attribute {key} not exists in the runnable of cudagraph wrapper")
|
||||
|
||||
|
||||
# Patch vLLM's CUDAGraphWrapper with our optimized version
|
||||
for _module_name, _module in sys.modules.items():
|
||||
if "vllm" not in _module_name:
|
||||
continue
|
||||
if hasattr(_module, "CUDAGraphWrapper") and _module.CUDAGraphWrapper is _OriginalCUDAGraphWrapper:
|
||||
_module.CUDAGraphWrapper = CUDAGraphWrapper
|
||||
|
||||
|
||||
class OmniGPUModelRunner(GPUModelRunner):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -1356,17 +1373,10 @@ class OmniGPUModelRunner(GPUModelRunner):
|
||||
req_state = self.requests.get(req_id)
|
||||
if req_state is None:
|
||||
return
|
||||
# Check if the model declares keys that should stay on GPU
|
||||
gpu_keys: set[str] = set()
|
||||
if hasattr(self, "model") and hasattr(self.model, "gpu_resident_buffer_keys"):
|
||||
gpu_keys = self.model.gpu_resident_buffer_keys
|
||||
existing = self.model_intermediate_buffer.setdefault(req_id, {})
|
||||
for k, v in upd.items():
|
||||
if isinstance(v, torch.Tensor):
|
||||
if k in gpu_keys:
|
||||
existing[k] = v.detach().clone()
|
||||
else:
|
||||
existing[k] = v.detach().to("cpu").contiguous()
|
||||
existing[k] = v.detach().to("cpu").contiguous()
|
||||
elif isinstance(v, list):
|
||||
existing[k] = [
|
||||
(item.detach().to("cpu").contiguous() if isinstance(item, torch.Tensor) else item) for item in v
|
||||
|
||||
Reference in new issue
Block a user