Signed-off-by: natureofnature <wzliu@connect.hku.hk> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
168 lines
5.4 KiB
Python
168 lines
5.4 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Ascend hardware validation for MiniCPM-o Code2Wav NPUGraph replay."""
|
|
|
|
from math import prod
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
import torch
|
|
import torch.nn as nn
|
|
|
|
pytest.importorskip("vllm_ascend")
|
|
|
|
from tests.helpers.mark import hardware_marks
|
|
from vllm_omni.model_executor.models.minicpmo_4_5.batched_token2wav import (
|
|
BatchedToken2Wav,
|
|
)
|
|
from vllm_omni.platforms.npu.graph_tools import NPUExactGraphRunner
|
|
from vllm_omni.platforms.npu.models.minicpmo_4_5_code2wav import (
|
|
_graphable_estimator_step,
|
|
prepare_code2wav_graph_runtime,
|
|
)
|
|
from vllm_omni.platforms.npu.models.step_audio2_token2wav import (
|
|
npu_token2wav_sdpa_context,
|
|
)
|
|
from vllm_omni.platforms.npu.worker.npu_model_runner import OmniNPUModelRunner
|
|
from vllm_omni.worker.gpu_model_runner import OmniGPUModelRunner
|
|
|
|
pytestmark = [
|
|
pytest.mark.core_model,
|
|
pytest.mark.omni,
|
|
*hardware_marks(res={"npu": "A3"}, num_cards=1),
|
|
]
|
|
|
|
|
|
class _Flow(nn.Module):
|
|
def __init__(self, estimator: nn.Module):
|
|
super().__init__()
|
|
self.encoder = nn.Identity()
|
|
self.decoder = SimpleNamespace(estimator=estimator)
|
|
|
|
|
|
class _Token2Wav:
|
|
def __init__(self, estimator: nn.Module):
|
|
self.flow = _Flow(estimator).eval()
|
|
self.hift = nn.Identity().eval()
|
|
self.float16 = False
|
|
self.n_timesteps = 2
|
|
self.mel_cache_len = 1
|
|
self.source_cache_len = 2
|
|
self.speech_window = torch.ones(4, device="npu")
|
|
|
|
|
|
def test_npu_runner_inherits_runtime_snapshot_replacement():
|
|
assert issubclass(OmniNPUModelRunner, OmniGPUModelRunner)
|
|
assert OmniNPUModelRunner._replace_intermediate_buffer is OmniGPUModelRunner._replace_intermediate_buffer
|
|
runner = object.__new__(OmniNPUModelRunner)
|
|
request = SimpleNamespace(additional_information_cpu=None)
|
|
runner.requests = {"req": request}
|
|
runner.model_intermediate_buffer = {
|
|
"req": {
|
|
"codes": {"audio": torch.tensor([1, 2])},
|
|
"meta": {"last_chunk": True},
|
|
}
|
|
}
|
|
|
|
runner._replace_intermediate_buffer("req", {"meta": {"is_segment_finished": True}})
|
|
|
|
assert runner.model_intermediate_buffer["req"] == {"meta": {"is_segment_finished": True}}
|
|
assert request.additional_information_cpu == runner.model_intermediate_buffer["req"]
|
|
|
|
|
|
def test_minicpmo_cfm_estimator_npugraph_matches_eager():
|
|
decoder_dit = pytest.importorskip("cosyvoice2.flow.decoder_dit")
|
|
prepare_code2wav_graph_runtime()
|
|
|
|
estimator = (
|
|
decoder_dit.DiT(
|
|
in_channels=6,
|
|
out_channels=2,
|
|
depth=2,
|
|
num_heads=2,
|
|
head_dim=8,
|
|
hidden_size=16,
|
|
mlp_ratio=2.0,
|
|
)
|
|
.npu()
|
|
.eval()
|
|
)
|
|
graph_runner = NPUExactGraphRunner(max_graphs=4)
|
|
adapter = BatchedToken2Wav(_Token2Wav(estimator))
|
|
|
|
def inputs(seed: int):
|
|
def make(shape: tuple[int, ...], offset: float):
|
|
values = torch.arange(
|
|
prod(shape),
|
|
device="npu",
|
|
dtype=torch.float32,
|
|
)
|
|
return torch.sin(values * 0.17 + seed + offset).reshape(shape)
|
|
|
|
return (
|
|
make((2, 2, 6), 0.0),
|
|
make((2, 2, 6), 0.5),
|
|
make((2, 1, 16), 1.0),
|
|
make((2, 1), 1.5),
|
|
make((2, 1, 6), 2.0),
|
|
)
|
|
|
|
def compute(*values, caches=None):
|
|
cnn_cache, att_cache = (None, None) if caches is None else caches
|
|
return _graphable_estimator_step(
|
|
adapter,
|
|
estimator,
|
|
x=values[0],
|
|
mu=values[1],
|
|
time_embedding=values[2],
|
|
speakers=values[3],
|
|
cond=values[4],
|
|
cnn_cache=cnn_cache,
|
|
att_cache=att_cache,
|
|
)
|
|
|
|
with torch.inference_mode(), npu_token2wav_sdpa_context(require_math=True):
|
|
warm_inputs = inputs(11)
|
|
warm_outputs = graph_runner.run(
|
|
"cfm_estimator",
|
|
warm_inputs,
|
|
(False,),
|
|
lambda *values: compute(*values),
|
|
)
|
|
replay_inputs = inputs(13)
|
|
expected = compute(*replay_inputs)
|
|
actual = graph_runner.run(
|
|
"cfm_estimator",
|
|
replay_inputs,
|
|
(False,),
|
|
lambda *values: compute(*values),
|
|
)
|
|
for eager, replayed in zip(expected, actual, strict=True):
|
|
torch.testing.assert_close(replayed, eager, rtol=1e-3, atol=1e-3)
|
|
|
|
cached_inputs = (*inputs(17), warm_outputs[1], warm_outputs[2])
|
|
graph_runner.run(
|
|
"cfm_estimator",
|
|
cached_inputs,
|
|
(True,),
|
|
lambda *values: compute(*values[:5], caches=(values[5], values[6])),
|
|
)
|
|
replay_cached_inputs = (
|
|
*inputs(19),
|
|
warm_outputs[1] + 0.1,
|
|
warm_outputs[2] + 0.1,
|
|
)
|
|
expected_cached = compute(
|
|
*replay_cached_inputs[:5],
|
|
caches=(replay_cached_inputs[5], replay_cached_inputs[6]),
|
|
)
|
|
actual_cached = graph_runner.run(
|
|
"cfm_estimator",
|
|
replay_cached_inputs,
|
|
(True,),
|
|
lambda *values: compute(*values[:5], caches=(values[5], values[6])),
|
|
)
|
|
for eager, replayed in zip(expected_cached, actual_cached, strict=True):
|
|
torch.testing.assert_close(replayed, eager, rtol=1e-3, atol=1e-3)
|
|
assert graph_runner.stats == {"captures": 2, "failed": 0, "hits": 2}
|