[Bugfix][Qwen3-Omni] Include deferred residual in captured Thinker states (#7304)

Signed-off-by: Deep Shah <deep@socratic.co>
This commit is contained in:
Deep Shah authored and GitHub committed 2026-09-16 11:36:52 -07:00
1 parent d4dba0ddb1
commit 4392af5ce3
2 files changed
+53 -3

No files matched your search

@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Regression tests for the Qwen3-Omni thinker forward return contract.
Background
@@ -41,6 +41,7 @@ from vllm_omni.model_executor.models.qwen3_omni.qwen3_omni import (
Qwen3OmniMoeForConditionalGeneration,
)
from vllm_omni.model_executor.models.qwen3_omni.qwen3_omni_moe_thinker import (
Qwen3MoeLLMModel,
Qwen3OmniMoeThinkerForConditionalGeneration,
)
@@ -54,6 +55,52 @@ def _hidden_states() -> torch.Tensor:
return torch.zeros(_TOKENS, _HIDDEN)
def test_single_rank_intermediate_capture_includes_deferred_residual(mocker):
"""Capture the Talker state on the default single-PP-rank path."""
import vllm_omni.model_executor.models.qwen3_omni.qwen3_omni_moe_thinker as module
pp_group = mocker.Mock(is_first_rank=True, is_last_rank=True)
mocker.patch.object(module, "get_pp_group", return_value=pp_group)
class SplitStateLayer(nn.Module):
def forward(self, positions, hidden_states, residual):
next_residual = hidden_states if residual is None else hidden_states + residual
return hidden_states * 2, next_residual
class Combine(nn.Module):
def forward(self, hidden_states, residual):
return hidden_states + residual, None
model = object.__new__(Qwen3MoeLLMModel)
nn.Module.__init__(model)
model.layers = nn.ModuleList([SplitStateLayer(), SplitStateLayer()])
model.start_layer = 0
model.end_layer = 2
model.norm = Combine()
inputs = torch.ones(_TOKENS, _HIDDEN)
output_without_capture, _ = Qwen3MoeLLMModel.forward(
model,
input_ids=None,
positions=torch.arange(_TOKENS),
inputs_embeds=inputs,
)
output_with_capture, captured = Qwen3MoeLLMModel.forward(
model,
input_ids=None,
positions=torch.arange(_TOKENS),
inputs_embeds=inputs,
capture_layer_indices=[0, 1],
return_hidden_states=True,
)
layers = captured["hidden_states"]["layers"]
torch.testing.assert_close(layers[0], inputs)
torch.testing.assert_close(layers[1], torch.full_like(inputs, 3))
torch.testing.assert_close(output_with_capture, output_without_capture)
torch.testing.assert_close(output_with_capture, torch.full_like(inputs, 7))
class _InnerModelStub:
"""Stands in for ``Qwen3MoeLLMModel``: always returns the internal 2-tuple."""
@@ -169,7 +216,7 @@ def test_make_omni_output_accepts_bare_tensor():
def test_make_omni_output_accepts_capture_tuple():
model = _make_combined(is_staged_run=True)
captured = {"hidden_states": {"layers": {}}}
captured: dict[str, dict[str, dict[int, torch.Tensor]]] = {"hidden_states": {"layers": {}}}
out = model.make_omni_output((_hidden_states(), captured))
assert isinstance(out, OmniOutput)
assert out.multimodal_outputs == captured
@@ -588,7 +588,10 @@ class Qwen3MoeLLMModel(_Qwen3MoeLLMModel):
if layer_idx in capture_set:
hs = captured_hidden_states.setdefault("hidden_states", {})
layers = hs.setdefault("layers", {})
layers[layer_idx] = hidden_states.clone().view(-1, hidden_states.shape[-1])
# vLLM defers the residual addition until the next RMSNorm.
# Reconstruct the logical decoder state before capturing it.
captured = hidden_states.clone() if residual is None else hidden_states + residual
layers[layer_idx] = captured.view(-1, captured.shape[-1])
hidden_states, residual = layer(
positions,