[Core][Frontend] Unified Full-duplex Framework (#7413)

Signed-off-by: chickeyton <ngton2014@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
chickeytonandClaude Opus 5 authored and GitHub committed 2026-09-16 15:35:36 +08:00
1 parent 9005d78903
commit 99ff4f3070
182 files changed
+26951 -34743

No files matched your search

+17 -21
View File
@@ -23,7 +23,7 @@ def _minicpmo_duplex_policy_case(
MiniCPMO45OmniForConditionalGeneration,
)
session_key = ("sid-policy", 1)
session_key = "sid-policy"
model = MiniCPMO45OmniForConditionalGeneration.__new__(MiniCPMO45OmniForConditionalGeneration)
model.model_stage = "llm"
model._minicpmo45_native_duplex_token_ids_cache = {
@@ -35,8 +35,7 @@ def _minicpmo_duplex_policy_case(
row = DuplexSamplingRow(
row_idx=0,
request_id="req-policy",
session_id=session_key[0],
incarnation=session_key[1],
session_id=session_key,
seq=3,
payload=payload,
max_tokens=20,
@@ -63,13 +62,12 @@ def test_minicpmo_model_hook_owns_duplex_sampling_rows_and_force_listen():
"tts_bos_token_id": 8,
"turn_eos_token_id": 9,
}
model._minicpmo45_duplex_data_plane_helper = SimpleNamespace(sessions={("sid-hook", 2): state})
model._minicpmo45_duplex_data_plane_helper = SimpleNamespace(sessions={"sid-hook": state})
logits = torch.zeros((1, 16), dtype=torch.float32)
row = DuplexSamplingRow(
row_idx=0,
request_id="req-hook",
session_id="sid-hook",
incarnation=2,
seq=3,
payload={"force_listen": True, "is_speech": True},
max_tokens=20,
@@ -78,7 +76,7 @@ def test_minicpmo_model_hook_owns_duplex_sampling_rows_and_force_listen():
model.prepare_duplex_sampling(logits, SimpleNamespace(), (row,))
assert model._minicpmo45_active_duplex_rows == [0]
assert model._minicpmo45_duplex_row_sessions == {0: ("sid-hook", 2)}
assert model._minicpmo45_duplex_row_sessions == {0: "sid-hook"}
assert model._minicpmo45_duplex_row_payloads == {0: row.payload}
assert model._minicpmo45_duplex_row_max_tokens == {0: 20}
assert logits[0, listen_id].item() == 0.0
@@ -152,7 +150,7 @@ def test_minicpmo_model_hook_old_response_output_does_not_clear_pending_speech_c
pending_speech_context=True,
)
model, row = _minicpmo_duplex_policy_case(state, {"is_speech": True})
model._minicpmo45_duplex_row_sessions = {0: (row.session_id, row.incarnation)}
model._minicpmo45_duplex_row_sessions = {0: row.session_id}
model._minicpmo45_duplex_row_payloads = {0: row.payload}
model._record_minicpmo45_duplex_terminator(
@@ -173,7 +171,7 @@ def test_minicpmo_model_hook_new_response_output_clears_pending_speech_context()
pending_speech_context=True,
)
model, row = _minicpmo_duplex_policy_case(state, {"is_speech": False})
model._minicpmo45_duplex_row_sessions = {0: (row.session_id, row.incarnation)}
model._minicpmo45_duplex_row_sessions = {0: row.session_id}
model._minicpmo45_duplex_row_payloads = {0: row.payload}
model._record_minicpmo45_duplex_terminator(
@@ -195,7 +193,7 @@ def test_minicpmo_model_hook_empty_speak_envelope_preserves_pending_speech_conte
pending_speech_response_open=False,
)
model, row = _minicpmo_duplex_policy_case(state, {"is_speech": False})
model._minicpmo45_duplex_row_sessions = {0: (row.session_id, row.incarnation)}
model._minicpmo45_duplex_row_sessions = {0: row.session_id}
model._minicpmo45_duplex_row_payloads = {0: row.payload}
token_ids = {
"listen_token_id": 7,
@@ -236,7 +234,7 @@ def test_minicpmo_model_hook_second_new_response_step_is_not_forced_to_listen():
original_logits = logits.clone()
model.prepare_duplex_sampling(logits, SimpleNamespace(), (row,))
model._minicpmo45_duplex_row_sessions = {0: (row.session_id, row.incarnation)}
model._minicpmo45_duplex_row_sessions = {0: row.session_id}
model._minicpmo45_duplex_row_payloads = {0: row.payload}
model._record_minicpmo45_duplex_terminator(
0,
@@ -269,7 +267,7 @@ def test_minicpmo_model_hook_same_speech_row_second_step_does_not_rearm_pending_
logits[0, 10] = 20.0
model.prepare_duplex_sampling(logits, SimpleNamespace(), (row,))
model._minicpmo45_duplex_row_sessions = {0: (row.session_id, row.incarnation)}
model._minicpmo45_duplex_row_sessions = {0: row.session_id}
model._minicpmo45_duplex_row_payloads = {0: row.payload}
model._record_minicpmo45_duplex_terminator(
0,
@@ -339,7 +337,6 @@ def test_generic_ar_runner_builds_typed_duplex_sampling_rows():
"duplex": {
"data_plane": True,
"session_id": "sid-runner-hook",
"incarnation": 4,
"seq": 4,
"payload": {"is_speech": True},
}
@@ -359,7 +356,6 @@ def test_generic_ar_runner_builds_typed_duplex_sampling_rows():
assert rows[0].row_idx == 0
assert rows[0].request_id == "req-duplex"
assert rows[0].session_id == "sid-runner-hook"
assert rows[0].incarnation == 4
assert rows[0].seq == 4
assert rows[0].payload == {"is_speech": True}
assert rows[0].max_tokens == 32
@@ -458,13 +454,13 @@ def test_generic_ar_runner_has_no_minicpmo_sampler_state_or_typeerror_probe():
assert 'if "duplex_rows" not in str(exc)' not in source
def test_minicpmo_model_cleans_incarnation_state_when_request_finishes():
def test_minicpmo_model_cleans_session_state_when_request_finishes():
from vllm_omni.model_executor.models.minicpmo_4_5.minicpmo_4_5_omni import (
MiniCPMO45OmniForConditionalGeneration,
)
request_id = "duplex-sid-cleanup-i3-e0-stage0"
session_key = ("sid-cleanup", 3)
session_key = "sid-cleanup"
model = MiniCPMO45OmniForConditionalGeneration.__new__(MiniCPMO45OmniForConditionalGeneration)
model.model = SimpleNamespace()
model._minicpmo45_duplex_data_plane_helper = SimpleNamespace(sessions={session_key: object()})
@@ -1731,8 +1727,8 @@ def test_minicpmo_stage0_native_sampler_penalizes_text_from_prior_chunk():
model = MiniCPMO45OmniForConditionalGeneration.__new__(MiniCPMO45OmniForConditionalGeneration)
model.model_stage = "llm"
model.thinker = SimpleNamespace(get_tokenizer=lambda: _Tokenizer())
session_key = ("sid-cross-chunk-repetition", 0)
state = _MiniCPMO45Stage0SessionState(session_id=session_key[0])
session_key = "sid-cross-chunk-repetition"
state = _MiniCPMO45Stage0SessionState(session_id=session_key)
state.generated_tokens = [198] * 8
model._minicpmo45_duplex_data_plane_helper = SimpleNamespace(sessions={session_key: state})
model._minicpmo45_duplex_row_sessions = {0: session_key}
@@ -1791,8 +1787,8 @@ def test_minicpmo_stage0_native_sampler_matches_official_negative_logit_penalty(
model = MiniCPMO45OmniForConditionalGeneration.__new__(MiniCPMO45OmniForConditionalGeneration)
model.model_stage = "llm"
model.thinker = SimpleNamespace(get_tokenizer=lambda: _Tokenizer())
session_key = ("sid-negative-logit-repetition", 0)
state = _MiniCPMO45Stage0SessionState(session_id=session_key[0])
session_key = "sid-negative-logit-repetition"
state = _MiniCPMO45Stage0SessionState(session_id=session_key)
repeated = 198
alternative = 1234
state.generated_tokens = [repeated]
@@ -1827,8 +1823,8 @@ def test_minicpmo_stage0_records_bounded_model_policy_history():
MiniCPMO45OmniForConditionalGeneration,
)
session_key = ("sid-text-history", 0)
state = _MiniCPMO45Stage0SessionState(session_id=session_key[0])
session_key = "sid-text-history"
state = _MiniCPMO45Stage0SessionState(session_id=session_key)
model = MiniCPMO45OmniForConditionalGeneration.__new__(MiniCPMO45OmniForConditionalGeneration)
model._minicpmo45_duplex_data_plane_helper = SimpleNamespace(sessions={session_key: state})
model._minicpmo45_duplex_row_sessions = {0: session_key}