From ea71649d31674823eba2c2d29a7fd7dc18f45d95 Mon Sep 17 00:00:00 2001 From: chaofengw Date: Sat, 17 Oct 2026 10:17:05 +0000 Subject: [PATCH 1/2] fix(personaplex): avoid timing discarded audio decode Decode each returned speech frame once in the Native benchmark. The auxiliary codec output was discarded but its decode was included in task latency; keep the existing official warmup and quality oracle unchanged. Add a CPU regression that verifies the returned waveform and counts work for a bounded generation request. Refs: #1782 Signed-off-by: chaofengw --- families/personaplex/reference/adapter.py | 2 +- .../personaplex/tests/benchmark/reference.py | 2 +- .../tests/test_reference_decode_work.py | 70 +++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 families/personaplex/tests/test_reference_decode_work.py diff --git a/families/personaplex/reference/adapter.py b/families/personaplex/reference/adapter.py index 0d2836b6ca..18ce5d7c33 100644 --- a/families/personaplex/reference/adapter.py +++ b/families/personaplex/reference/adapter.py @@ -62,8 +62,8 @@ def run() -> list[Any]: tokens = self.generator.step(encoded[:, :, index:index + 1]) if tokens is None: continue + # Decode the returned stream once; the other codec is only needed for official warmup. chunks.append(self.mimi.decode(tokens[:, 1:9])[0, 0].float()) - self.other_mimi.decode(tokens[:, 1:9]) if len(chunks) >= frames: return chunks return chunks diff --git a/families/personaplex/tests/benchmark/reference.py b/families/personaplex/tests/benchmark/reference.py index ef0cb1b69d..7c107971ae 100644 --- a/families/personaplex/tests/benchmark/reference.py +++ b/families/personaplex/tests/benchmark/reference.py @@ -104,8 +104,8 @@ def summary() -> Mapping[str, Any]: tokens = generator.step(encoded[:, :, index : index + 1]) if tokens is None: continue + # The auxiliary codec is for official warmup, not a second copy of returned audio. decoded_chunks.append(mimi.decode(tokens[:, 1:9])) - other_mimi.decode(tokens[:, 1:9]) generated_frames += 1 if generated_frames >= max_frames: return summary() diff --git a/families/personaplex/tests/test_reference_decode_work.py b/families/personaplex/tests/test_reference_decode_work.py new file mode 100644 index 0000000000..3cc9ffd663 --- /dev/null +++ b/families/personaplex/tests/test_reference_decode_work.py @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""The timed Native task decodes the audio it returns, once per frame.""" + +from __future__ import annotations + +import sys +from types import ModuleType, SimpleNamespace + +import numpy as np +import pytest + +from families.personaplex.reference.adapter import Adapter + + +def test_native_speech_does_not_decode_discarded_audio(monkeypatch, tmp_path): + torch = pytest.importorskip("torch") + + class Codec: + sample_rate = 24_000 + + def __init__(self): + self.decode_calls = 0 + + def reset_streaming(self): + pass + + def decode(self, tokens): + self.decode_calls += 1 + value = float(tokens[0, 0, 0]) + return torch.tensor([[[value, value + 0.5]]]) + + class Generator: + def reset_streaming(self): + pass + + def step(self, encoded): + return encoded[:, :1].expand(1, 17, 1) + + # Exercise the real invoke loop without loading a checkpoint or using CUDA. + lm = ModuleType("moshi.models.lm") + lm._iterate_audio = lambda *args, **kwargs: iter(()) + lm.encode_from_sphn = lambda *args, **kwargs: iter( + [torch.tensor([[[1, 2, 3]]], dtype=torch.int64)] + ) + monkeypatch.setitem(sys.modules, "moshi.models.lm", lm) + + adapter = Adapter.__new__(Adapter) + adapter.mimi = Codec() + adapter.other_mimi = Codec() # retained for the pinned official warmup + adapter.generator = Generator() + adapter.frame_size = 1920 + adapter.host = SimpleNamespace( + required=lambda request, name: request[name], + load_audio=lambda *args: np.zeros(5760, dtype=np.float32), + timed=lambda run: (run(), 1.0), + tensor_observation=lambda audio, base: {"audio": audio}, + invocation=lambda observation, ms: (observation, ms), + ) + + observation, _ = adapter.invoke( + {"audio_path": "input.wav", "max_new_tokens": 2}, tmp_path / "result" + ) + + np.testing.assert_array_equal(observation["audio"], [1.0, 1.5, 2.0, 2.5]) + assert observation["output_tokens"] == 2 + assert observation["sample_rate"] == 24_000 + assert adapter.mimi.decode_calls == 2 + assert adapter.other_mimi.decode_calls == 0 From 0e0ea393ee9649597da6bef04c76285acf0af6ee Mon Sep 17 00:00:00 2001 From: chaofengw Date: Sat, 17 Oct 2026 10:43:59 +0000 Subject: [PATCH 2/2] chore(personaplex): focus the Native timing fix Keep this change on the active AIPerf reference path. The legacy benchmark entry point depends on the retired qualification harness and is outside this fix. Refs: #1782 Signed-off-by: chaofengw --- families/personaplex/tests/benchmark/reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/families/personaplex/tests/benchmark/reference.py b/families/personaplex/tests/benchmark/reference.py index 7c107971ae..ef0cb1b69d 100644 --- a/families/personaplex/tests/benchmark/reference.py +++ b/families/personaplex/tests/benchmark/reference.py @@ -104,8 +104,8 @@ def summary() -> Mapping[str, Any]: tokens = generator.step(encoded[:, :, index : index + 1]) if tokens is None: continue - # The auxiliary codec is for official warmup, not a second copy of returned audio. decoded_chunks.append(mimi.decode(tokens[:, 1:9])) + other_mimi.decode(tokens[:, 1:9]) generated_frames += 1 if generated_frames >= max_frames: return summary()