[Bugfix][VoxCPM2]: Fix vectorized_gather OOB under concurrent prefill+decode batches (#2903)
Signed-off-by: Sy03 <1370724210@qq.com>
This commit is contained in:
4 files changed
+177
-11
No files matched your search
@@ -41,6 +41,11 @@ logger = init_logger(__name__)
|
||||
|
||||
_ENABLE_PROFILING = os.environ.get("VOXCPM2_PROFILE", "0") == "1"
|
||||
|
||||
# Lower bound for the _active_states leak-warn threshold. The effective
|
||||
# threshold is max(_ACTIVE_STATE_LEAK_WARN_MIN, 4 * max_batch_size) so small
|
||||
# deployments still get a usable floor instead of a tiny noisy one.
|
||||
_ACTIVE_STATE_LEAK_WARN_MIN = 512
|
||||
|
||||
|
||||
def is_cjk_char(c: str) -> bool:
|
||||
"""Check if a character is a CJK ideograph."""
|
||||
@@ -440,6 +445,9 @@ class VoxCPM2TalkerForConditionalGeneration(nn.Module):
|
||||
self._results_queue: list[tuple[str, torch.Tensor | None]] = []
|
||||
self._audio_queue: list[tuple[str, Any]] = []
|
||||
self._deferred_cleanup_ids: set[str] = set()
|
||||
self._active_state_warn_threshold = max(_ACTIVE_STATE_LEAK_WARN_MIN, 4 * self._max_batch_size)
|
||||
# one-shot by design: fires at most once per process to avoid log spam.
|
||||
self._active_state_warned = False
|
||||
|
||||
@property
|
||||
def tts(self) -> nn.Module:
|
||||
@@ -449,9 +457,20 @@ class VoxCPM2TalkerForConditionalGeneration(nn.Module):
|
||||
# -------------------- request state management --------------------
|
||||
|
||||
def _get_or_create_state(self, request_id: str) -> _RequestState:
|
||||
if request_id not in self._active_states:
|
||||
self._active_states[request_id] = _RequestState(request_id=request_id)
|
||||
return self._active_states[request_id]
|
||||
state = self._active_states.get(request_id)
|
||||
if state is None:
|
||||
state = _RequestState(request_id=request_id)
|
||||
self._active_states[request_id] = state
|
||||
if len(self._active_states) > self._active_state_warn_threshold and not self._active_state_warned:
|
||||
logger.warning(
|
||||
"VoxCPM2: _active_states size=%d exceeds threshold %d "
|
||||
"(max_batch_size=%d); possible cleanup path leak",
|
||||
len(self._active_states),
|
||||
self._active_state_warn_threshold,
|
||||
self._max_batch_size,
|
||||
)
|
||||
self._active_state_warned = True
|
||||
return state
|
||||
|
||||
def _switch_to_request(self, request_id: str) -> _RequestState:
|
||||
if request_id != self._current_request_id:
|
||||
@@ -1087,14 +1106,10 @@ class VoxCPM2TalkerForConditionalGeneration(nn.Module):
|
||||
is_prefill = span_len > 1
|
||||
|
||||
if is_prefill:
|
||||
# Evict stale states
|
||||
pending_ids = {rid for rid, *_ in self._pending_requests}
|
||||
pending_ids.add(req_id)
|
||||
if self._current_request_id:
|
||||
pending_ids.add(self._current_request_id)
|
||||
for rid in [r for r, s in self._active_states.items() if r not in pending_ids and s.prefill_completed]:
|
||||
self._cleanup_request(rid)
|
||||
|
||||
# Do not evict state here: _pending_requests is a per-step prefix,
|
||||
# not the full batch. Cleanup is driven by on_requests_finished ->
|
||||
# _flush_deferred_cleanup (fed by vLLM scheduler._free_request via
|
||||
# gpu_ar_model_runner.py).
|
||||
real = info_dict.get("text_token_ids")
|
||||
token_ids = input_ids.tolist() if real is None else real[0]
|
||||
# Fail-fast: unsplit multichar Chinese IDs in input_ids means the
|
||||
|
||||
Reference in new issue
Block a user