[Model] Preserve MiniCPM-o Talker full attention until capacity (#6799)
Signed-off-by: natureofnature <wzliu@connect.hku.hk> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
1 parent
715b8b8746
commit
ff6e906a25
12 files changed
+913
-108
No files matched your search
@@ -19,6 +19,9 @@ from vllm.v1.core.sched.output import SchedulerOutput
|
||||
from vllm.v1.outputs import ModelRunnerOutput
|
||||
from vllm.v1.request import Request, RequestStatus, StreamingUpdate
|
||||
from vllm_omni.core.sched.omni_ar_scheduler import OmniARAsyncScheduler, OmniARScheduler
|
||||
from vllm_omni.distributed.omni_connectors.transfer_adapter.chunk_transfer_adapter import (
|
||||
OmniChunkTransferAdapter,
|
||||
)
|
||||
|
||||
# isort: on
|
||||
|
||||
@@ -62,6 +65,43 @@ def _make_update(prompt_token_ids: list[int] | None = None) -> StreamingUpdate:
|
||||
)
|
||||
|
||||
|
||||
def _make_talker_adapter(
|
||||
*,
|
||||
max_model_len: int = 100,
|
||||
recompute_on_capacity: bool = True,
|
||||
) -> OmniChunkTransferAdapter:
|
||||
adapter = OmniChunkTransferAdapter.__new__(OmniChunkTransferAdapter)
|
||||
adapter.receives_chunks = False
|
||||
adapter._max_model_len = max_model_len
|
||||
adapter._streaming_prompt_previous_chunks = 1
|
||||
adapter._streaming_prompt_recompute_on_capacity = recompute_on_capacity
|
||||
adapter._streaming_condition_lengths = {}
|
||||
adapter._streaming_condition_seqs = {}
|
||||
adapter.segment_finished_requests = set()
|
||||
adapter.requests_num_chunks_sent = {}
|
||||
return adapter
|
||||
|
||||
|
||||
def _make_talker_update(
|
||||
condition_len: int,
|
||||
*,
|
||||
reserve: int = 10,
|
||||
condition_seq: int | None,
|
||||
) -> StreamingUpdate:
|
||||
update = _make_update([0] * condition_len)
|
||||
update.model_intermediate_buffer = {
|
||||
"native_duplex": True,
|
||||
"ids": {"prompt": [1]},
|
||||
"meta": {
|
||||
"next_stage_prompt_len": condition_len,
|
||||
"next_stage_generation_tokens": reserve,
|
||||
},
|
||||
}
|
||||
if condition_seq is not None:
|
||||
update.model_intermediate_buffer["meta"]["streaming_condition_seq"] = condition_seq
|
||||
return update
|
||||
|
||||
|
||||
def _run_resumable_segment_stop(
|
||||
session: Request,
|
||||
*,
|
||||
@@ -538,6 +578,288 @@ def test_explicit_model_intermediate_prompt_replacement_releases_cache_and_water
|
||||
sched.encoder_cache_manager.free.assert_called_once_with(session)
|
||||
|
||||
|
||||
def test_talker_capacity_exact_fit_extends_from_declared_length_without_ids_prompt() -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
adapter = _make_talker_adapter(max_model_len=100)
|
||||
sched.chunk_transfer_adapter = adapter
|
||||
session = _make_request()
|
||||
session.external_req_id = "external-capacity-exact-fit"
|
||||
session.prompt_token_ids = [0] * 70
|
||||
session._all_token_ids.clear()
|
||||
session._all_token_ids.extend(session.prompt_token_ids)
|
||||
session._output_token_ids.clear()
|
||||
session.append_output_token_ids([7, 8, 9, 10])
|
||||
session.num_prompt_tokens = 70
|
||||
session.num_computed_tokens = 74
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
adapter._streaming_condition_lengths[session.request_id] = 20
|
||||
adapter._streaming_condition_seqs[session.request_id] = 0
|
||||
update = _make_talker_update(16, condition_seq=1)
|
||||
update.model_intermediate_buffer["ids"] = {"tts": [1, 2, 3]}
|
||||
|
||||
sched._update_request_as_session(session, update)
|
||||
|
||||
assert session.num_computed_tokens == 74
|
||||
assert session.num_prompt_tokens == 90
|
||||
assert list(session._all_token_ids[-20:]) == [7, 8, 9, 10] + [0] * 16
|
||||
assert update.model_intermediate_buffer["meta"] == {
|
||||
"next_stage_prompt_len": 16,
|
||||
"next_stage_generation_tokens": 10,
|
||||
"streaming_condition_seq": 1,
|
||||
"streaming_prompt_recompute": False,
|
||||
}
|
||||
assert adapter._streaming_condition_lengths[session.request_id] == 16
|
||||
assert adapter._streaming_condition_seqs[session.request_id] == 1
|
||||
assert session._omni_segment_generation == 1
|
||||
assert session.status == RequestStatus.WAITING
|
||||
assert sched.num_waiting_for_streaming_input == 0
|
||||
sched._free_request_blocks.assert_not_called()
|
||||
sched.encoder_cache_manager.free.assert_not_called()
|
||||
|
||||
|
||||
def test_talker_first_update_seeds_sender_condition_tracking() -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
adapter = _make_talker_adapter(max_model_len=100)
|
||||
sched.chunk_transfer_adapter = adapter
|
||||
session = _make_request()
|
||||
session.external_req_id = "external-capacity-first-update"
|
||||
session.prompt_token_ids = [0] * 20
|
||||
session._all_token_ids.clear()
|
||||
session._all_token_ids.extend(session.prompt_token_ids)
|
||||
session._output_token_ids.clear()
|
||||
session.append_output_token_ids([7, 8, 9])
|
||||
session.num_prompt_tokens = 20
|
||||
session.num_computed_tokens = 23
|
||||
session.model_intermediate_buffer = {
|
||||
"native_duplex": True,
|
||||
"meta": {
|
||||
"next_stage_prompt_len": 20,
|
||||
"next_stage_generation_tokens": 10,
|
||||
"streaming_condition_seq": 0,
|
||||
},
|
||||
}
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
update = _make_talker_update(16, condition_seq=1)
|
||||
|
||||
sched._update_request_as_session(session, update)
|
||||
|
||||
assert update.model_intermediate_buffer["meta"]["streaming_condition_seq"] == 1
|
||||
assert update.model_intermediate_buffer["meta"]["streaming_prompt_recompute"] is False
|
||||
assert adapter._streaming_condition_lengths[session.request_id] == 16
|
||||
assert adapter._streaming_condition_seqs[session.request_id] == 1
|
||||
assert session.num_computed_tokens == 23
|
||||
assert session.num_prompt_tokens == 39
|
||||
sched._free_request_blocks.assert_not_called()
|
||||
sched.encoder_cache_manager.free.assert_not_called()
|
||||
|
||||
|
||||
def test_talker_first_sliding_update_recomputes_from_seeded_condition() -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
adapter = _make_talker_adapter(recompute_on_capacity=False)
|
||||
sched.chunk_transfer_adapter = adapter
|
||||
session = _make_request()
|
||||
session.external_req_id = "external-sliding-first-update"
|
||||
session.prompt_token_ids = [0] * 20
|
||||
session._all_token_ids.clear()
|
||||
session._all_token_ids.extend(session.prompt_token_ids)
|
||||
session._output_token_ids.clear()
|
||||
previous_codes = [7, 8, 9]
|
||||
session.append_output_token_ids(previous_codes)
|
||||
session.num_prompt_tokens = 20
|
||||
session.num_computed_tokens = 23
|
||||
session.model_intermediate_buffer = {
|
||||
"native_duplex": True,
|
||||
"meta": {
|
||||
"next_stage_prompt_len": 20,
|
||||
"next_stage_generation_tokens": 10,
|
||||
"streaming_condition_seq": 0,
|
||||
},
|
||||
}
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
update = _make_talker_update(16, condition_seq=1)
|
||||
|
||||
sched._update_request_as_session(session, update)
|
||||
|
||||
assert session.num_computed_tokens == 0
|
||||
assert session.num_prompt_tokens == 39
|
||||
assert update.model_intermediate_buffer["ids"]["streaming_prompt_previous_codes"] == previous_codes
|
||||
assert update.model_intermediate_buffer["meta"]["streaming_prompt_recompute"] is True
|
||||
assert adapter._streaming_condition_lengths[session.request_id] == 16
|
||||
assert adapter._streaming_condition_seqs[session.request_id] == 1
|
||||
sched._free_request_blocks.assert_called_once_with(session)
|
||||
sched.encoder_cache_manager.free.assert_called_once_with(session)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("condition_seq", "error"),
|
||||
[
|
||||
(None, "missing streaming_condition_seq"),
|
||||
(0, "expected=1, received=0"),
|
||||
(2, "expected=1, received=2"),
|
||||
],
|
||||
)
|
||||
def test_talker_invalid_condition_sequence_does_not_advance_tracking(mocker, condition_seq, error) -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
adapter = _make_talker_adapter()
|
||||
adapter.record_receive_failure = mocker.MagicMock()
|
||||
adapter._streaming_condition_lengths["req-ar-streaming-test"] = 20
|
||||
adapter._streaming_condition_seqs["req-ar-streaming-test"] = 0
|
||||
sched.chunk_transfer_adapter = adapter
|
||||
session = _make_request()
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
update = _make_talker_update(16, condition_seq=condition_seq)
|
||||
|
||||
sched._update_request_as_session(session, update)
|
||||
|
||||
failure = adapter.record_receive_failure.call_args.args
|
||||
assert failure[0] == session.request_id
|
||||
assert error in failure[1]
|
||||
assert adapter._streaming_condition_lengths[session.request_id] == 20
|
||||
assert adapter._streaming_condition_seqs[session.request_id] == 0
|
||||
assert session.status == RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
|
||||
|
||||
def test_talker_first_update_that_cannot_fit_window_is_request_local(mocker) -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
adapter = _make_talker_adapter()
|
||||
adapter.record_receive_failure = mocker.MagicMock()
|
||||
sched.chunk_transfer_adapter = adapter
|
||||
session = _make_request()
|
||||
session.prompt_token_ids = [0] * 20
|
||||
session._all_token_ids.clear()
|
||||
session._all_token_ids.extend(session.prompt_token_ids)
|
||||
session._output_token_ids.clear()
|
||||
session.append_output_token_ids(list(range(9)))
|
||||
session.num_prompt_tokens = 20
|
||||
session.num_computed_tokens = 29
|
||||
session.model_intermediate_buffer = {
|
||||
"native_duplex": True,
|
||||
"meta": {
|
||||
"next_stage_prompt_len": 20,
|
||||
"next_stage_generation_tokens": 10,
|
||||
"streaming_condition_seq": 0,
|
||||
},
|
||||
}
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
update = _make_talker_update(62, condition_seq=1)
|
||||
|
||||
sched._update_request_as_session(session, update)
|
||||
|
||||
failure = adapter.record_receive_failure.call_args.args
|
||||
assert failure[0] == session.request_id
|
||||
assert "sliding streaming prompt plus generation reserve exceeds max_model_len" in failure[1]
|
||||
assert adapter._streaming_condition_lengths == {}
|
||||
assert adapter._streaming_condition_seqs == {}
|
||||
assert session.status == RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
|
||||
|
||||
def test_talker_capacity_overflow_recomputes_then_resumes_accumulation() -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
adapter = _make_talker_adapter(max_model_len=100)
|
||||
sched.chunk_transfer_adapter = adapter
|
||||
session = _make_request()
|
||||
session.external_req_id = "external-capacity-rollover"
|
||||
session.prompt_token_ids = [0] * 70
|
||||
session._all_token_ids.clear()
|
||||
session._all_token_ids.extend(session.prompt_token_ids)
|
||||
session._output_token_ids.clear()
|
||||
previous_codes = [7, 8, 9, 10]
|
||||
session.append_output_token_ids(previous_codes)
|
||||
session.num_prompt_tokens = 70
|
||||
session.num_computed_tokens = 74
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
adapter._streaming_condition_lengths[session.request_id] = 20
|
||||
adapter._streaming_condition_seqs[session.request_id] = 0
|
||||
adapter.segment_finished_requests.add(session.request_id)
|
||||
adapter.requests_num_chunks_sent[session.external_req_id] = 74
|
||||
overflow_update = _make_talker_update(17, condition_seq=1)
|
||||
|
||||
sched._update_request_as_session(session, overflow_update)
|
||||
|
||||
assert session.num_computed_tokens == 0
|
||||
assert session.num_prompt_tokens == 41
|
||||
assert session.prompt_token_ids == [0] * 41
|
||||
assert overflow_update.model_intermediate_buffer["ids"]["streaming_prompt_previous_codes"] == previous_codes
|
||||
assert overflow_update.model_intermediate_buffer["meta"]["streaming_condition_seq"] == 1
|
||||
assert overflow_update.model_intermediate_buffer["meta"]["streaming_prompt_recompute"] is True
|
||||
assert adapter.segment_finished_requests == set()
|
||||
assert adapter.requests_num_chunks_sent == {}
|
||||
assert session._omni_segment_generation == 1
|
||||
sched._free_request_blocks.assert_called_once_with(session)
|
||||
sched.encoder_cache_manager.free.assert_called_once_with(session)
|
||||
|
||||
# Once the replacement has been recomputed, later conditions append to
|
||||
# the new full-attention prefix until capacity is approached again.
|
||||
next_codes = [21, 22, 23, 24, 25, 26]
|
||||
session.append_output_token_ids(next_codes)
|
||||
session.num_computed_tokens = 47
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
append_update = _make_talker_update(20, condition_seq=2)
|
||||
|
||||
sched._update_request_as_session(session, append_update)
|
||||
|
||||
assert session.num_computed_tokens == 47
|
||||
assert session.num_prompt_tokens == 67
|
||||
assert list(session._all_token_ids[-26:]) == next_codes + [0] * 20
|
||||
assert append_update.model_intermediate_buffer["meta"]["streaming_condition_seq"] == 2
|
||||
assert append_update.model_intermediate_buffer["meta"]["streaming_prompt_recompute"] is False
|
||||
assert session._omni_segment_generation == 2
|
||||
assert sched._free_request_blocks.call_count == 1
|
||||
assert sched.encoder_cache_manager.free.call_count == 1
|
||||
|
||||
# A later overflow replaces the accumulated prefix again rather than
|
||||
# reverting to recompute-on-every-condition behavior.
|
||||
second_rollover_codes = [31, 32, 33, 34, 35, 36]
|
||||
session.num_computed_tokens = session.num_prompt_tokens
|
||||
session.append_output_token_ids(second_rollover_codes)
|
||||
session.num_computed_tokens += len(second_rollover_codes)
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
second_overflow_update = _make_talker_update(18, condition_seq=3)
|
||||
|
||||
sched._update_request_as_session(session, second_overflow_update)
|
||||
|
||||
assert session.num_computed_tokens == 0
|
||||
assert session.num_prompt_tokens == 44
|
||||
assert second_overflow_update.model_intermediate_buffer["ids"]["streaming_prompt_previous_codes"] == (
|
||||
second_rollover_codes
|
||||
)
|
||||
assert second_overflow_update.model_intermediate_buffer["meta"]["streaming_condition_seq"] == 3
|
||||
assert second_overflow_update.model_intermediate_buffer["meta"]["streaming_prompt_recompute"] is True
|
||||
assert session._omni_segment_generation == 3
|
||||
assert sched._free_request_blocks.call_count == 2
|
||||
assert sched.encoder_cache_manager.free.call_count == 2
|
||||
|
||||
|
||||
def test_talker_invalid_capacity_update_is_reported_per_request(mocker) -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
adapter = _make_talker_adapter(max_model_len=100)
|
||||
adapter.record_receive_failure = mocker.MagicMock()
|
||||
sched.chunk_transfer_adapter = adapter
|
||||
session = _make_request()
|
||||
session.status = RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
sched.num_waiting_for_streaming_input = 1
|
||||
update = _make_talker_update(91, condition_seq=0)
|
||||
|
||||
sched._update_request_as_session(session, update)
|
||||
|
||||
adapter.record_receive_failure.assert_called_once_with(
|
||||
session.request_id,
|
||||
"fresh streaming prompt plus generation reserve exceeds max_model_len: prompt=91, reserve=10, limit=100",
|
||||
)
|
||||
assert session.status == RequestStatus.WAITING_FOR_STREAMING_REQ
|
||||
assert sched.num_waiting_for_streaming_input == 1
|
||||
sched._free_request_blocks.assert_not_called()
|
||||
sched.encoder_cache_manager.free.assert_not_called()
|
||||
|
||||
|
||||
def test_ready_async_chunk_prompt_replacement_releases_stale_kv_once() -> None:
|
||||
sched = _make_scheduler(stage_id=1)
|
||||
session = _make_request()
|
||||
|
||||
Reference in new issue
Block a user