Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
665e9c8
perf(minicpmo_4_5): raise the stage-1 concurrency cap to 8
y-null Sep 21, 2026
d5795da
perf(minicpmo_4_5): raise the NPU graph pool from 32 to 64
y-null Sep 21, 2026
97a033f
feat(minicpmo_4_5): emit a 25-frame first audio chunk
y-null Sep 21, 2026
6edaa61
perf(minicpmo_4_5): add an opt-in stage-2 single-row even batch
y-null Sep 21, 2026
10c5f20
perf(minicpmo_4_5): enable stage-0 prefix caching
y-null Sep 21, 2026
e257447
perf(minicpmo_4_5): enable stage-0 n-gram speculative decoding
y-null Sep 21, 2026
0672891
feat(minicpmo_4_5): Talker K=8 multi-frame decode with a fixed stop c…
y-null Sep 21, 2026
8e236e8
fix(config): drop stage-0 n-gram block that conflicts with prefix cac…
y-null Sep 21, 2026
d7112a4
test(minicpmo_4_5): add no-device regression tests for the Talker mul…
y-null Sep 21, 2026
6142cbf
fix(minicpmo_4_5): keep the single-frame codec contract when multi-fr…
y-null Sep 21, 2026
001275f
style: fix ruff lint and format findings across the branch
y-null Sep 21, 2026
5e6973f
fix(deploy): align minicpmo_4_5.yaml with the validated optimization set
y-null Sep 21, 2026
4b6c691
refactor(minicpmo_4_5): configure the Talker multi-frame decode from …
y-null Sep 21, 2026
34891c8
refactor(minicpmo_4_5): configure the Talker multi-frame decode from …
y-null Sep 21, 2026
f33256e
perf(minicpmo_4_5): let CFM mel bucketing take effect on NPU
y-null Sep 21, 2026
4e7497e
refactor(minicpmo_4_5): configure the Talker multi-frame decode from …
y-null Sep 21, 2026
e6e06e4
test(minicpmo_4_5): cover a bucketing pad mask replaying in-graph
y-null Sep 21, 2026
e94578a
revert(npu): restore the 910C/A3 Triton warmup guard
y-null Sep 21, 2026
6426adf
feat(deploy): turn on stage-0 n-gram speculation in the two-replica p…
y-null Sep 21, 2026
67bbd5b
docs(deploy): the profile keeps stage-0 speculation at 15 because tha…
y-null Sep 21, 2026
09a43c9
revert(deploy): drop the stage-0 n-gram block from the two-replica pr…
y-null Sep 21, 2026
9bd47e0
docs(deploy): name the check that actually refuses prefix caching wit…
y-null Sep 21, 2026
9f58ff1
fix(npu): migrate the fixed-KV decode backend the 910B K-step capture…
y-null Sep 21, 2026
7061265
fix(npu): skip the 910B rejection-sampler warmup unconditionally
y-null Sep 21, 2026
2bce893
docs(config): correct the talker_frames_per_step comment on stage wor…
y-null Sep 21, 2026
436cb68
docs(npu): tighten the comments added by the K-step fixes
y-null Sep 21, 2026
6e1f366
style: satisfy pre-commit across the branch
y-null Sep 22, 2026
00e9ee0
docs(minicpmo_4_5): align the comments with the code and drop dead knobs
y-null Sep 22, 2026
3de892c
refactor(npu): drop the fixed-KV decode context-length switch
y-null Sep 22, 2026
ab8c2e6
refactor(npu): name the attention modules after what they do
y-null Sep 22, 2026
7e9ba1a
refactor(npu): make the static-shape decode defaults code constants
y-null Sep 22, 2026
f3b962d
[Style][Ascend] Wrap over-long lines in static_shape_backend to satis…
y-null Sep 22, 2026
522b3ad
[Clean][Ascend][TTS] Drop fused top-k/top-p sampling path and stale e…
y-null Sep 22, 2026
c7f7263
fix(minicpmo_4_5): scope the NPU-only stage overrides to the NPU plat…
y-null Sep 22, 2026
1dcde09
docs(minicpmo_4_5): trim the NPU-scoping comments to what the code needs
y-null Sep 22, 2026
79f1712
fix(config): record the K step through the NPU overlay
y-null Sep 22, 2026
b804b9e
fix(minicpmo_4_5): rebuild the codec sampler's device state per duple…
y-null Sep 22, 2026
ee12b1a
fix(minicpmo_4_5): wire the duplex turn-end EOS window into the K-ste…
y-null Sep 22, 2026
3f23cff
fix(minicpmo_4_5): feed the real codec history to streaming prompt re…
y-null Sep 22, 2026
8d61b99
fix(minicpmo_4_5): derive the Talker stop id from the armed head, not…
y-null Sep 22, 2026
dfda666
fix(minicpmo_4_5): scope the NPU-only stage overrides to the NPU plat…
y-null Sep 22, 2026
2ff445b
docs(minicpmo_4_5): trim the NPU-scoping comments to what the code needs
y-null Sep 22, 2026
492659a
fix(config): record the K step through the NPU overlay
y-null Sep 22, 2026
8207eb0
fix(minicpmo_4_5): rebuild the codec sampler's device state per duple…
y-null Sep 22, 2026
2449c9d
fix(minicpmo_4_5): wire the duplex turn-end EOS window into the K-ste…
y-null Sep 22, 2026
209558f
fix(minicpmo_4_5): feed the real codec history to streaming prompt re…
y-null Sep 22, 2026
d95250a
fix(minicpmo_4_5): derive the Talker stop id from the armed head, not…
y-null Sep 22, 2026
d96289e
docs(deploy): drop the unverified PIECEWISE number from the cudagraph…
y-null Sep 22, 2026
83601d1
fix(minicpmo_4_5): keep static-shape decode buckets FULL-decode-only
y-null Sep 23, 2026
620e44b
fix(deploy): drop the leftover conflict markers from the prof yaml note
y-null Sep 23, 2026
6a5bd1c
fix(deploy): drop the leftover conflict markers from the main yaml note
y-null Sep 23, 2026
cb78e5d
Merge branch 'main' into feat/port-opt-to-main
y-null Sep 23, 2026
0794b0b
Merge branch 'main' into feat/port-opt-to-main
y-null Sep 24, 2026
1883b4f
fix(minicpmo_4_5): forward sample eligibility and honor deploy/reques…
y-null Sep 24, 2026
58773b6
Merge branch 'main' into feat/port-opt-to-main
y-null Sep 24, 2026
80faf5d
Merge remote-tracking branch 'origin/feat/port-opt-to-main' into feat…
y-null Sep 24, 2026
1b4cd23
fix(minicpmo_4_5): drop dead K-step RNG draft; forward _batch_stop_lo…
y-null Sep 24, 2026
20e5b1f
Merge remote-tracking branch 'origin/feat/port-opt-to-main' into feat…
y-null Sep 24, 2026
cf447c1
fix(minicpmo_4_5): propagate request min_tokens into K-step state; ke…
y-null Sep 24, 2026
75da97c
Merge remote-tracking branch 'up/main' (ed5fc2b83) into feat/port-opt…
y-null Sep 24, 2026
839bf4a
Merge branch 'main' into feat/port-opt-to-main
y-null Sep 24, 2026
7f5f5b3
Merge branch 'main' into feat/port-opt-to-main
y-null Sep 25, 2026
cdbe8d6
fix(minicpmo_4_5): align the K-step codec filtering with the single-f…
y-null Sep 25, 2026
61cf0d9
fix(minicpmo_4_5): mirror the remaining engine sampling filters in th…
y-null Sep 25, 2026
2f74c09
style: satisfy ruff in the MiniCPM-o Talker codec path
y-null Sep 25, 2026
86b312b
fix(minicpmo_4_5): drop the K-step allowed_token_ids / logit_bias fil…
y-null Sep 25, 2026
7c28f57
Fix two review findings in the K-step request param merge
y-null Sep 28, 2026
7ad0743
Compat: drive vllm 0.29 and 0.30 from one omni build
y-null Sep 28, 2026
2661dcc
Merge branch 'main' into feat/port-opt-to-main
y-null Sep 28, 2026
1c7bb89
Merge branch 'main' into feat/port-opt-to-main
y-null Sep 28, 2026
fd78f82
fix(minicpmo): address review comments on codec sampling and duplex m…
y-null Sep 29, 2026
3257f18
Merge up/main: resolve CFM graph conflicts (whole-euler x bucketing/m…
y-null Sep 29, 2026
1cca045
fix(minicpmo_4_5): restore the CPU codec transport contract in make_o…
y-null Sep 29, 2026
e77b5a3
fix(minicpmo): route codec stop ids through the device state
y-null Sep 29, 2026
93168b5
fix(minicpmo_4_5): use PIECEWISE cudagraphs to keep vllm 0.30.0 runnable
y-null Sep 29, 2026
bfd36cc
fix(minicpmo_4_5_prof): use PIECEWISE cudagraphs to keep vllm 0.30.0 …
y-null Sep 29, 2026
a45528e
fix(minicpmo_4_5): stage0 PIECEWISE + stage1 FULL recovers c1 and bea…
y-null Sep 29, 2026
19105e8
fix(minicpmo_4_5_prof): align stage 1 cudagraph_mode with minicpmo_4_…
y-null Sep 29, 2026
538347b
[test][Ascend] Expect PIECEWISE stage-0 graphs under vLLM 0.30.0
y-null Sep 29, 2026
a9cee1e
fix(review amy-why-3459): P2-1/P2-2/P2-3/P3-5/P3-6 + claims-2 code fixes
y-null Sep 29, 2026
9ad237e
[Deploy][MiniCPM-o 4.5] Turn the stage-2 replicas profile into a base…
y-null Sep 29, 2026
a93cbd6
Merge branch 'main' into feat/port-opt-to-main
amy-why-3459 Sep 29, 2026
1cb0958
style: fix pre-commit violations (ruff format)
y-null Sep 29, 2026
893094c
Merge up/main into feat/port-opt-to-main: resolve minicpmo_4_5_omni_t…
y-null Sep 30, 2026
5bc44ed
Fix ruff-format blank lines in minicpmo_4_5_omni_tts.py
y-null Sep 30, 2026
d03dca0
Fix num_output_tokens AttributeError in request_max_tokens_remaining
y-null Sep 30, 2026
72a6160
Merge #7929 (Talker multi-frame decode, head d03dca05c) into main
BeatSeat Oct 2, 2026
618ba5d
[Perf][Core] Opt-in in-place growth for CUMULATIVE stage outputs
BeatSeat Oct 2, 2026
ba1e195
[Perf][Duplex] Framework hooks for pacing, unit continuation, and sta…
BeatSeat Oct 2, 2026
91097ab
[Perf][Worker] Multi-frame decode hook for Talker K-step
BeatSeat Oct 2, 2026
f602f4b
[Perf][MiniCPM-o] Talker K-step multi-frame decode on CUDA
BeatSeat Oct 2, 2026
d6f62a9
[Perf][MiniCPM-o] Replay Talker K-step per-frame sampling tail as CUD…
BeatSeat Oct 2, 2026
171654b
[Perf][MiniCPM-o] Stage 0 streaming audio encoder batching, in-place …
BeatSeat Oct 2, 2026
e6fcbc7
[Perf][MiniCPM-o] Stage 0 fused vision encoder and CUDA graphs
BeatSeat Oct 2, 2026
11565f2
[Perf][MiniCPM-o] Code2Wav Whole-Euler slot pool and fused DiT body
BeatSeat Oct 2, 2026
94be6ab
[Perf][MiniCPM-o] Code2Wav exact-shape CUDA graphs for flow encoder, …
BeatSeat Oct 2, 2026
fbc0446
[Bugfix][MiniCPM-o] HiFT zero-width mel safeguard and Stage 2 memory …
BeatSeat Oct 2, 2026
78e087a
[Config][MiniCPM-o] Consolidated single recommended deployment profile
BeatSeat Oct 2, 2026
a6453d9
style: fix import sorting in deadline scheduler tests
BeatSeat Oct 2, 2026
64fe261
style: ruff format cleanup
BeatSeat Oct 2, 2026
7f12e09
fix(deploy): consolidate multi-modal Stage 0 vision and audio CUDA gr…
BeatSeat Oct 2, 2026
dbf79f9
fix(deploy): remove obsolete stats from codec_deadline
BeatSeat Oct 2, 2026
266b4ec
test(deploy): accept optional step_tiers in codec_deadline deploy config
BeatSeat Oct 2, 2026
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
52 changes: 52 additions & 0 deletions tests/config/test_config_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,58 @@ def test_load_minicpmo_default_deploy_config(self):
assert stages[1].yaml_extras["default_sampling_params"]["stop_token_ids"] == [6561]
assert "codec_sampling_params" not in stages[1].yaml_engine_args

@pytest.mark.parametrize("platform", ["cuda", "npu"])
def test_minicpmo_talker_multi_frame_is_npu_scoped(self, platform: str):
"""Stage 1's K-frame decode needs the NPU worker to inject constant
continue drafts, so the deploy block that arms it must not sit in the
platform-shared stage.

On NPU the ``speculative_config`` is a fingerprint: the worker replays
the decode graph and vLLM's n-gram proposer is never consulted. On CUDA
the same block hands the Talker to the real proposer plus the rejection
sampler, which asserts on its codec ids (device-side assert out of
``get_token_bin_counts_and_mask``).

Stage 0's prefix caching is NPU-scoped for the same reason: a CUDA
stage-0 prefix hit hands the Thinker an empty tts handoff, and the
Talker then indexes past the codec vocab.
"""
deploy = _apply_platform_overrides(
load_deploy_config(Path(get_deploy_config_path("minicpmo_4_5.yaml"))),
platform=platform,
)
# Check the deploy-side stop id before the merge: afterwards it is
# unioned with the pipeline's codec-EOS default.
deploy_stage1 = next(stage for stage in deploy.stages if stage.stage_id == 1)
stages = merge_pipeline_deploy(resolve_pipeline_config("minicpmo_4_5"), deploy)

if platform == "npu":
assert deploy_stage1.default_sampling_params["stop_token_ids"] == [1]
# The armed head ends on 1; the union keeps the codec EOS inert
# (the collapsed head cannot sample it).
assert stages[1].yaml_extras["default_sampling_params"]["stop_token_ids"] == [1, 6561]
assert stages[0].yaml_engine_args["enable_prefix_caching"] is True
assert stages[1].yaml_engine_args["speculative_config"] == {
"method": "ngram",
"num_speculative_tokens": 7,
"prompt_lookup_min": 1,
"prompt_lookup_max": 1,
}
assert stages[1].yaml_engine_args["async_scheduling"] is False
assert [stage.yaml_engine_args["max_num_seqs"] for stage in stages] == [8, 8, 8]
else:
assert "stop_token_ids" not in (deploy_stage1.default_sampling_params or {})
# Without the block the head is the full codec vocabulary, so the
# codec EOS is the only stop id -- the marker would end a request
# on an ordinary codec id.
assert stages[1].yaml_extras["default_sampling_params"]["stop_token_ids"] == [6561]
# Either absent (falls back to the top-level false) or explicitly
# false; the regression this guards against is true.
assert stages[0].yaml_engine_args.get("enable_prefix_caching") is not True
assert "speculative_config" not in stages[1].yaml_engine_args
assert stages[1].yaml_engine_args["async_scheduling"] is True
assert [stage.yaml_engine_args["max_num_seqs"] for stage in stages] == [4, 4, 4]

@pytest.mark.parametrize(
("filename", "stage0_devices", "stage1_devices", "stage2_devices", "stage1_replicas"),
[
Expand Down
277 changes: 277 additions & 0 deletions tests/core/sched/test_codec_deadline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project

"""Deadline batching policy for the codec stage (``additional_config.codec_deadline``), pure Python."""

from __future__ import annotations

import pytest
import torch
from vllm_omni.core.sched.codec_deadline import (
ChunkMeta,
CodecDeadlineConfig,
CodecDeadlinePolicy,
LateBreaker,
ReadyChunk,
StepTimeModel,
StreamLedger,
build_codec_deadline_policy,
graph_step_tiers,
seed_step_s,
)

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

NOW = 100.0


@pytest.fixture(autouse=True)
def _default_graph_grid(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("VLLM_OMNI_CFM_GRAPH_GRID", raising=False)


def _meta(chunk_seq: int = 3, *, frames: int = 25, last: bool = False, turn_end: bool = False, epoch=0, cache=0):
return ChunkMeta(
chunk_seq=chunk_seq,
cache_epoch=cache,
last_chunk=last,
turn_end=turn_end,
codec_chunk_frames=frames,
duplex_epoch=epoch,
)


def _policy(**config: object) -> CodecDeadlinePolicy:
return CodecDeadlinePolicy(CodecDeadlineConfig(enabled=True, **config))


def _stream(policy: CodecDeadlinePolicy, rid: str, *, play_end: float, anchor: float = 90.0, audio_s: float = 20.0):
policy.ledgers[rid] = StreamLedger(key=(0, 0), anchor=anchor, play_end=play_end, audio_s=audio_s, d1=1.0)
return ReadyChunk(rid, _meta())


def test_config_defaults_validation_and_parsing() -> None:
config = CodecDeadlineConfig()
assert config.enabled is False
assert (config.margin_s, config.max_hold_s, config.max_rows, config.eager_rows) == (0.1, 0.35, 8, 4)
assert CodecDeadlineConfig.from_additional_config({}) is None
assert CodecDeadlineConfig.from_additional_config(None) is None
parsed = CodecDeadlineConfig.from_additional_config({"codec_deadline": {"enabled": True, "max_rows": 16}})
assert parsed is not None and parsed.enabled and parsed.max_rows == 16
with pytest.raises(ValueError, match="unknown keys"):
CodecDeadlineConfig.from_additional_config({"codec_deadline": {"enabld": True}})
# The client buffer and the graph tiers are not configured here any more.
for removed in ("prebuffer_s", "step_tiers", "step_seed_s", "trim_split_batches"):
with pytest.raises(ValueError, match="unknown keys"):
CodecDeadlineConfig.from_additional_config({"codec_deadline": {removed: 1}})
with pytest.raises(ValueError, match="must be a boolean"):
CodecDeadlineConfig(enabled=1) # type: ignore[arg-type]


def test_chunk_meta_reads_tensor_fields_and_ignores_tts_is_last_chunk() -> None:
info = {
"meta": {
"chunk_seq": torch.tensor(4),
"cache_epoch": torch.tensor([1]),
"last_chunk": torch.tensor(False),
"turn_end": False,
"codec_chunk_frames": 28,
"duplex_epoch": 2,
# True on every native duplex unit (flush_pending): not a last chunk.
"tts_is_last_chunk": True,
}
}
meta = ChunkMeta.from_info(info)
assert meta == ChunkMeta(4, 1, False, False, 28, 2)
assert ChunkMeta.from_info({}) is None
assert ChunkMeta.from_info({"meta": {"request_id": "x"}}) is None


@pytest.mark.parametrize(
"meta",
[
_meta(0),
_meta(last=True),
_meta(turn_end=True),
_meta(frames=0),
_meta(epoch=None),
_meta(cache=1), # another turn than the ledger
None,
],
)
def test_eager_chunks(meta) -> None:
policy = _policy()
_stream(policy, "a", play_end=NOW + 10)
assert policy._eager(ReadyChunk("a", meta), NOW) is True


def test_a_continuation_with_a_ledger_is_paced_but_not_without_one() -> None:
policy = _policy()
chunk = _stream(policy, "a", play_end=NOW + 10)
assert policy._eager(chunk, NOW) is False
assert policy._eager(ReadyChunk("b", _meta()), NOW) is True
# The client ran dry long ago: a new segment, go now.
policy.ledgers["a"].play_end = NOW - 2.0
assert policy._eager(chunk, NOW) is True


def test_ledger_anchors_buffers_once_and_reanchors_late_chunks() -> None:
policy = _policy()
policy.on_scheduled("a", _meta(0), NOW)
policy.on_output("a", 0.12, NOW)
ledger = policy.ledgers["a"]
assert ledger.play_end == pytest.approx(NOW + 0.5 + 0.12)
assert (ledger.anchor, ledger.d1) == (NOW, 0.12)
# Arrives 0.3 s after the client ran out: re-anchor at arrival.
policy.on_scheduled("a", _meta(1), NOW + 0.9)
policy.on_output("a", 1.0, NOW + 0.92)
assert ledger.late_s == pytest.approx(0.3)
assert ledger.play_end == pytest.approx(NOW + 1.92)
assert ledger.audio_s == pytest.approx(1.12)
# The turn ends: the next turn re-anchors without the buffer.
policy.on_scheduled("a", _meta(2, last=True), NOW + 1.5)
policy.on_output("a", 0.5, NOW + 1.5)
assert "a" not in policy.ledgers
policy.on_scheduled("a", _meta(0, cache=1), NOW + 5)
policy.on_output("a", 1.0, NOW + 5)
assert policy.ledgers["a"].play_end == pytest.approx(NOW + 6.0)
policy.forget("a")
assert "a" not in policy.ledgers and "a" not in policy.buffered


def test_rate_deadline_is_tighter_after_a_long_first_chunk() -> None:
policy = _policy(max_hold_s=10.0)
policy.ledgers["a"] = StreamLedger(key=(0, 0), anchor=NOW, play_end=NOW + 1.34, audio_s=0.84, d1=0.84)
chunk = ReadyChunk("a", _meta(1, frames=25))
# rate: NOW + 1.1 * (0.84 + 1.0 - 0.84) = NOW + 1.1 < zero-stall NOW + 1.34;
# minus the margin, its own one-row step and an eager step that may run first.
release = policy.release_by(chunk, NOW, 1)
assert release == pytest.approx(NOW + 1.1 - 0.1 - seed_step_s(1) - seed_step_s(4))


def test_eager_step_takes_only_rows_due_within_a_step() -> None:
policy = _policy()
ready = [
ReadyChunk("e", _meta(0)),
_stream(policy, "soon", play_end=NOW + 0.5), # release 100.1 <= now + T(1)
_stream(policy, "slack", play_end=NOW + 10), # release by max hold: 100.35
]
plan = policy.plan(ready, NOW)
assert plan.trigger == "eager"
assert plan.released == ["e", "soon"]
assert plan.held == {"slack"}
assert policy.next_release_in(NOW) == pytest.approx(0.35)


def _two_due_three_slack(policy: CodecDeadlinePolicy) -> list[ReadyChunk]:
ready = [_stream(policy, f"due{i}", play_end=NOW + 0.3) for i in range(2)]
ready += [_stream(policy, f"slack{i}", play_end=NOW + 10 + i) for i in range(3)]
return ready


def test_deadline_step_fills_the_graph_tier(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("VLLM_OMNI_CFM_GRAPH_GRID", "1,4,8,16,20")
policy = _policy()
plan = policy.plan(_two_due_three_slack(policy), NOW)
assert plan.trigger == "deadline"
assert len(plan.released) == 4 # 2 due -> tier 4
assert plan.released[:2] == ["due0", "due1"]
assert len(plan.held) == 1


def test_deadline_step_does_not_fill_while_a_first_chunk_is_pending(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("VLLM_OMNI_CFM_GRAPH_GRID", "1,4,8,16,20")
policy = _policy()
plan = policy.plan(_two_due_three_slack(policy), NOW, onset_pending=True)
assert plan.trigger == "deadline"
assert plan.released == ["due0", "due1"]
assert len(plan.held) == 3


def test_graph_step_tiers_follow_the_code2wav_grid(monkeypatch: pytest.MonkeyPatch) -> None:
assert graph_step_tiers(8) == [1, 2, 4, 8] # the Code2Wav default: powers of two
monkeypatch.setenv("VLLM_OMNI_CFM_GRAPH_GRID", "1,4,8,16,20")
assert graph_step_tiers(8) == [1, 4, 8]
assert graph_step_tiers(16) == [1, 4, 8, 16]
monkeypatch.setenv("VLLM_OMNI_CFM_GRAPH_GRID", "3,5")
assert graph_step_tiers(8) == [3, 5, 8]


def test_holds_everything_until_a_release_time() -> None:
policy = _policy()
ready = [_stream(policy, "a", play_end=NOW + 10), _stream(policy, "b", play_end=NOW + 10)]
plan = policy.plan(ready, NOW)
assert plan.trigger == "hold"
assert plan.released == [] and plan.held == {"a", "b"}
assert policy.next_release_in(NOW) == pytest.approx(0.35)
# The max hold expires: both go.
plan = policy.plan(ready, NOW + 0.36)
assert plan.trigger == "deadline" and set(plan.released) == {"a", "b"}


def test_max_rows_caps_a_rows_trigger() -> None:
policy = _policy(max_rows=8)
ready = [_stream(policy, f"r{i}", play_end=NOW + 10 + i) for i in range(10)]
plan = policy.plan(ready, NOW)
assert plan.trigger == "rows" and len(plan.released) == 8
assert len(plan.held) == 2


def test_step_time_model_ewma() -> None:
model = StepTimeModel((1, 4, 8), 0.5)
assert model.mean == pytest.approx([seed_step_s(1), seed_step_s(4), seed_step_s(8)])
model.mean = [0.1, 0.2, 0.3]
assert model.tier_capacity(2) == 4 and model.tier_capacity(30) == 8
assert model.estimate(1) == pytest.approx(0.1)
model.observe(1, 0.2)
assert model.mean[0] == pytest.approx(0.15)
assert model.mad[0] == pytest.approx(0.05)
assert model.estimate(1) == pytest.approx(0.15 + 1.5 * 0.05)
model.observe(0, 1.0)
assert model.mean[0] == pytest.approx(0.15)


def test_breaker_opens_after_late_held_chunks_and_cools_off() -> None:
breaker = LateBreaker(window=10, late_frac=0.1, cooloff_s=5.0)
breaker.record(True, NOW)
assert not breaker.is_open(NOW)
breaker.record(True, NOW)
assert breaker.is_open(NOW + 1) and breaker.opens == 1
assert not breaker.is_open(NOW + 5.1)

policy = _policy(breaker_window=2, breaker_late_frac=0.4)
for step in range(2):
rid = "a"
_stream(policy, rid, play_end=NOW - 0.5)
policy.held_once.add(rid)
policy.on_scheduled(rid, _meta(3 + step), NOW)
policy.on_output(rid, 1.0, NOW)
assert policy.breaker.is_open(NOW)
assert policy._eager(_stream(policy, "b", play_end=NOW + 10), NOW) is True


def test_late_held_chunks_are_counted_by_the_trigger_that_released_them() -> None:
policy = _policy(stats=True)
for trigger in ("eager", "deadline", "deadline"):
_stream(policy, "a", play_end=NOW - 0.5)
policy.held_once.add("a")
policy._last_trigger = trigger
policy.on_scheduled("a", _meta(3), NOW)
policy.on_output("a", 1.0, NOW)
assert policy._stats.late == {"eager": 1, "deadline": 2}


@pytest.mark.parametrize(
("kwargs", "enabled"),
[
({"async_scheduling": False, "native_data_plane": False, "idle_wait_s": 0.05}, True),
({"async_scheduling": True, "native_data_plane": False, "idle_wait_s": 0.05}, False),
({"async_scheduling": False, "native_data_plane": True, "idle_wait_s": 0.05}, False),
({"async_scheduling": False, "native_data_plane": False, "idle_wait_s": 0.0}, False),
],
)
def test_build_requires_sync_scheduling_the_chunk_adapter_and_the_idle_park(kwargs, enabled) -> None:
policy = build_codec_deadline_policy({"codec_deadline": {"enabled": True}}, **kwargs)
assert (policy is not None) is enabled
assert build_codec_deadline_policy({"codec_deadline": {"enabled": False}}, **kwargs) is None
assert build_codec_deadline_policy({}, **kwargs) is None
Loading
Loading