Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions tests/engine/duplex/test_deadline_pacing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project

"""Deterministic unit tests for deadline-aligned silence continuation.

The engine-resident duplex scheduler in
``vllm_omni/engine/duplex/session/runner.py`` aligns each silence
continuation to ``submission_time_N + chunk_period`` and sleeps only the
remaining budget. These tests cover the pure deadline arithmetic and the
per-session reset semantics of the shared ``DuplexModelSessionState``
contract.
"""

from __future__ import annotations

import pytest

from vllm_omni.engine.duplex.session.runner import (
compute_silence_continuation_deadline,
)
from vllm_omni.model_executor.models.minicpmo_4_5.duplex.session import (
MiniCPMO45ServingSessionState,
)

pytestmark = [pytest.mark.core_model, pytest.mark.cpu]


@pytest.mark.parametrize(
("chunk_period_s", "now", "last_submit", "current_deadline", "delay_s", "next_silence_deadline"),
[
# First continuation anchors to the last real submission: unit N was
# submitted at t=1.0, audio N produced at 1.4 -> 0.6 s of sleep left.
(1.0, 1.4, 1.0, None, 0.6, 3.0),
# Only the remaining budget is slept (audio produced at 1.7).
(1.0, 1.7, 1.0, None, 0.3, 3.0),
# Overdue deadline: no sleep; the chain still advances by the period.
(1.0, 2.5, 1.0, None, 0.0, 3.0),
# No submission yet (None): the first continuation anchors to now.
(1.0, 0.4, None, None, 1.0, 2.4),
# Deadlines advance from the current deadline, not from now, so pipeline
# processing time does not accumulate as timer drift.
(1.0, 4.2, 3.0, 5.0, 0.8, 6.0),
# Two consecutive units that overrun their deadline by 0.2 s keep the
# 1 s cadence: no sleep, and the chain advances one period each.
(1.0, 2.2, 1.0, None, 0.0, 3.0),
(1.0, 3.2, 2.0, 3.0, 0.0, 4.0),
# A short overshoot (within one period of the current deadline) still
# chases the stale deadline: immediate submit, chain advances.
(1.0, 2.5, 1.0, 2.0, 0.0, 3.0),
# A long stall (more than one period past the current deadline) submits
# one continuation immediately and restarts from that submission.
(1.0, 5.0, 1.0, 2.0, 0.0, 6.0),
# The same recovery applies to the first continuation after a stall.
(1.0, 5.0, 1.0, None, 0.0, 6.0),
# Zero chunk period (guarded upstream): total, no sleep.
(0.0, 10.0, None, None, 0.0, 10.0),
],
)
def test_compute_silence_continuation_deadline(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] These cases pin the arithmetic well, but nothing here runs _schedule_silence_continuation itself: with a fake monotonic clock and a patched asyncio.sleep, a test that drives two continuations then a real append and asserts the recorded sleeps and the re-anchored deadline would catch both interleavings above and would fail if the scheduler ever went back to a flat sleep(chunk_period). Right now that revert passes this file, and removing _reanchor_chain fails nothing.

chunk_period_s: float,
now: float,
last_submit: float | None,
current_deadline: float | None,
delay_s: float,
next_silence_deadline: float,
) -> None:
delay, next_dl = compute_silence_continuation_deadline(
chunk_period_s=chunk_period_s,
now=now,
last_submit=last_submit,
current_deadline=current_deadline,
)
assert delay == pytest.approx(delay_s)
assert next_dl == pytest.approx(next_silence_deadline)


class TestSilenceDeadlineSessionState:
def test_clear_continuation_resets_the_deadline_chain(self) -> None:
# The native-duplex session state feeds the shared runner, so it must
# start with an unset chain and drop it at turn boundaries.
state = MiniCPMO45ServingSessionState()
assert state.last_native_submit_monotonic is None
assert state.silence_deadline_monotonic is None
state.last_native_submit_monotonic = 5.0
state.silence_deadline_monotonic = 6.0
state.clear_continuation()
assert state.last_native_submit_monotonic is None
assert state.silence_deadline_monotonic is None
Loading
Loading