[Hardware][Ascend][Model] Optimize Qwen3-TTS on 310P (#4841)
Signed-off-by: zouyizhou <zouyizhou@huawei.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
1 parent
a7b6a174bc
commit
2d58070f60
9 files changed
+909
-166
No files matched your search
@@ -49,6 +49,11 @@ def _load_module(name: str, filename: str):
|
||||
|
||||
def _build_mock_modules(mocker: MockerFixture) -> dict[str, object]:
|
||||
"""Build the dict of modules to inject into sys.modules."""
|
||||
|
||||
class NativeCustomOp(torch.nn.Module):
|
||||
def forward(self, *args, **kwargs):
|
||||
return self.forward_native(*args, **kwargs)
|
||||
|
||||
platforms_mock = mocker.MagicMock()
|
||||
platforms_mock.current_omni_platform.supports_torch_inductor.return_value = False
|
||||
platforms_mock.current_omni_platform.is_npu.return_value = False
|
||||
@@ -76,6 +81,8 @@ def _build_mock_modules(mocker: MockerFixture) -> dict[str, object]:
|
||||
|
||||
vllm_parallel_mock = mocker.MagicMock()
|
||||
vllm_parallel_mock.VocabParallelEmbedding = torch.nn.Embedding
|
||||
custom_op_mock = types.ModuleType("vllm_omni.diffusion.layers.custom_op")
|
||||
custom_op_mock.CustomOp = NativeCustomOp
|
||||
|
||||
return {
|
||||
"vllm_omni": mocker.MagicMock(),
|
||||
@@ -85,6 +92,7 @@ def _build_mock_modules(mocker: MockerFixture) -> dict[str, object]:
|
||||
"vllm.config.vllm": vllm_config_mod,
|
||||
"vllm.model_executor.model_loader.weight_utils": weight_utils_mock,
|
||||
"vllm.model_executor.layers.vocab_parallel_embedding": vllm_parallel_mock,
|
||||
"vllm_omni.diffusion.layers.custom_op": custom_op_mock,
|
||||
"vllm_omni.model_executor": types.ModuleType("vllm_omni.model_executor"),
|
||||
"vllm_omni.model_executor.models": models_pkg,
|
||||
"vllm_omni.model_executor.models.common": common_pkg,
|
||||
@@ -168,6 +176,38 @@ def _make_vllm_config(mocker: MockerFixture, max_num_seqs: int = 4):
|
||||
return vllm_config
|
||||
|
||||
|
||||
def test_npu_custom_ops_use_fused_norm_and_cached_rope(mocker: MockerFixture, loaded_target_classes) -> None:
|
||||
common_mod = sys.modules["vllm_omni.model_executor.models.common.qwen3_code_predictor"]
|
||||
cp_config, _ = _make_tiny_config(loaded_target_classes)
|
||||
rms_calls = []
|
||||
|
||||
def npu_rms_norm(hidden_states, weight, epsilon):
|
||||
rms_calls.append((hidden_states, weight, epsilon))
|
||||
return hidden_states + 1, None
|
||||
|
||||
mocker.patch.object(common_mod.current_omni_platform, "is_npu", return_value=True)
|
||||
mocker.patch.object(
|
||||
common_mod,
|
||||
"torch_npu",
|
||||
types.SimpleNamespace(npu_rms_norm=npu_rms_norm),
|
||||
create=True,
|
||||
)
|
||||
|
||||
hidden_states = torch.zeros(1, 2, cp_config.hidden_size, dtype=torch.float16)
|
||||
norm = common_mod._RMSNorm(cp_config.hidden_size, eps=cp_config.rms_norm_eps)
|
||||
torch.testing.assert_close(norm.forward_npu(hidden_states), hidden_states + 1)
|
||||
assert len(rms_calls) == 1
|
||||
assert rms_calls[0][2] == cp_config.rms_norm_eps
|
||||
|
||||
rotary = common_mod._RotaryEmbedding(cp_config)
|
||||
assert rotary.cos_cached.shape == (cp_config.num_code_groups + 1, cp_config.head_dim)
|
||||
assert rotary.sin_cached.shape == rotary.cos_cached.shape
|
||||
position_ids = torch.tensor([[0, 2, 4]])
|
||||
cos, sin = rotary.forward_npu(hidden_states, position_ids)
|
||||
torch.testing.assert_close(cos, rotary.cos_cached[position_ids].to(torch.float16))
|
||||
torch.testing.assert_close(sin, rotary.sin_cached[position_ids].to(torch.float16))
|
||||
|
||||
|
||||
class TestCodePredictorDtypeAlignment:
|
||||
"""Test that code predictor buffers match model parameter dtype."""
|
||||
|
||||
|
||||
@@ -67,10 +67,37 @@ def _install_qwen3_tts_patch_fakes(monkeypatch: pytest.MonkeyPatch):
|
||||
class FakeCodePredictorBaseModel(torch.nn.Module):
|
||||
pass
|
||||
|
||||
class FakeMimiEuclideanCodebook(torch.nn.Module):
|
||||
@property
|
||||
def embed(self):
|
||||
return self._embed
|
||||
class FakeProjection(torch.nn.Linear):
|
||||
def __init__(self):
|
||||
super().__init__(4, 4, bias=False)
|
||||
self.call_shapes: list[tuple[int, ...]] = []
|
||||
|
||||
def forward(self, hidden_states):
|
||||
self.call_shapes.append(tuple(hidden_states.shape))
|
||||
return super().forward(hidden_states)
|
||||
|
||||
class FakeCodePredictorWrapper(torch.nn.Module):
|
||||
def __init__(self, *args, **kwargs):
|
||||
del args, kwargs
|
||||
super().__init__()
|
||||
self.model = torch.nn.Module()
|
||||
self.model.codec_embedding = torch.nn.ModuleList([torch.nn.Embedding(8, 4), torch.nn.Embedding(8, 4)])
|
||||
self.model.linear = torch.nn.Linear(4, 4, bias=False)
|
||||
self.lm_head = torch.nn.ModuleList([torch.nn.Linear(4, 8, bias=False), torch.nn.Linear(4, 8, bias=False)])
|
||||
self.small_to_mtp_projection = FakeProjection()
|
||||
with torch.no_grad():
|
||||
self.small_to_mtp_projection.weight.copy_(torch.diag(torch.tensor([1.0, 2.0, 3.0, 4.0])))
|
||||
self._wrapper_config = SimpleNamespace(use_parallel_embedding=False, sampling_mode="per_call")
|
||||
self._projected_codec_embed_weight = None
|
||||
|
||||
def load_weights(self, weights):
|
||||
del weights
|
||||
return {"loaded"}
|
||||
|
||||
class FakeCode2WavBase(torch.nn.Module):
|
||||
def __init__(self, *args, **kwargs):
|
||||
del args, kwargs
|
||||
super().__init__()
|
||||
|
||||
class FakeEncoder:
|
||||
def __init__(self):
|
||||
@@ -136,8 +163,21 @@ def _install_qwen3_tts_patch_fakes(monkeypatch: pytest.MonkeyPatch):
|
||||
CodePredictorAttention=FakeCodePredictorAttention,
|
||||
CodePredictorDecoderLayer=FakeCodePredictorDecoderLayer,
|
||||
CodePredictorBaseModel=FakeCodePredictorBaseModel,
|
||||
CodePredictorWrapper=FakeCodePredictorWrapper,
|
||||
_rotate_half=lambda x: x,
|
||||
)
|
||||
fake_qwen3_tts_code_predictor_vllm = _install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code_predictor_vllm",
|
||||
Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM=FakeCodePredictorWrapper,
|
||||
Qwen3TTSTalkerCodePredictorModelVLLM=FakeCodePredictorBaseModel,
|
||||
CodePredictorWrapper=FakeCodePredictorWrapper,
|
||||
)
|
||||
fake_qwen3_tts_code2wav = _install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code2wav",
|
||||
Qwen3TTSCode2Wav=FakeCode2WavBase,
|
||||
)
|
||||
fake_prompt_builder = _install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.model_executor.models.qwen3_tts.prompt_embeds_builder",
|
||||
@@ -150,22 +190,40 @@ def _install_qwen3_tts_patch_fakes(monkeypatch: pytest.MonkeyPatch):
|
||||
Qwen3TTSTalkerForConditionalGeneration=FakeTalkerBase,
|
||||
Qwen3TTSPromptEmbedsBuilder=FakePromptEmbedsBuilder,
|
||||
)
|
||||
fake_modeling_mimi = _install_fake_module(
|
||||
monkeypatch,
|
||||
"transformers.models.mimi.modeling_mimi",
|
||||
MimiEuclideanCodebook=FakeMimiEuclideanCodebook,
|
||||
)
|
||||
fake_mimi = _install_fake_module(
|
||||
monkeypatch,
|
||||
"transformers.models.mimi",
|
||||
modeling_mimi=fake_modeling_mimi,
|
||||
)
|
||||
|
||||
_install_fake_module(monkeypatch, "vllm")
|
||||
_install_fake_module(monkeypatch, "vllm.multimodal")
|
||||
_install_fake_module(monkeypatch, "vllm.multimodal.audio", AudioResampler=FakeAudioResampler)
|
||||
_install_fake_module(monkeypatch, "transformers")
|
||||
_install_fake_module(monkeypatch, "transformers.models", mimi=fake_mimi)
|
||||
_install_fake_module(monkeypatch, "torch_npu", npu_format_cast=lambda weight, _fmt: weight)
|
||||
_install_fake_module(monkeypatch, "vllm_ascend")
|
||||
_install_fake_module(monkeypatch, "vllm_ascend._310p")
|
||||
_install_fake_module(monkeypatch, "vllm_ascend._310p.attention")
|
||||
_install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_ascend._310p.attention.attention_mask",
|
||||
AttentionMaskBuilder310=SimpleNamespace(
|
||||
gen_causal_additive_mask=lambda max_seq, device: torch.zeros(
|
||||
max_seq,
|
||||
max_seq,
|
||||
device=device,
|
||||
)
|
||||
),
|
||||
)
|
||||
_install_fake_module(monkeypatch, "vllm_ascend.sample")
|
||||
_install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_ascend.sample.sampler",
|
||||
apply_top_k_top_p=lambda logits, **_kwargs: logits,
|
||||
random_sample=lambda probs, _generators: probs.argmax(dim=-1, keepdim=True),
|
||||
)
|
||||
_install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_ascend.utils",
|
||||
ACL_FORMAT_FRACTAL_NZ=29,
|
||||
aligned_16=lambda tensor: tensor,
|
||||
maybe_trans_nz=lambda weight: weight,
|
||||
nd_to_nz_2d=lambda tensor: tensor,
|
||||
)
|
||||
_install_fake_module(monkeypatch, "vllm_omni")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor.models")
|
||||
@@ -178,9 +236,17 @@ def _install_qwen3_tts_patch_fakes(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch,
|
||||
"vllm_omni.model_executor.models.qwen3_tts",
|
||||
prompt_embeds_builder=fake_prompt_builder,
|
||||
qwen3_tts_code2wav=fake_qwen3_tts_code2wav,
|
||||
qwen3_tts_code_predictor_vllm=fake_qwen3_tts_code_predictor_vllm,
|
||||
qwen3_tts_talker=fake_talker,
|
||||
)
|
||||
return fake_qwen3_code_predictor, fake_prompt_builder, fake_talker
|
||||
return (
|
||||
fake_qwen3_code_predictor,
|
||||
fake_qwen3_tts_code_predictor_vllm,
|
||||
fake_qwen3_tts_code2wav,
|
||||
fake_prompt_builder,
|
||||
fake_talker,
|
||||
)
|
||||
|
||||
|
||||
def _load_qwen3_tts_patch(monkeypatch: pytest.MonkeyPatch):
|
||||
@@ -193,7 +259,7 @@ def _load_qwen3_tts_patch(monkeypatch: pytest.MonkeyPatch):
|
||||
def test_registry_applies_worker_once_and_model_patch_lazily(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
registry_path = _repo_root() / "vllm_omni" / "platforms" / "npu" / "_310p" / "patch" / "__init__.py"
|
||||
registry = _load_source_module("vllm_omni_test_310p_patch_registry", registry_path)
|
||||
calls = {"worker": 0, "talker": 0}
|
||||
calls = {"worker": 0, "talker": 0, "code2wav": 0}
|
||||
|
||||
_install_fake_module(
|
||||
monkeypatch,
|
||||
@@ -204,52 +270,35 @@ def test_registry_applies_worker_once_and_model_patch_lazily(monkeypatch: pytest
|
||||
monkeypatch,
|
||||
"vllm_omni.platforms.npu._310p.patch.qwen3_tts",
|
||||
apply_talker_patches=lambda: calls.__setitem__("talker", calls["talker"] + 1),
|
||||
apply_code2wav_patches=lambda: calls.__setitem__("code2wav", calls["code2wav"] + 1),
|
||||
)
|
||||
|
||||
registry.apply_patches()
|
||||
registry.apply_patches()
|
||||
registry.apply_model_patches(SimpleNamespace(model_arch="OtherModel"))
|
||||
registry.apply_model_patches(SimpleNamespace(model_arch="Qwen3TTSTalkerForConditionalGeneration"))
|
||||
registry.apply_model_patches(SimpleNamespace(model_arch="Qwen3TTSCode2Wav"))
|
||||
|
||||
assert calls == {"worker": 1, "talker": 1}
|
||||
assert calls == {"worker": 1, "talker": 1, "code2wav": 1}
|
||||
|
||||
|
||||
def test_worker_patch_replaces_base_and_runs_disable_jit(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
calls: list[str] = []
|
||||
def test_qwen3_tts_patch_registers_target_classes(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
(
|
||||
module,
|
||||
(
|
||||
fake_code_predictor,
|
||||
fake_code_predictor_vllm,
|
||||
fake_code2wav,
|
||||
fake_prompt_builder,
|
||||
fake_talker,
|
||||
),
|
||||
) = _load_qwen3_tts_patch(monkeypatch)
|
||||
|
||||
class FakeOmniNPUWorkerBase:
|
||||
def _init_device(self):
|
||||
calls.append("parent")
|
||||
return "npu:0"
|
||||
|
||||
fake_worker_base = _install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.platforms.npu.worker.base",
|
||||
OmniNPUWorkerBase=FakeOmniNPUWorkerBase,
|
||||
)
|
||||
_install_fake_module(monkeypatch, "vllm_omni")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.platforms")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.platforms.npu")
|
||||
_install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.platforms.npu._310p",
|
||||
disable_jit_compile=lambda: calls.append("disable_jit"),
|
||||
)
|
||||
_install_fake_module(monkeypatch, "vllm_omni.platforms.npu.worker", base=fake_worker_base)
|
||||
|
||||
path = _repo_root() / "vllm_omni" / "platforms" / "npu" / "_310p" / "patch" / "worker.py"
|
||||
module = _load_source_module("vllm_omni_test_310p_worker_patch", path)
|
||||
module.apply_patch()
|
||||
|
||||
assert fake_worker_base.OmniNPUWorkerBase is module._OmniNPUWorkerBase310P
|
||||
assert fake_worker_base.OmniNPUWorkerBase()._init_device() == "npu:0"
|
||||
assert calls == ["parent", "disable_jit"]
|
||||
|
||||
|
||||
def test_qwen3_tts_patch_replaces_target_classes(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
module, (fake_code_predictor, fake_prompt_builder, fake_talker) = _load_qwen3_tts_patch(monkeypatch)
|
||||
original_common_wrapper = fake_code_predictor.CodePredictorWrapper
|
||||
original_vllm_wrapper = fake_code_predictor_vllm.CodePredictorWrapper
|
||||
|
||||
module.apply_talker_patches()
|
||||
module.apply_code2wav_patches()
|
||||
|
||||
assert fake_talker.Qwen3TTSTalkerForConditionalGeneration is module._Qwen3TTSTalker310P
|
||||
assert fake_talker.Qwen3TTSPromptEmbedsBuilder is module._Qwen3TTSPromptEmbedsBuilder310P
|
||||
@@ -257,6 +306,112 @@ def test_qwen3_tts_patch_replaces_target_classes(monkeypatch: pytest.MonkeyPatch
|
||||
assert fake_code_predictor.CodePredictorAttention is module._Qwen3CodePredictorAttention310P
|
||||
assert fake_code_predictor.CodePredictorDecoderLayer is module._Qwen3CodePredictorDecoderLayer310P
|
||||
assert fake_code_predictor.CodePredictorBaseModel is module._Qwen3CodePredictorBaseModel310P
|
||||
assert (
|
||||
fake_code_predictor_vllm.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM
|
||||
is module._Qwen3TTSTalkerCodePredictor310P
|
||||
)
|
||||
assert fake_code_predictor.CodePredictorWrapper is original_common_wrapper
|
||||
assert fake_code_predictor_vllm.CodePredictorWrapper is original_vllm_wrapper
|
||||
assert fake_code2wav.Qwen3TTSCode2Wav is module._Qwen3TTSCode2Wav310P
|
||||
|
||||
code2wav = module._Qwen3TTSCode2Wav310P(
|
||||
vllm_config=SimpleNamespace(device_config=SimpleNamespace(device=torch.device("cpu")))
|
||||
)
|
||||
|
||||
assert code2wav._npu_decoder_runtime_dtype(torch.device("cpu")) is torch.float16
|
||||
|
||||
|
||||
def test_qwen3_tts_code_predictor_forward_uses_projected_embedding_and_sampling(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
module, _ = _load_qwen3_tts_patch(monkeypatch)
|
||||
predictor = module._Qwen3TTSTalkerCodePredictor310P(
|
||||
vllm_config=object(),
|
||||
config=object(),
|
||||
talker_config=object(),
|
||||
)
|
||||
codec_weight = predictor.model.codec_embedding[0].weight.detach().clone()
|
||||
projection_weight = predictor.small_to_mtp_projection.weight.detach().clone()
|
||||
|
||||
assert predictor.load_weights(iter(())) == {"loaded"}
|
||||
torch.testing.assert_close(
|
||||
predictor._projected_codec_embed_weight[0],
|
||||
torch.nn.functional.linear(codec_weight, projection_weight),
|
||||
)
|
||||
assert predictor.small_to_mtp_projection.call_shapes == [(8, 4), (8, 4)]
|
||||
predictor.small_to_mtp_projection.call_shapes.clear()
|
||||
predictor._num_groups = 3
|
||||
predictor._model_dtype = torch.float32
|
||||
predictor._prefix_graphs_enabled = False
|
||||
predictor._bucket_pos_ids = {}
|
||||
predictor._device_graphs = {}
|
||||
predictor._lm_heads_list = [
|
||||
lambda hidden: torch.nn.functional.one_hot(
|
||||
torch.full((hidden.shape[0],), 2, dtype=torch.long),
|
||||
num_classes=8,
|
||||
).to(torch.float32),
|
||||
lambda hidden: torch.nn.functional.one_hot(
|
||||
torch.full((hidden.shape[0],), 3, dtype=torch.long),
|
||||
num_classes=8,
|
||||
).to(torch.float32),
|
||||
]
|
||||
predictor._setup_compile = lambda: None
|
||||
predictor._padded_bsz = lambda bsz: bsz
|
||||
|
||||
def ensure_buffers(device, dtype, padded_bsz):
|
||||
predictor._proj_buf = torch.zeros(padded_bsz, 4, 4, device=device, dtype=dtype)
|
||||
|
||||
predictor._ensure_buffers = ensure_buffers
|
||||
predictor._compiled_model_fwd = lambda embeds, _positions: torch.zeros_like(embeds)
|
||||
|
||||
codes = predictor.forward(
|
||||
torch.tensor([1]),
|
||||
torch.zeros(1, 4),
|
||||
torch.zeros(1, 4),
|
||||
do_sample=False,
|
||||
)
|
||||
|
||||
assert codes.tolist() == [[1, 2, 3]]
|
||||
assert predictor.small_to_mtp_projection.call_shapes == [(1, 2, 4)]
|
||||
torch.testing.assert_close(
|
||||
predictor._proj_buf[0, 2],
|
||||
predictor._projected_codec_embed_weight[0, 2],
|
||||
)
|
||||
|
||||
filter_calls = []
|
||||
sample_calls = []
|
||||
|
||||
def apply_top_k_top_p(logits, *, p, k, top_k):
|
||||
filter_calls.append((p, k, top_k))
|
||||
return logits
|
||||
|
||||
def random_sample(_probs, generators):
|
||||
sample_calls.append(generators)
|
||||
return torch.tensor([[4]])
|
||||
|
||||
monkeypatch.setattr(module, "apply_top_k_top_p", apply_top_k_top_p)
|
||||
monkeypatch.setattr(module, "random_sample", random_sample)
|
||||
predictor._num_groups = 2
|
||||
predictor._wrapper_config.sampling_mode = "stored"
|
||||
predictor._top_k = 2
|
||||
predictor._top_p = 0.8
|
||||
predictor._lm_heads_list = predictor._lm_heads_list[:1]
|
||||
generator = torch.Generator().manual_seed(1234)
|
||||
|
||||
sampled_codes = predictor.forward(
|
||||
torch.tensor([1]),
|
||||
torch.zeros(1, 4),
|
||||
torch.zeros(1, 4),
|
||||
generator=generator,
|
||||
)
|
||||
|
||||
assert sampled_codes.tolist() == [[1, 4]]
|
||||
assert len(filter_calls) == 1
|
||||
top_p_tensor, top_k_tensor, top_k_hint = filter_calls[0]
|
||||
torch.testing.assert_close(top_p_tensor, torch.tensor([0.8]))
|
||||
assert top_k_tensor.tolist() == [2]
|
||||
assert top_k_hint == 2
|
||||
assert sample_calls == [{0: generator}]
|
||||
|
||||
|
||||
def test_qwen3_tts_talker_patch_uses_fp16_runtime_dtype(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -265,12 +420,14 @@ def test_qwen3_tts_talker_patch_uses_fp16_runtime_dtype(monkeypatch: pytest.Monk
|
||||
|
||||
assert talker._embedding_dtype is torch.float16
|
||||
assert talker._prompt_builder._embedding_dtype is torch.float16
|
||||
assert talker.talker_mtp_graph_safe is False
|
||||
assert talker.talker_mtp_accepts_per_row_generators is True
|
||||
assert talker.load_weights([]) == {"loaded"}
|
||||
assert talker.encoder.to_calls[-1] == {"dtype": torch.float16}
|
||||
assert talker.encoder.to_calls[-1] == {"device": torch.device("cpu"), "dtype": torch.float32}
|
||||
|
||||
codes = talker._encode_ref_audio_batch([np.zeros(8, dtype=np.float32)], 24000, device=torch.device("cpu"))
|
||||
|
||||
assert talker.encoder.last_input_dtype is torch.float16
|
||||
assert talker.encoder.last_input_dtype is torch.float32
|
||||
assert len(codes) == 1
|
||||
assert codes[0].dtype is torch.long
|
||||
assert codes[0].shape == (4, 2)
|
||||
@@ -311,28 +468,154 @@ def test_qwen3_tts_prompt_patch_runs_stft_frontend_on_cpu(monkeypatch: pytest.Mo
|
||||
assert speaker.dtype is torch.float16
|
||||
|
||||
|
||||
def test_qwen3_tts_mimi_codebook_quantize_uses_cpu_fp32_cdist(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
module, _ = _load_qwen3_tts_patch(monkeypatch)
|
||||
real_cdist = torch.cdist
|
||||
captured = {}
|
||||
def test_qwen3_tts_tokenizer_npu_patch_dispatches_fused_ops(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
rotary_calls = []
|
||||
rms_calls = []
|
||||
|
||||
def fake_cdist(x1, x2, p=2):
|
||||
captured["x1_device"] = x1.device
|
||||
captured["x2_device"] = x2.device
|
||||
captured["x1_dtype"] = x1.dtype
|
||||
captured["x2_dtype"] = x2.dtype
|
||||
return real_cdist(x1, x2, p=p)
|
||||
def rotary_mul(hidden_states, cos, sin):
|
||||
rotary_calls.append((hidden_states, cos, sin))
|
||||
return hidden_states + 1
|
||||
|
||||
monkeypatch.setattr(torch, "cdist", fake_cdist)
|
||||
codebook = object.__new__(module._MimiEuclideanCodebook310P)
|
||||
codebook._embed = torch.tensor([[0.0, 0.0], [2.0, 0.0]], dtype=torch.float16)
|
||||
def rms_norm(hidden_states, weight, *, epsilon):
|
||||
rms_calls.append((hidden_states, weight, epsilon))
|
||||
return hidden_states * weight, None
|
||||
|
||||
indices = codebook.quantize(torch.tensor([[0.1, 0.0], [1.8, 0.0]], dtype=torch.float16))
|
||||
_install_fake_module(
|
||||
monkeypatch,
|
||||
"torch_npu",
|
||||
npu_rotary_mul=rotary_mul,
|
||||
npu_rms_norm=rms_norm,
|
||||
)
|
||||
_install_fake_module(monkeypatch, "vllm")
|
||||
_install_fake_module(monkeypatch, "vllm.logger", init_logger=lambda _name: SimpleNamespace(debug=lambda *_: None))
|
||||
|
||||
assert captured == {
|
||||
"x1_device": torch.device("cpu"),
|
||||
"x2_device": torch.device("cpu"),
|
||||
"x1_dtype": torch.float32,
|
||||
"x2_dtype": torch.float32,
|
||||
class FakeRMSNorm(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.weight = torch.nn.Parameter(torch.tensor([2.0, 3.0]))
|
||||
self.variance_epsilon = 1e-5
|
||||
|
||||
def forward(self, hidden_states):
|
||||
return hidden_states
|
||||
|
||||
def original_rope(q, k, cos, sin):
|
||||
del cos, sin
|
||||
return q, k
|
||||
|
||||
tokenizer = _install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.model_executor.models.qwen3_tts.tokenizer_12hz.modeling_qwen3_tts_tokenizer_v2",
|
||||
Qwen3TTSTokenizerV2DecoderRMSNorm=FakeRMSNorm,
|
||||
apply_rotary_pos_emb=original_rope,
|
||||
)
|
||||
tokenizer_package = _install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.model_executor.models.qwen3_tts.tokenizer_12hz",
|
||||
modeling_qwen3_tts_tokenizer_v2=tokenizer,
|
||||
)
|
||||
_install_fake_module(monkeypatch, "vllm_omni")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor.models")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor.models.qwen3_tts")
|
||||
monkeypatch.setitem(
|
||||
sys.modules,
|
||||
"vllm_omni.model_executor.models.qwen3_tts.tokenizer_12hz",
|
||||
tokenizer_package,
|
||||
)
|
||||
|
||||
path = _repo_root() / "vllm_omni" / "platforms" / "npu" / "models" / "qwen3_tts_tokenizer_v2.py"
|
||||
module = _load_source_module("vllm_omni_test_qwen3_tts_tokenizer_npu_patch", path)
|
||||
module.apply_qwen3_tts_tokenizer_v2_patch()
|
||||
|
||||
q = torch.zeros(1, 2, 3, 2)
|
||||
k = torch.ones_like(q)
|
||||
cos = torch.zeros(1, 3, 2)
|
||||
sin = torch.ones_like(cos)
|
||||
q_out, k_out = tokenizer.apply_rotary_pos_emb(q, k, cos, sin)
|
||||
norm = FakeRMSNorm()
|
||||
norm_out = norm(torch.ones(1, 2))
|
||||
|
||||
assert len(rotary_calls) == 2
|
||||
assert rotary_calls[0][1].shape == (1, 1, 3, 2)
|
||||
torch.testing.assert_close(q_out, q + 1)
|
||||
torch.testing.assert_close(k_out, k + 1)
|
||||
assert len(rms_calls) == 1
|
||||
assert rms_calls[0][2] == pytest.approx(1e-5)
|
||||
torch.testing.assert_close(norm_out, torch.tensor([[2.0, 3.0]]))
|
||||
|
||||
|
||||
def test_qwen3_tts_code2wav_npu_patch_prepares_loaded_decoder(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
linear_weights = []
|
||||
conv_weights = []
|
||||
|
||||
def maybe_trans_nz(weight):
|
||||
linear_weights.append(weight)
|
||||
return weight
|
||||
|
||||
def format_cast(weight, fmt):
|
||||
conv_weights.append((weight, fmt))
|
||||
return weight
|
||||
|
||||
class FakeDecoder(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.linear = torch.nn.Linear(4, 4)
|
||||
self.conv = torch.nn.Conv1d(4, 4, 3)
|
||||
self.deconv = torch.nn.ConvTranspose1d(4, 4, 4)
|
||||
self.grouped_conv = torch.nn.Conv1d(4, 4, 3, groups=2)
|
||||
self.cache_precompute_calls = 0
|
||||
|
||||
def precompute_snake_caches(self):
|
||||
self.cache_precompute_calls += 1
|
||||
|
||||
class FakeCode2Wav:
|
||||
def __init__(self, *, vllm_config, prefix=""):
|
||||
self.vllm_config = vllm_config
|
||||
self.prefix = prefix
|
||||
self.decoder = FakeDecoder()
|
||||
|
||||
def _npu_decoder_runtime_dtype(self, _device):
|
||||
return torch.float16
|
||||
|
||||
def load_weights(self, weights):
|
||||
assert list(weights) == []
|
||||
return {"loaded"}
|
||||
|
||||
logger = SimpleNamespace(info=lambda *_: None, debug=lambda *_: None)
|
||||
current_platform = SimpleNamespace(is_npu=lambda: False)
|
||||
target = _install_fake_module(
|
||||
monkeypatch,
|
||||
"vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code2wav",
|
||||
Qwen3TTSCode2Wav=FakeCode2Wav,
|
||||
)
|
||||
_install_fake_module(monkeypatch, "torch_npu", npu_format_cast=format_cast)
|
||||
_install_fake_module(monkeypatch, "vllm")
|
||||
_install_fake_module(monkeypatch, "vllm.config", VllmConfig=object)
|
||||
_install_fake_module(monkeypatch, "vllm.logger", init_logger=lambda _name: logger)
|
||||
_install_fake_module(monkeypatch, "vllm_ascend")
|
||||
_install_fake_module(monkeypatch, "vllm_ascend.utils", maybe_trans_nz=maybe_trans_nz)
|
||||
_install_fake_module(monkeypatch, "vllm_omni")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.platforms", current_omni_platform=current_platform)
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor.models")
|
||||
_install_fake_module(monkeypatch, "vllm_omni.model_executor.models.qwen3_tts")
|
||||
|
||||
path = _repo_root() / "vllm_omni" / "platforms" / "npu" / "models" / "qwen3_tts_code2wav.py"
|
||||
module = _load_source_module("vllm_omni_test_qwen3_tts_code2wav_npu_patch", path)
|
||||
module.apply_qwen3_tts_code2wav_patch()
|
||||
|
||||
model = target.Qwen3TTSCode2Wav(
|
||||
vllm_config=SimpleNamespace(device_config=SimpleNamespace(device=torch.device("cpu"))),
|
||||
prefix="stage1",
|
||||
)
|
||||
assert model.load_weights(iter(())) == {"loaded"}
|
||||
|
||||
assert model.prefix == "stage1"
|
||||
assert model.decoder.linear.weight.dtype is torch.float16
|
||||
assert [weight.data_ptr() for weight in linear_weights] == [model.decoder.linear.weight.data_ptr()]
|
||||
assert {weight.data_ptr() for weight, _ in conv_weights} == {
|
||||
model.decoder.conv.weight.data_ptr(),
|
||||
model.decoder.deconv.weight.data_ptr(),
|
||||
}
|
||||
assert indices.tolist() == [0, 1]
|
||||
assert all(fmt == module._ACL_FORMAT_FRACTAL_Z for _, fmt in conv_weights)
|
||||
assert model.decoder.cache_precompute_calls == 1
|
||||
@@ -3,7 +3,7 @@
|
||||
Shared by Qwen3-Omni and Qwen3-TTS talker models.
|
||||
|
||||
* SDPA attention (F.scaled_dot_product_attention) with native GQA support
|
||||
* HF-compatible numerics (float32 RMSNorm, float32 RoPE, separate linear layers)
|
||||
* HF-compatible CPU/CUDA numerics with NPU-only fused norm/RoPE fast paths
|
||||
* Per-call embedding buffer to avoid cross-request aliasing
|
||||
* Pre-allocated position_ids (read-only, safe to persist)
|
||||
* torch.compile (epilogue_fusion=False) on inner transformer by default
|
||||
@@ -24,6 +24,7 @@ from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
|
||||
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
|
||||
|
||||
from vllm_omni.diffusion.layers.custom_op import CustomOp
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
logger = init_logger(__name__)
|
||||
@@ -31,33 +32,48 @@ logger = init_logger(__name__)
|
||||
_GeneratorLike = torch.Generator | Sequence[torch.Generator | None] | None
|
||||
_UNIFORM_EPS = 1e-20
|
||||
|
||||
if current_omni_platform.is_npu():
|
||||
import torch_npu
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# HF-numerics-compatible layers for code predictor
|
||||
# Portable layers for code predictor
|
||||
# ===================================================================
|
||||
#
|
||||
# These use plain PyTorch ops (nn.Linear, manual RMSNorm in float32,
|
||||
# rotate_half RoPE) to produce outputs numerically identical to the
|
||||
# HuggingFace reference. vLLM's fused kernels (RMSNorm, QKVParallel,
|
||||
# get_rope) introduce small precision differences that compound across
|
||||
# the autoregressive steps of the code predictor, causing severe
|
||||
# audio quality degradation.
|
||||
# HuggingFace reference on CPU/CUDA. vLLM's fused kernels (RMSNorm,
|
||||
# QKVParallel, get_rope) introduce small precision differences that compound
|
||||
# across the autoregressive steps of the code predictor, causing severe audio
|
||||
# quality degradation. The Ascend fused norm/RoPE kernels below are dispatched
|
||||
# by the current device platform.
|
||||
#
|
||||
# See: https://github.com/vllm-project/vllm-omni/issues/2274
|
||||
|
||||
|
||||
class _RMSNorm(nn.Module):
|
||||
"""RMSNorm matching HuggingFace's implementation exactly.
|
||||
|
||||
Computes variance in float32 to avoid bfloat16 precision loss.
|
||||
"""
|
||||
class _RMSNorm(CustomOp):
|
||||
"""RMSNorm with HuggingFace-compatible CPU/CUDA math and an NPU fast path."""
|
||||
|
||||
def __init__(self, hidden_size: int, eps: float = 1e-6) -> None:
|
||||
super().__init__()
|
||||
self.weight = nn.Parameter(torch.ones(hidden_size))
|
||||
self.variance_epsilon = eps
|
||||
|
||||
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
def forward_npu(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
hidden_states, _ = torch_npu.npu_rms_norm(
|
||||
hidden_states,
|
||||
self.weight,
|
||||
self.variance_epsilon,
|
||||
)
|
||||
return hidden_states
|
||||
|
||||
def forward_cuda(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
return self.forward_native(hidden_states)
|
||||
|
||||
def forward_xpu(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
return self.forward_native(hidden_states)
|
||||
|
||||
def forward_native(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
input_dtype = hidden_states.dtype
|
||||
hidden_states = hidden_states.to(torch.float32)
|
||||
variance = hidden_states.pow(2).mean(-1, keepdim=True)
|
||||
@@ -72,11 +88,8 @@ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
|
||||
return torch.cat((-x2, x1), dim=-1)
|
||||
|
||||
|
||||
class _RotaryEmbedding(nn.Module):
|
||||
"""RoPE matching HuggingFace's implementation exactly.
|
||||
|
||||
Forces float32 computation for cos/sin, matching HF's torch.autocast(enabled=False).
|
||||
"""
|
||||
class _RotaryEmbedding(CustomOp):
|
||||
"""RoPE with HuggingFace-compatible CPU/CUDA math and cached NPU tables."""
|
||||
|
||||
def __init__(self, config) -> None:
|
||||
super().__init__()
|
||||
@@ -88,8 +101,24 @@ class _RotaryEmbedding(nn.Module):
|
||||
rope_theta = getattr(config, "rope_theta", 10000.0)
|
||||
inv_freq = 1.0 / (rope_theta ** (torch.arange(0, head_dim, 2, dtype=torch.float32) / head_dim))
|
||||
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
||||
if current_omni_platform.is_npu():
|
||||
max_seq = int(getattr(config, "num_code_groups", 0) or 0) + 1
|
||||
positions = torch.arange(max_seq, dtype=torch.float32)
|
||||
freqs = torch.outer(positions, inv_freq)
|
||||
emb = torch.cat((freqs, freqs), dim=-1)
|
||||
self.register_buffer("cos_cached", emb.cos(), persistent=False)
|
||||
self.register_buffer("sin_cached", emb.sin(), persistent=False)
|
||||
|
||||
def forward(self, x: torch.Tensor, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
def forward_npu(self, x: torch.Tensor, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
return self.cos_cached[position_ids].to(dtype=x.dtype), self.sin_cached[position_ids].to(dtype=x.dtype)
|
||||
|
||||
def forward_cuda(self, x: torch.Tensor, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
return self.forward_native(x, position_ids)
|
||||
|
||||
def forward_xpu(self, x: torch.Tensor, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
return self.forward_native(x, position_ids)
|
||||
|
||||
def forward_native(self, x: torch.Tensor, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
# position_ids: [batch, seq_len]
|
||||
inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1)
|
||||
position_ids_expanded = position_ids[:, None, :].float()
|
||||
@@ -145,7 +174,6 @@ class CodePredictorAttention(nn.Module):
|
||||
self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=False)
|
||||
self.q_norm = _RMSNorm(self.head_dim, eps=config.rms_norm_eps)
|
||||
self.k_norm = _RMSNorm(self.head_dim, eps=config.rms_norm_eps)
|
||||
|
||||
if current_omni_platform.is_npu():
|
||||
if self.max_seq > 2048:
|
||||
raise ValueError(
|
||||
@@ -168,8 +196,6 @@ class CodePredictorAttention(nn.Module):
|
||||
bsz: int,
|
||||
seq_len: int,
|
||||
) -> torch.Tensor:
|
||||
import torch_npu
|
||||
|
||||
q_f, k_f, v_f = q, k, v
|
||||
if self.is_gqa:
|
||||
k_f = (
|
||||
@@ -230,10 +256,13 @@ class CodePredictorAttention(nn.Module):
|
||||
# cos/sin are [batch, seq_len, head_dim], need unsqueeze at dim=1 for heads
|
||||
cos = cos.unsqueeze(1) # [batch, 1, seq_len, head_dim]
|
||||
sin = sin.unsqueeze(1)
|
||||
q = (q * cos) + (_rotate_half(q) * sin)
|
||||
k = (k * cos) + (_rotate_half(k) * sin)
|
||||
|
||||
if not current_omni_platform.is_npu():
|
||||
if current_omni_platform.is_npu():
|
||||
q = torch_npu.npu_rotary_mul(q, cos, sin)
|
||||
k = torch_npu.npu_rotary_mul(k, cos, sin)
|
||||
attn_out = self._forward_npu_attention(q, k, v, bsz, seq_len)
|
||||
else:
|
||||
q = (q * cos) + (_rotate_half(q) * sin)
|
||||
k = (k * cos) + (_rotate_half(k) * sin)
|
||||
attn_out = F.scaled_dot_product_attention(
|
||||
q,
|
||||
k,
|
||||
@@ -242,8 +271,6 @@ class CodePredictorAttention(nn.Module):
|
||||
is_causal=True,
|
||||
enable_gqa=self.is_gqa,
|
||||
)
|
||||
else:
|
||||
attn_out = self._forward_npu_attention(q, k, v, bsz, seq_len)
|
||||
|
||||
attn_out = attn_out.transpose(1, 2).reshape(bsz, seq_len, -1)
|
||||
return self.o_proj(attn_out)
|
||||
@@ -290,10 +317,17 @@ class CodePredictorDecoderLayer(nn.Module):
|
||||
residual = hidden_states
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
hidden_states = self.self_attn(hidden_states, position_embeddings)
|
||||
hidden_states = residual + hidden_states
|
||||
|
||||
residual = hidden_states
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
if current_omni_platform.is_npu():
|
||||
hidden_states, _, residual = torch_npu.npu_add_rms_norm(
|
||||
hidden_states,
|
||||
residual,
|
||||
self.post_attention_layernorm.weight,
|
||||
self.post_attention_layernorm.variance_epsilon,
|
||||
)
|
||||
else:
|
||||
hidden_states = residual + hidden_states
|
||||
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
|
||||
@@ -875,7 +909,6 @@ class CodePredictorWrapper(nn.Module):
|
||||
# Use captured device graph if available, otherwise call compiled fn.
|
||||
device_graph_entry = self._device_graphs.get(graph_key)
|
||||
|
||||
# Run transformer (device graph replay or compiled forward)
|
||||
if device_graph_entry is not None:
|
||||
device_graph_entry[0].replay()
|
||||
hidden_out = device_graph_entry[1]
|
||||
@@ -939,6 +972,18 @@ class CodePredictorWrapper(nn.Module):
|
||||
# Weight loading
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _prepare_npu_weights(self) -> None:
|
||||
from vllm_ascend.utils import maybe_trans_nz
|
||||
|
||||
linear_count = 0
|
||||
with torch.no_grad():
|
||||
# Pack linear weights once for NPU matmul.
|
||||
for module in self.modules():
|
||||
if isinstance(module, nn.Linear):
|
||||
module.weight.data = maybe_trans_nz(module.weight.data)
|
||||
linear_count += 1
|
||||
logger.info("Prepared NPU code predictor weights: linear=%d", linear_count)
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights directly (no fused projection remapping needed)."""
|
||||
loaded: set[str] = set()
|
||||
@@ -965,4 +1010,7 @@ class CodePredictorWrapper(nn.Module):
|
||||
weight_loader(param, w)
|
||||
loaded.add(name)
|
||||
|
||||
if current_omni_platform.is_npu():
|
||||
self._prepare_npu_weights()
|
||||
|
||||
return loaded
|
||||
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
|
||||
_WORKER_PATCHED = False
|
||||
_QWEN3_TTS_TALKER_ARCH = "Qwen3TTSTalkerForConditionalGeneration"
|
||||
_QWEN3_TTS_CODE2WAV_ARCH = "Qwen3TTSCode2Wav"
|
||||
|
||||
|
||||
def apply_patches() -> None:
|
||||
@@ -22,9 +23,13 @@ def apply_patches() -> None:
|
||||
|
||||
|
||||
def apply_model_patches(model_config) -> None:
|
||||
if getattr(model_config, "model_arch", None) != _QWEN3_TTS_TALKER_ARCH:
|
||||
model_arch = getattr(model_config, "model_arch", None)
|
||||
if model_arch == _QWEN3_TTS_TALKER_ARCH:
|
||||
from vllm_omni.platforms.npu._310p.patch.qwen3_tts import apply_talker_patches
|
||||
|
||||
apply_talker_patches()
|
||||
return
|
||||
elif model_arch == _QWEN3_TTS_CODE2WAV_ARCH:
|
||||
from vllm_omni.platforms.npu._310p.patch.qwen3_tts import apply_code2wav_patches
|
||||
|
||||
from vllm_omni.platforms.npu._310p.patch.qwen3_tts import apply_talker_patches
|
||||
|
||||
apply_talker_patches()
|
||||
apply_code2wav_patches()
|
||||
@@ -5,26 +5,30 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from transformers.models.mimi import modeling_mimi
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch_npu
|
||||
from vllm.multimodal.audio import AudioResampler
|
||||
from vllm_ascend._310p.attention.attention_mask import AttentionMaskBuilder310
|
||||
from vllm_ascend.sample.sampler import apply_top_k_top_p, random_sample
|
||||
from vllm_ascend.utils import ACL_FORMAT_FRACTAL_NZ, aligned_16, maybe_trans_nz, nd_to_nz_2d
|
||||
|
||||
from vllm_omni.model_executor.models.common import qwen3_code_predictor
|
||||
from vllm_omni.model_executor.models.qwen3_tts import prompt_embeds_builder, qwen3_tts_talker
|
||||
from vllm_omni.model_executor.models.qwen3_tts import (
|
||||
prompt_embeds_builder,
|
||||
qwen3_tts_code2wav,
|
||||
qwen3_tts_code_predictor_vllm,
|
||||
qwen3_tts_talker,
|
||||
)
|
||||
|
||||
_RUNTIME_DTYPE = torch.float16
|
||||
_CPU_DEVICE = torch.device("cpu")
|
||||
|
||||
|
||||
class _MimiEuclideanCodebook310P(modeling_mimi.MimiEuclideanCodebook):
|
||||
def quantize(self, hidden_states):
|
||||
# 310P does not support torch.cdist on NPU.
|
||||
device = hidden_states.device
|
||||
dists = torch.cdist(
|
||||
hidden_states[None].to(_CPU_DEVICE, torch.float32), self.embed[None].to(_CPU_DEVICE, torch.float32), p=2
|
||||
)[0]
|
||||
return dists.argmin(dim=-1).to(device=device)
|
||||
_PATCHED = False
|
||||
_CODE2WAV_PATCHED = False
|
||||
|
||||
|
||||
class _Qwen3TTSTalker310P(qwen3_tts_talker.Qwen3TTSTalkerForConditionalGeneration):
|
||||
@@ -32,10 +36,18 @@ class _Qwen3TTSTalker310P(qwen3_tts_talker.Qwen3TTSTalkerForConditionalGeneratio
|
||||
super().__init__(vllm_config=vllm_config, prefix=prefix)
|
||||
self._embedding_dtype = _RUNTIME_DTYPE
|
||||
self._prompt_builder._embedding_dtype = _RUNTIME_DTYPE
|
||||
# 310P random sampling operators cannot be captured by ACL graphs.
|
||||
# Keep Talker-MTP eager so CodePredictor can replay its own NPU graphs
|
||||
# without changing sampling semantics.
|
||||
self.talker_mtp_graph_safe = False
|
||||
self.talker_mtp_accepts_per_row_generators = True
|
||||
|
||||
def load_weights(self, weights):
|
||||
loaded = super().load_weights(weights)
|
||||
self.encoder.to(dtype=_RUNTIME_DTYPE)
|
||||
# The Mimi tokenizer encoder is used only while building ref_audio
|
||||
# prompts. Keep this preprocessing-only module on CPU because the 310P
|
||||
# NPU path is sensitive to changing reference-audio shapes.
|
||||
self.encoder.to(device=_CPU_DEVICE, dtype=torch.float32)
|
||||
return loaded
|
||||
|
||||
def _encode_ref_audio_batch(
|
||||
@@ -51,7 +63,7 @@ class _Qwen3TTSTalker310P(qwen3_tts_talker.Qwen3TTSTalkerForConditionalGeneratio
|
||||
resampler = AudioResampler(target_sr=target_sr)
|
||||
wavs = [resampler.resample(w.astype(np.float32), orig_sr=int(sr)) for w in wavs]
|
||||
|
||||
inputs = fe(raw_audio=wavs, sampling_rate=target_sr, return_tensors="pt").to(device).to(_RUNTIME_DTYPE)
|
||||
inputs = fe(raw_audio=wavs, sampling_rate=target_sr, return_tensors="pt").to(torch.float32)
|
||||
|
||||
with torch.inference_mode():
|
||||
encoded = self.encoder.encode(
|
||||
@@ -100,10 +112,56 @@ class _Qwen3TTSPromptEmbedsBuilder310P(prompt_embeds_builder.Qwen3TTSPromptEmbed
|
||||
return spk.to(dtype=dtype)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Code2Wav layer patches
|
||||
# ===================================================================
|
||||
#
|
||||
# Code2Wav runs under the 310P graph path after the stage-0 Talker has
|
||||
# produced codec tokens. Common NPU weight packing and fused tokenizer ops
|
||||
# live in the shared model code; the 310P patch only selects the runtime dtype.
|
||||
|
||||
|
||||
class _Qwen3TTSCode2Wav310P(qwen3_tts_code2wav.Qwen3TTSCode2Wav):
|
||||
"""Qwen3-TTS Code2Wav specialized for the 310P NPU path."""
|
||||
|
||||
def _npu_decoder_runtime_dtype(self, device: torch.device) -> torch.dtype:
|
||||
return _RUNTIME_DTYPE
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# CodePredictor layer patches
|
||||
# ===================================================================
|
||||
#
|
||||
# Keep the portable implementation in common/qwen3_code_predictor.py.
|
||||
# The overrides below are installed only by the 310P platform patch because
|
||||
# the short CodePredictor loop is graph-captured on 310P and needs the 310P
|
||||
# flash-attention mask layout plus loop-local projection and sampling changes.
|
||||
|
||||
|
||||
class _Qwen3CodePredictorAttention310P(qwen3_code_predictor.CodePredictorAttention):
|
||||
"""Attention override using 310P RoPE and flash-attention kernels.
|
||||
|
||||
The shared attention path is written in portable PyTorch. This override
|
||||
keeps the 310P-specific RoPE op, token alignment, and FRACTAL_NZ mask path
|
||||
scoped to the platform patch.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self._buffers.pop("_fusion_causal_mask", None)
|
||||
self._q_size = self.num_heads * self.head_dim
|
||||
self._kv_size = self.num_kv_heads * self.head_dim
|
||||
self._fused_qkv_weight = None
|
||||
self._fused_qkv_bias = None
|
||||
|
||||
def prepare_qkv_weights(self) -> None:
|
||||
# Pack QKV once so each graph replay uses one matmul and consumes the
|
||||
# weight directly in the 310P matmul layout.
|
||||
self._fused_qkv_weight = maybe_trans_nz(
|
||||
torch.cat((self.q_proj.weight, self.k_proj.weight, self.v_proj.weight), dim=0).contiguous()
|
||||
)
|
||||
if self.q_proj.bias is not None:
|
||||
self._fused_qkv_bias = torch.cat((self.q_proj.bias, self.k_proj.bias, self.v_proj.bias), dim=0)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
@@ -111,32 +169,29 @@ class _Qwen3CodePredictorAttention310P(qwen3_code_predictor.CodePredictorAttenti
|
||||
position_embeddings: tuple[torch.Tensor, torch.Tensor],
|
||||
attention_mask: torch.Tensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
if hidden_states.device.type != "npu" or attention_mask is None:
|
||||
return super().forward(hidden_states, position_embeddings)
|
||||
|
||||
bsz, seq_len, _ = hidden_states.shape
|
||||
hidden_shape_q = (bsz, seq_len, self.num_heads, self.head_dim)
|
||||
hidden_shape_kv = (bsz, seq_len, self.num_kv_heads, self.head_dim)
|
||||
|
||||
q = self.q_norm(self.q_proj(hidden_states).view(hidden_shape_q)).transpose(1, 2)
|
||||
k = self.k_norm(self.k_proj(hidden_states).view(hidden_shape_kv)).transpose(1, 2)
|
||||
v = self.v_proj(hidden_states).view(hidden_shape_kv).transpose(1, 2)
|
||||
qkv = F.linear(hidden_states, self._fused_qkv_weight, self._fused_qkv_bias)
|
||||
q, k, v = qkv.split((self._q_size, self._kv_size, self._kv_size), dim=-1)
|
||||
q = self.q_norm(q.view(bsz, seq_len, self.num_heads, self.head_dim)).transpose(1, 2)
|
||||
k = self.k_norm(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)
|
||||
|
||||
cos, sin = position_embeddings
|
||||
cos = cos.unsqueeze(1)
|
||||
sin = sin.unsqueeze(1)
|
||||
q = (q * cos) + (qwen3_code_predictor._rotate_half(q) * sin)
|
||||
k = (k * cos) + (qwen3_code_predictor._rotate_half(k) * sin)
|
||||
# Use the fused Ascend RoPE op instead of expanding RoPE into
|
||||
# elementwise mul/add/rotate-half kernels.
|
||||
q = torch_npu.npu_rotary_mul(q, cos, sin)
|
||||
k = torch_npu.npu_rotary_mul(k, cos, sin)
|
||||
|
||||
real_tokens = int(bsz) * int(seq_len)
|
||||
output_dtype = q.dtype
|
||||
|
||||
import torch_npu
|
||||
from vllm_ascend.utils import aligned_16
|
||||
|
||||
q_f = aligned_16(q.to(torch.float16).transpose(1, 2).reshape(real_tokens, self.num_heads, self.head_dim))
|
||||
k_f = aligned_16(k.to(torch.float16).transpose(1, 2).reshape(real_tokens, self.num_kv_heads, self.head_dim))
|
||||
v_f = aligned_16(v.to(torch.float16).transpose(1, 2).reshape(real_tokens, self.num_kv_heads, self.head_dim))
|
||||
# 310P flash attention consumes token-major fp16 inputs with 16-token
|
||||
# alignment; seq_lens carries the padding information.
|
||||
q_f = aligned_16(q.transpose(1, 2).reshape(real_tokens, self.num_heads, self.head_dim))
|
||||
k_f = aligned_16(k.transpose(1, 2).reshape(real_tokens, self.num_kv_heads, self.head_dim))
|
||||
v_f = aligned_16(v.transpose(1, 2).reshape(real_tokens, self.num_kv_heads, self.head_dim))
|
||||
|
||||
aligned_tokens = int(q_f.shape[0])
|
||||
seq_lens = torch.full((int(bsz),), int(seq_len), dtype=torch.int32, device="cpu")
|
||||
@@ -160,23 +215,34 @@ class _Qwen3CodePredictorAttention310P(qwen3_code_predictor.CodePredictorAttenti
|
||||
|
||||
|
||||
class _Qwen3CodePredictorDecoderLayer310P(qwen3_code_predictor.CodePredictorDecoderLayer):
|
||||
"""Decoder layer override that passes the 310P attention mask."""
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
position_embeddings: tuple[torch.Tensor, torch.Tensor],
|
||||
attention_mask: torch.Tensor | None = None,
|
||||
attention_mask: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
residual = hidden_states
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
hidden_states = self.self_attn(hidden_states, position_embeddings, attention_mask=attention_mask)
|
||||
hidden_states = residual + hidden_states
|
||||
|
||||
residual = hidden_states
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
hidden_states, _, residual = torch_npu.npu_add_rms_norm(
|
||||
hidden_states,
|
||||
residual,
|
||||
self.post_attention_layernorm.weight,
|
||||
self.post_attention_layernorm.variance_epsilon,
|
||||
)
|
||||
return residual + self.mlp(hidden_states)
|
||||
|
||||
|
||||
class _Qwen3CodePredictorBaseModel310P(qwen3_code_predictor.CodePredictorBaseModel):
|
||||
"""Base model override with a cached 310P causal mask.
|
||||
|
||||
The 310P flash-attention path consumes the additive causal mask in
|
||||
FRACTAL_NZ format. The CodePredictor sequence length is fixed and short,
|
||||
so the mask is built once and reused across graph replays.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self._attention_mask_310p = None
|
||||
@@ -188,14 +254,9 @@ class _Qwen3CodePredictorBaseModel310P(qwen3_code_predictor.CodePredictorBaseMod
|
||||
inputs_embeds: torch.Tensor,
|
||||
position_ids: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
if inputs_embeds.device.type != "npu":
|
||||
return super().forward(inputs_embeds, position_ids)
|
||||
|
||||
if self._attention_mask_310p is None or self._attention_mask_310p_device != inputs_embeds.device:
|
||||
import torch_npu
|
||||
from vllm_ascend._310p.attention.attention_mask import AttentionMaskBuilder310
|
||||
from vllm_ascend.utils import ACL_FORMAT_FRACTAL_NZ, nd_to_nz_2d
|
||||
|
||||
# Store the additive causal mask in the format consumed by the
|
||||
# 310P flash-attention kernel.
|
||||
mask = AttentionMaskBuilder310.gen_causal_additive_mask(
|
||||
self._attention_mask_310p_max_seq,
|
||||
inputs_embeds.device,
|
||||
@@ -205,23 +266,239 @@ class _Qwen3CodePredictorBaseModel310P(qwen3_code_predictor.CodePredictorBaseMod
|
||||
|
||||
input_dtype = inputs_embeds.dtype
|
||||
hidden_states = inputs_embeds
|
||||
with torch.amp.autocast(inputs_embeds.device.type, enabled=False, dtype=torch.float32):
|
||||
position_embeddings = self.rotary_emb(hidden_states, position_ids)
|
||||
for layer in self.layers:
|
||||
hidden_states = layer(
|
||||
hidden_states,
|
||||
position_embeddings,
|
||||
attention_mask=self._attention_mask_310p,
|
||||
)
|
||||
hidden_states = self.norm(hidden_states)
|
||||
position_embeddings = self.rotary_emb(hidden_states, position_ids)
|
||||
for layer in self.layers:
|
||||
hidden_states = layer(
|
||||
hidden_states,
|
||||
position_embeddings,
|
||||
attention_mask=self._attention_mask_310p,
|
||||
)
|
||||
hidden_states = self.norm(hidden_states)
|
||||
return hidden_states.to(input_dtype)
|
||||
|
||||
|
||||
class _Qwen3TTSTalkerCodePredictor310P(
|
||||
qwen3_tts_code_predictor_vllm.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM
|
||||
):
|
||||
"""Qwen3-TTS code predictor specialized for the 310P NPU path."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
vllm_config,
|
||||
config,
|
||||
talker_config,
|
||||
prefix: str = "code_predictor",
|
||||
) -> None:
|
||||
super().__init__(
|
||||
vllm_config=vllm_config,
|
||||
config=config,
|
||||
talker_config=talker_config,
|
||||
prefix=prefix,
|
||||
)
|
||||
self._projected_codec_embed_weight = None
|
||||
|
||||
def _prepare_npu_weights(self) -> None:
|
||||
qkv_projections = set()
|
||||
with torch.no_grad():
|
||||
for layer in self.model.layers:
|
||||
attention = layer.self_attn
|
||||
attention.prepare_qkv_weights()
|
||||
qkv_projections.update((attention.q_proj, attention.k_proj, attention.v_proj))
|
||||
|
||||
for module in self.modules():
|
||||
if isinstance(module, nn.Linear) and module not in qkv_projections:
|
||||
module.weight.data = maybe_trans_nz(module.weight.data)
|
||||
|
||||
def load_weights(self, weights):
|
||||
loaded = super().load_weights(weights)
|
||||
with torch.no_grad():
|
||||
self._projected_codec_embed_weight = torch.stack(
|
||||
[self.small_to_mtp_projection(embed.weight).detach() for embed in self.model.codec_embedding],
|
||||
dim=0,
|
||||
).contiguous()
|
||||
return loaded
|
||||
|
||||
@torch.inference_mode()
|
||||
def forward(
|
||||
self,
|
||||
layer0_code: torch.Tensor,
|
||||
layer0_embed: torch.Tensor,
|
||||
last_talker_hidden: torch.Tensor,
|
||||
do_sample: bool = True,
|
||||
temperature: float = 0.9,
|
||||
top_k: int = 50,
|
||||
top_p: float = 1.0,
|
||||
generator: torch.Generator | None = None,
|
||||
generators: Sequence[torch.Generator | None] | None = None,
|
||||
) -> torch.Tensor:
|
||||
bsz = int(layer0_code.shape[0])
|
||||
if generators is not None and len(generators) != bsz:
|
||||
raise ValueError(f"generators must have one entry per row: got {len(generators)} for batch {bsz}")
|
||||
num_groups = self._num_groups
|
||||
device = layer0_code.device
|
||||
|
||||
self._setup_compile()
|
||||
dtype = self._model_dtype
|
||||
|
||||
padded_bsz = self._padded_bsz(bsz)
|
||||
self._ensure_buffers(device, dtype, padded_bsz)
|
||||
|
||||
proj_buf = self._proj_buf
|
||||
max_seq = num_groups + 1
|
||||
projection = self.small_to_mtp_projection
|
||||
model_fwd = self._compiled_model_fwd
|
||||
lm_heads = self._lm_heads_list
|
||||
if generators is not None:
|
||||
npu_generators = {i: row_generator for i, row_generator in enumerate(generators) if row_generator}
|
||||
elif generator is not None:
|
||||
npu_generators = {i: generator for i in range(bsz)}
|
||||
else:
|
||||
npu_generators = {}
|
||||
|
||||
proj_buf[:padded_bsz].zero_()
|
||||
initial_embeds = torch.cat(
|
||||
(
|
||||
last_talker_hidden.reshape(bsz, 1, -1),
|
||||
layer0_embed.reshape(bsz, 1, -1),
|
||||
),
|
||||
dim=1,
|
||||
)
|
||||
if initial_embeds.dtype != dtype:
|
||||
initial_embeds = initial_embeds.to(dtype)
|
||||
proj_buf[:bsz, :2, :].copy_(projection(initial_embeds))
|
||||
|
||||
stored_mode = self._wrapper_config.sampling_mode == "stored"
|
||||
if stored_mode:
|
||||
s_top_k = self._top_k
|
||||
s_top_p = self._top_p
|
||||
else:
|
||||
use_sampling = do_sample and temperature > 0
|
||||
inv_temperature = 1.0 / max(temperature, 1e-6) if use_sampling else 0.0
|
||||
if use_sampling and top_p != 1.0:
|
||||
raise NotImplementedError(
|
||||
"top_p sampling is not implemented for the vLLM-native code predictor; please set top_p=1.0."
|
||||
)
|
||||
|
||||
top_k_tensor = None
|
||||
top_p_tensor = None
|
||||
if stored_mode:
|
||||
top_k_hint = s_top_k if s_top_k > 0 else None
|
||||
if s_top_k > 0:
|
||||
top_k_tensor = torch.full((bsz,), s_top_k, dtype=torch.int32, device=device)
|
||||
if s_top_p < 1.0:
|
||||
top_p_tensor = torch.full((bsz,), s_top_p, dtype=dtype, device=device)
|
||||
elif use_sampling:
|
||||
top_k_hint = top_k if top_k > 0 else None
|
||||
if top_k > 0:
|
||||
top_k_tensor = torch.full((bsz,), top_k, dtype=torch.int32, device=device)
|
||||
else:
|
||||
top_k_hint = None
|
||||
|
||||
all_codes = torch.empty(bsz, num_groups, dtype=torch.long, device=device)
|
||||
all_codes[:, 0] = layer0_code.reshape(bsz)
|
||||
|
||||
for step in range(1, num_groups):
|
||||
graph_key: int | tuple[int, int] = padded_bsz
|
||||
seq_len = max_seq
|
||||
if self._prefix_graphs_enabled:
|
||||
prefix_key = (padded_bsz, step + 1)
|
||||
if prefix_key in self._device_graphs:
|
||||
graph_key = prefix_key
|
||||
seq_len = step + 1
|
||||
pos_ids = self._bucket_pos_ids.get(graph_key)
|
||||
if pos_ids is None:
|
||||
pos_ids = (
|
||||
torch.arange(seq_len, device=device, dtype=torch.long)
|
||||
.unsqueeze(0)
|
||||
.expand(padded_bsz, -1)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
device_graph_entry = self._device_graphs.get(graph_key)
|
||||
if device_graph_entry is not None:
|
||||
device_graph_entry[0].replay()
|
||||
hidden_out = device_graph_entry[1]
|
||||
else:
|
||||
hidden_out = model_fwd(proj_buf[:padded_bsz, :seq_len, :], pos_ids)
|
||||
|
||||
logits = lm_heads[step - 1](hidden_out[:bsz, step, :])
|
||||
|
||||
if stored_mode:
|
||||
if top_k_tensor is not None or top_p_tensor is not None:
|
||||
logits = apply_top_k_top_p(logits, p=top_p_tensor, k=top_k_tensor, top_k=top_k_hint)
|
||||
candidate_indices = None
|
||||
if isinstance(logits, tuple):
|
||||
logits, candidate_indices = logits
|
||||
probs = F.softmax(logits, dim=-1, dtype=torch.float32)
|
||||
code = random_sample(probs, npu_generators)
|
||||
if candidate_indices is not None:
|
||||
code = candidate_indices.gather(1, code.unsqueeze(1)).squeeze(1)
|
||||
else:
|
||||
if use_sampling:
|
||||
scaled = logits * inv_temperature
|
||||
if top_k_tensor is not None:
|
||||
scaled = apply_top_k_top_p(scaled, p=None, k=top_k_tensor, top_k=top_k_hint)
|
||||
candidate_indices = None
|
||||
if isinstance(scaled, tuple):
|
||||
scaled, candidate_indices = scaled
|
||||
probs = F.softmax(scaled, dim=-1, dtype=torch.float32)
|
||||
code = random_sample(probs, npu_generators)
|
||||
if candidate_indices is not None:
|
||||
code = candidate_indices.gather(1, code.unsqueeze(1)).squeeze(1)
|
||||
else:
|
||||
code = logits.argmax(dim=-1, keepdim=True)
|
||||
|
||||
all_codes[:, step] = code.reshape(bsz)
|
||||
if step < num_groups - 1:
|
||||
proj_buf[:bsz, step + 1, :].copy_(
|
||||
F.embedding(code.reshape(-1), self._projected_codec_embed_weight[step - 1])
|
||||
)
|
||||
|
||||
return all_codes
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Patch registration
|
||||
# ===================================================================
|
||||
|
||||
|
||||
def apply_talker_patches() -> None:
|
||||
modeling_mimi.MimiEuclideanCodebook = _MimiEuclideanCodebook310P
|
||||
"""Install Qwen3-TTS Talker and CodePredictor 310P patches.
|
||||
|
||||
The generic model modules stay unchanged. Patch registration swaps in the
|
||||
310P-specialized CodePredictor classes and wrapper methods only when the
|
||||
310P platform applies the Talker patch.
|
||||
"""
|
||||
|
||||
global _PATCHED
|
||||
|
||||
if _PATCHED:
|
||||
return
|
||||
|
||||
qwen3_tts_talker.Qwen3TTSTalkerForConditionalGeneration = _Qwen3TTSTalker310P
|
||||
qwen3_tts_talker.Qwen3TTSPromptEmbedsBuilder = _Qwen3TTSPromptEmbedsBuilder310P
|
||||
qwen3_tts_talker.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM = _Qwen3TTSTalkerCodePredictor310P
|
||||
qwen3_tts_code_predictor_vllm.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM = (
|
||||
_Qwen3TTSTalkerCodePredictor310P
|
||||
)
|
||||
qwen3_tts_code_predictor_vllm.CodePredictorBaseModel = _Qwen3CodePredictorBaseModel310P
|
||||
qwen3_tts_code_predictor_vllm.Qwen3TTSTalkerCodePredictorModelVLLM = _Qwen3CodePredictorBaseModel310P
|
||||
prompt_embeds_builder.Qwen3TTSPromptEmbedsBuilder = _Qwen3TTSPromptEmbedsBuilder310P
|
||||
qwen3_code_predictor.CodePredictorAttention = _Qwen3CodePredictorAttention310P
|
||||
qwen3_code_predictor.CodePredictorDecoderLayer = _Qwen3CodePredictorDecoderLayer310P
|
||||
qwen3_code_predictor.CodePredictorBaseModel = _Qwen3CodePredictorBaseModel310P
|
||||
|
||||
_PATCHED = True
|
||||
|
||||
|
||||
def apply_code2wav_patches() -> None:
|
||||
"""Install the 310P Code2Wav runtime patch."""
|
||||
global _CODE2WAV_PATCHED
|
||||
|
||||
if _CODE2WAV_PATCHED:
|
||||
return
|
||||
|
||||
qwen3_tts_code2wav.Qwen3TTSCode2Wav = _Qwen3TTSCode2Wav310P
|
||||
|
||||
_CODE2WAV_PATCHED = True
|
||||
@@ -12,6 +12,9 @@ compile-mode setup is applied here after device initialization.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from vllm.v1.sample.sampler import Sampler
|
||||
from vllm_ascend._310p.sample.sampler import AscendSampler310
|
||||
|
||||
from vllm_omni.platforms.npu._310p import disable_jit_compile
|
||||
from vllm_omni.platforms.npu.worker import base as worker_base
|
||||
|
||||
@@ -24,4 +27,6 @@ class _OmniNPUWorkerBase310P(worker_base.OmniNPUWorkerBase):
|
||||
|
||||
|
||||
def apply_patch() -> None:
|
||||
# Triton-Ascend does not target 310P; use vLLM's native penalty path.
|
||||
AscendSampler310.apply_penalties = staticmethod(Sampler.apply_penalties)
|
||||
worker_base.OmniNPUWorkerBase = _OmniNPUWorkerBase310P
|
||||
@@ -1,15 +1,18 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
"""Monkey-patch ``Qwen3TTSCode2Wav.__init__`` for NPU Code2Wav runtime knobs."""
|
||||
"""Patch Qwen3-TTS Code2Wav NPU runtime setup and weight preparation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch_npu
|
||||
from vllm.config import VllmConfig
|
||||
from vllm.logger import init_logger
|
||||
from vllm_ascend.utils import maybe_trans_nz
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
@@ -18,6 +21,8 @@ logger = init_logger(__name__)
|
||||
|
||||
_PATCHED = False
|
||||
_original_init = None
|
||||
_original_load_weights = None
|
||||
_ACL_FORMAT_FRACTAL_Z = 4
|
||||
|
||||
|
||||
def _prepare_npu_code2wav_runtime() -> None:
|
||||
@@ -35,14 +40,43 @@ def _patched_init(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
|
||||
_original_init(self, vllm_config=vllm_config, prefix=prefix)
|
||||
|
||||
|
||||
def _prepare_npu_decoder_weights(decoder: nn.Module) -> None:
|
||||
linear_count = 0
|
||||
conv_count = 0
|
||||
with torch.no_grad():
|
||||
for module in decoder.modules():
|
||||
if isinstance(module, nn.Linear):
|
||||
module.weight.data = maybe_trans_nz(module.weight.data)
|
||||
linear_count += 1
|
||||
elif isinstance(module, (nn.Conv1d, nn.ConvTranspose1d)) and module.groups == 1:
|
||||
module.weight.data = torch_npu.npu_format_cast(module.weight.data.contiguous(), _ACL_FORMAT_FRACTAL_Z)
|
||||
conv_count += 1
|
||||
|
||||
logger.info("Prepared NPU Code2Wav weights: linear=%d conv=%d", linear_count, conv_count)
|
||||
|
||||
|
||||
def _patched_load_weights(self, weights):
|
||||
assert _original_load_weights is not None
|
||||
loaded = _original_load_weights(self, weights)
|
||||
device = self.vllm_config.device_config.device
|
||||
runtime_dtype = getattr(self, "_npu_decoder_runtime_dtype", lambda _: torch.float32)(device)
|
||||
self.decoder.to(device=device, dtype=runtime_dtype)
|
||||
_prepare_npu_decoder_weights(self.decoder)
|
||||
if runtime_dtype != torch.float32 and hasattr(self.decoder, "precompute_snake_caches"):
|
||||
self.decoder.precompute_snake_caches()
|
||||
return loaded
|
||||
|
||||
|
||||
def apply_qwen3_tts_code2wav_patch() -> None:
|
||||
global _PATCHED, _original_init
|
||||
global _PATCHED, _original_init, _original_load_weights
|
||||
if _PATCHED:
|
||||
return
|
||||
|
||||
from vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code2wav import Qwen3TTSCode2Wav
|
||||
|
||||
_original_init = Qwen3TTSCode2Wav.__init__
|
||||
_original_load_weights = Qwen3TTSCode2Wav.load_weights
|
||||
Qwen3TTSCode2Wav.__init__ = _patched_init # type: ignore[method-assign]
|
||||
Qwen3TTSCode2Wav.load_weights = _patched_load_weights # type: ignore[method-assign]
|
||||
_PATCHED = True
|
||||
logger.debug("Applied NPU patch for Qwen3TTSCode2Wav.__init__")
|
||||
logger.debug("Applied NPU patch for Qwen3TTSCode2Wav")
|
||||
@@ -0,0 +1,47 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
"""NPU patches for the Qwen3-TTS 12Hz tokenizer decoder."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import torch
|
||||
import torch_npu
|
||||
from vllm.logger import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
_PATCHED = False
|
||||
|
||||
|
||||
def _apply_rotary_pos_emb_npu(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
cos: torch.Tensor,
|
||||
sin: torch.Tensor,
|
||||
position_ids=None,
|
||||
unsqueeze_dim: int = 1,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
del position_ids
|
||||
cos = cos.unsqueeze(unsqueeze_dim)
|
||||
sin = sin.unsqueeze(unsqueeze_dim)
|
||||
return torch_npu.npu_rotary_mul(q, cos, sin), torch_npu.npu_rotary_mul(k, cos, sin)
|
||||
|
||||
|
||||
def _rms_norm_forward_npu(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||
return torch_npu.npu_rms_norm(hidden_states, self.weight, epsilon=self.variance_epsilon)[0]
|
||||
|
||||
|
||||
def apply_qwen3_tts_tokenizer_v2_patch() -> None:
|
||||
global _PATCHED
|
||||
if _PATCHED:
|
||||
return
|
||||
|
||||
from vllm_omni.model_executor.models.qwen3_tts.tokenizer_12hz import (
|
||||
modeling_qwen3_tts_tokenizer_v2,
|
||||
)
|
||||
|
||||
modeling_qwen3_tts_tokenizer_v2.apply_rotary_pos_emb = _apply_rotary_pos_emb_npu
|
||||
modeling_qwen3_tts_tokenizer_v2.Qwen3TTSTokenizerV2DecoderRMSNorm.forward = _rms_norm_forward_npu
|
||||
_PATCHED = True
|
||||
logger.debug("Applied NPU patch for Qwen3-TTS 12Hz tokenizer decoder")
|
||||
@@ -37,8 +37,12 @@ class NPUOmniPlatform(OmniPlatform, NPUPlatform):
|
||||
from vllm_omni.platforms.npu.models.qwen3_tts_code2wav import (
|
||||
apply_qwen3_tts_code2wav_patch,
|
||||
)
|
||||
from vllm_omni.platforms.npu.models.qwen3_tts_tokenizer_v2 import (
|
||||
apply_qwen3_tts_tokenizer_v2_patch,
|
||||
)
|
||||
|
||||
apply_qwen3_tts_code2wav_patch()
|
||||
apply_qwen3_tts_tokenizer_v2_patch()
|
||||
apply_310p_patches()
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in new issue
Block a user