[Performance][MiniCPM-o] Whole-Euler CUDA graphs for Code2Wav with a shared attention arena (#8007)
This commit is contained in:
1 parent
0173cb3748
commit
3e62bba3cb
15 files changed
+3425
-210
No files matched your search
@@ -15,7 +15,16 @@ import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from vllm.logger import init_logger
|
||||
|
||||
from .cuda_graph_wrapper import CFMGraphWrapper, HiFTGraphWrapper
|
||||
from .cuda_graph_wrapper import (
|
||||
CFMGraphWrapper,
|
||||
HiFTGraphWrapper,
|
||||
WholeEulerCFMGraphWrapper,
|
||||
_att_keep_ranges,
|
||||
_copy_frame_segments,
|
||||
_euler_step,
|
||||
_euler_timeline,
|
||||
_zero_padded_cnn_cache,
|
||||
)
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
@@ -25,6 +34,17 @@ _SILENCE_TOKEN = 4218
|
||||
# than this many codec tokens overflows the PE slice (matrix_ac vs matrix_bd).
|
||||
_DEFAULT_RELPOS_MAX_POS = 5000
|
||||
_MAX_ENCODE_TOKEN_CAP = 1024
|
||||
# Streaming cache trim (stepaudio2 ``Token2wav.stream``): past ``prompt_len +
|
||||
# 100`` frames, keep the first ``prompt_len`` frames and the last 100.
|
||||
_CACHE_TRIM_SUFFIX = 100
|
||||
|
||||
|
||||
def _trim_streaming_cache(cache: torch.Tensor, prompt_len: int) -> torch.Tensor:
|
||||
"""The streaming trim of a cache whose frame axis is ``-2``."""
|
||||
ranges = _att_keep_ranges(int(cache.shape[-2]), (prompt_len, _CACHE_TRIM_SUFFIX))
|
||||
if len(ranges) == 1:
|
||||
return cache
|
||||
return torch.cat([cache[..., start : start + length, :] for start, length in ranges], dim=-2)
|
||||
|
||||
|
||||
def relpos_encode_token_budget(
|
||||
@@ -47,6 +67,26 @@ def relpos_encode_token_budget(
|
||||
return max(lookahead + 1, min(int(cap), room))
|
||||
|
||||
|
||||
def _supports_ragged_kernel(estimator: nn.Module) -> bool:
|
||||
"""Whether ``_blocks_forward_chunk_ragged`` can run this estimator's DiT blocks."""
|
||||
blocks = getattr(estimator, "blocks", None)
|
||||
if not blocks or not hasattr(estimator, "in_proj") or not hasattr(estimator, "final_layer"):
|
||||
return False
|
||||
block = blocks[0]
|
||||
attn = getattr(block, "attn", None)
|
||||
conv_blocks = getattr(getattr(block, "conv", None), "block", None)
|
||||
return (
|
||||
hasattr(block, "adaLN_modulation")
|
||||
and all(
|
||||
hasattr(attn, name)
|
||||
for name in ("forward_chunk", "to_q", "to_k", "to_v", "to_heads", "q_norm", "k_norm", "proj")
|
||||
)
|
||||
and conv_blocks is not None
|
||||
and len(conv_blocks) >= 8
|
||||
and all(isinstance(conv_blocks[index], nn.Conv1d) for index in (1, 6))
|
||||
)
|
||||
|
||||
|
||||
def _cfm_pad_frames(
|
||||
*,
|
||||
mel_frames: int,
|
||||
@@ -98,38 +138,6 @@ def _zero_padded_frames(tensor: torch.Tensor, valid_frames: int | None) -> None:
|
||||
tensor[..., valid_frames:] = 0.0
|
||||
|
||||
|
||||
def _zero_padded_cnn_cache(
|
||||
cnn_cache: torch.Tensor,
|
||||
estimator: nn.Module,
|
||||
pad_frames: int,
|
||||
) -> None:
|
||||
"""Clear the cache positions that come from padded frames, in place.
|
||||
|
||||
Each block's CNN cache holds the tail of its convolution output
|
||||
(``new_cnn_cache = x[..., -causal_padding[0]:]``, inside ``stepaudio2``),
|
||||
so when the padding sits at the chunk tail those positions are
|
||||
padding-derived and would otherwise become the next chunk's left context.
|
||||
Padding is at most ``pad_frames`` wide, so only the trailing positions that
|
||||
can come from it are cleared; the valid part of the window is kept.
|
||||
"""
|
||||
blocks = estimator.blocks
|
||||
if pad_frames > 0 and blocks and len(blocks) == cnn_cache.shape[0]:
|
||||
width = int(cnn_cache.shape[-1])
|
||||
if width > 0 and all(int(block.conv.block[1].causal_padding[0]) == width for block in blocks):
|
||||
# _estimator_buffers packs equal-width blocks into one tensor.
|
||||
# Clear their shared tail with one write instead of one per block.
|
||||
cnn_cache[..., max(0, width - pad_frames) :] = 0.0
|
||||
return
|
||||
|
||||
for index, block in enumerate(estimator.blocks):
|
||||
width = int(block.conv.block[1].causal_padding[0])
|
||||
if width <= 0:
|
||||
continue
|
||||
zero_from = max(0, width - pad_frames)
|
||||
if zero_from < width:
|
||||
cnn_cache[index][..., zero_from:] = 0.0
|
||||
|
||||
|
||||
def plan_token2wav_encode_slices(
|
||||
num_frames: int,
|
||||
*,
|
||||
@@ -297,6 +305,7 @@ class BatchedToken2Wav(nn.Module):
|
||||
token2wav.speech_window.detach().clone(),
|
||||
persistent=False,
|
||||
)
|
||||
self._timeline_cache: dict[tuple[torch.device, torch.dtype], tuple[torch.Tensor, list[float]]] = {}
|
||||
self.hift_graph_wrapper: HiFTGraphWrapper | None = None
|
||||
graph_config = dict(hift_graph_config or {})
|
||||
if bool(graph_config.get("enabled", False)):
|
||||
@@ -312,26 +321,79 @@ class BatchedToken2Wav(nn.Module):
|
||||
"MiniCPM-o HiFT CUDA Graph requires source_cache_len to be divisible by mel_cache_len"
|
||||
)
|
||||
capture_batch_sizes = graph_config.get("capture_batch_sizes", [1])
|
||||
logger.info("Enabling HiFT CUDA Graph with batch sizes %s", capture_batch_sizes)
|
||||
max_serial_batch = int(graph_config.get("max_serial_batch", 4))
|
||||
logger.info(
|
||||
"Enabling HiFT CUDA Graph with batch sizes %s (max_serial_batch=%d)",
|
||||
capture_batch_sizes,
|
||||
max_serial_batch,
|
||||
)
|
||||
self.hift_graph_wrapper = HiFTGraphWrapper(
|
||||
token2wav=token2wav,
|
||||
connector_config=dict(connector_config),
|
||||
capture_batch_sizes=capture_batch_sizes,
|
||||
max_serial_batch=max_serial_batch,
|
||||
)
|
||||
with torch.inference_mode(), _autocast_disabled(hift_parameter.device):
|
||||
self.hift_graph_wrapper.capture()
|
||||
logger.info("HiFT CUDA Graph captured successfully")
|
||||
self._cfm_graph_wrapper: CFMGraphWrapper | None = None
|
||||
self._whole_euler_graph_wrapper: WholeEulerCFMGraphWrapper | None = None
|
||||
cfm_graph_cfg = dict(cfm_graph_config or {})
|
||||
if bool(cfm_graph_cfg.get("enabled", False)):
|
||||
flow_parameter = next(self.flow.parameters(), None)
|
||||
if flow_parameter is not None and flow_parameter.device.type == "cuda":
|
||||
estimator = self.flow.decoder.estimator
|
||||
max_graphs = int(cfm_graph_cfg.get("max_graphs", 32))
|
||||
max_serial_batch = int(cfm_graph_cfg.get("max_serial_batch", 4))
|
||||
max_graph_batch_cfg = cfm_graph_cfg.get("max_graph_batch")
|
||||
max_graph_batch = int(max_graph_batch_cfg) if max_graph_batch_cfg is not None else None
|
||||
micro_batch_size_cfg = cfm_graph_cfg.get("micro_batch_size")
|
||||
micro_batch_size = int(micro_batch_size_cfg) if micro_batch_size_cfg is not None else 4
|
||||
if bool(cfm_graph_cfg.get("enable_whole_euler", True)) and self._trt_stepper is None:
|
||||
capture_frames = cfm_graph_cfg.get("capture_frames")
|
||||
if capture_frames is None and connector_config is not None:
|
||||
# The steady chunk's mel width, derived like HiFTGraphWrapper's
|
||||
# capture bucket: steady chunks run unpadded and shorter
|
||||
# chunks share their graph under a mask.
|
||||
capture_frames = int(connector_config["codec_chunk_frames"]) * int(
|
||||
getattr(self.flow, "token_mel_ratio", 2)
|
||||
)
|
||||
query_bucket_frames = int(capture_frames if capture_frames is not None else 64)
|
||||
self._whole_euler_graph_wrapper = WholeEulerCFMGraphWrapper(
|
||||
estimator=estimator,
|
||||
n_timesteps=self.n_timesteps,
|
||||
inference_cfg_rate=getattr(self.flow.decoder, "inference_cfg_rate", 0.7),
|
||||
att_cache_dtype=self._estimator_att_cache_dtype,
|
||||
max_graphs=max_graphs,
|
||||
max_serial_batch=max_serial_batch,
|
||||
max_graph_batch=max_graph_batch,
|
||||
micro_batch_size=micro_batch_size,
|
||||
query_bucket_frames=query_bucket_frames,
|
||||
pad_max_rows=cfm_graph_cfg.get("pad_max_rows"),
|
||||
# Looked up at capture time, so a replaced ragged kernel is the one captured.
|
||||
ragged_body=(
|
||||
(lambda *args: self._blocks_forward_chunk_ragged(*args))
|
||||
if _supports_ragged_kernel(estimator)
|
||||
else None
|
||||
),
|
||||
)
|
||||
logger.info(
|
||||
"Whole-Euler CFM CUDA Graph enabled "
|
||||
"(max_graphs=%d, max_serial_batch=%d, max_graph_batch=%s, "
|
||||
"micro_batch_size=%d, query_bucket_frames=%d)",
|
||||
max_graphs,
|
||||
max_serial_batch,
|
||||
str(max_graph_batch),
|
||||
micro_batch_size,
|
||||
query_bucket_frames,
|
||||
)
|
||||
elif self._trt_stepper is not None and bool(cfm_graph_cfg.get("enable_whole_euler", True)):
|
||||
logger.info("Whole-Euler CFM CUDA Graph disabled because TensorRT stepper is configured")
|
||||
self._cfm_graph_wrapper = CFMGraphWrapper(
|
||||
graph_fn=estimator.blocks_forward_chunk,
|
||||
max_graphs=int(cfm_graph_cfg.get("max_graphs", 32)),
|
||||
max_graphs=max_graphs,
|
||||
)
|
||||
logger.info("CFM CUDA Graph enabled (max_graphs=%d)", int(cfm_graph_cfg.get("max_graphs", 32)))
|
||||
logger.info("CFM CUDA Graph enabled (max_graphs=%d)", max_graphs)
|
||||
else:
|
||||
logger.info(
|
||||
"CFM CUDA Graph is disabled on device type %s",
|
||||
@@ -341,7 +403,9 @@ class BatchedToken2Wav(nn.Module):
|
||||
# chunk up to a multiple of this many frames so the graph cache key
|
||||
# space stays small (0 disables bucketing, e.g. when graphs are off).
|
||||
self._cfm_graph_bucket_frames = (
|
||||
int(cfm_graph_cfg.get("bucket_frames", 0)) if self._cfm_graph_wrapper is not None else 0
|
||||
int(cfm_graph_cfg.get("bucket_frames", 0))
|
||||
if (self._cfm_graph_wrapper is not None or self._whole_euler_graph_wrapper is not None)
|
||||
else 0
|
||||
)
|
||||
if self._cfm_graph_bucket_frames > 1:
|
||||
logger.info(
|
||||
@@ -350,9 +414,7 @@ class BatchedToken2Wav(nn.Module):
|
||||
)
|
||||
self._prompt_features: dict[tuple[str, str], PromptFeatures] = {}
|
||||
self._setup_cache_size = setup_cache_size
|
||||
self._setup_cache: OrderedDict[tuple[tuple[str, str], int, int], tuple[BatchedToken2WavState, ...]] = (
|
||||
OrderedDict()
|
||||
)
|
||||
self._setup_cache: OrderedDict[tuple[tuple[str, str], int], BatchedToken2WavState] = OrderedDict()
|
||||
|
||||
def _hift_inference(
|
||||
self,
|
||||
@@ -507,6 +569,7 @@ class BatchedToken2Wav(nn.Module):
|
||||
attn_mask: torch.Tensor | None = None,
|
||||
valid_lengths: list[int] | None = None,
|
||||
valid_frames: int | None = None,
|
||||
time_embedding: torch.Tensor | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
if self._trt_stepper is not None and valid_lengths is None:
|
||||
out, new_cnn, new_att = self._trt_stepper.step(
|
||||
@@ -519,7 +582,8 @@ class BatchedToken2Wav(nn.Module):
|
||||
att_cache=att_cache,
|
||||
)
|
||||
return out.to(mu.dtype), new_cnn, new_att
|
||||
time_embedding = estimator.t_embedder(time).unsqueeze(1)
|
||||
if time_embedding is None:
|
||||
time_embedding = estimator.t_embedder(time).unsqueeze(1)
|
||||
width = int(x.shape[-1])
|
||||
speaker_features = speakers.unsqueeze(-1).expand(-1, -1, width)
|
||||
if valid_frames is not None and valid_frames < width:
|
||||
@@ -583,14 +647,95 @@ class BatchedToken2Wav(nn.Module):
|
||||
valid_lengths: torch.Tensor,
|
||||
width: int,
|
||||
) -> torch.Tensor:
|
||||
"""The ``width`` frames of ``history`` (b, width + t, c) ending each row's valid frames, as (b, c, width)."""
|
||||
indices = valid_lengths[:, None] + torch.arange(width, device=history.device)[None, :]
|
||||
return history.gather(
|
||||
2,
|
||||
indices[:, None, :].expand(-1, int(history.shape[1]), -1),
|
||||
)
|
||||
return history.gather(1, indices[:, :, None].expand(-1, -1, int(history.shape[2]))).transpose(1, 2)
|
||||
|
||||
@staticmethod
|
||||
def _causal_conv_frames_major(conv: nn.Conv1d, history: torch.Tensor) -> torch.Tensor:
|
||||
"""``conv`` over ``history`` (b, width + t, c_in) as (b, t, c_out), without a layout change.
|
||||
|
||||
The DiT keeps activations as (b, t, c), which already is NHWC for a
|
||||
height-1 image. Upstream transposes them to (b, c, t) for ``Conv1d``,
|
||||
and cuDNN converts that to NHWC and back around every convolution. As
|
||||
a channels-last conv2d it reads the activations where they are. The
|
||||
channels-last weight is converted once and reused. Other devices run
|
||||
upstream's ``Conv1d`` layout (Ascend has no channels-last format).
|
||||
"""
|
||||
if not history.is_cuda:
|
||||
output = F.conv1d(
|
||||
history.transpose(1, 2),
|
||||
conv.weight,
|
||||
conv.bias,
|
||||
conv.stride,
|
||||
conv.padding,
|
||||
conv.dilation,
|
||||
conv.groups,
|
||||
)
|
||||
return output.transpose(1, 2)
|
||||
weight = conv.weight
|
||||
version = (weight.data_ptr(), weight._version, weight.dtype)
|
||||
cached = conv.__dict__.get("_channels_last_weight")
|
||||
if cached is None or cached[0] != version:
|
||||
cached = (version, weight.detach().unsqueeze(2).contiguous(memory_format=torch.channels_last))
|
||||
conv.__dict__["_channels_last_weight"] = cached
|
||||
output = F.conv2d(
|
||||
history.transpose(1, 2).unsqueeze(2),
|
||||
cached[1],
|
||||
conv.bias,
|
||||
(1, conv.stride[0]),
|
||||
(0, conv.padding[0]),
|
||||
(1, conv.dilation[0]),
|
||||
conv.groups,
|
||||
)
|
||||
return output.squeeze(2).transpose(1, 2)
|
||||
|
||||
@staticmethod
|
||||
def _attend_in_place(
|
||||
attn: nn.Module,
|
||||
x: torch.Tensor,
|
||||
att_cache: torch.Tensor | None,
|
||||
attn_mask: torch.Tensor | None,
|
||||
kv: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
"""``attn.forward_chunk`` that keeps the new keys and values in ``kv``.
|
||||
|
||||
``kv`` (b, heads, >= t + cache frames, 2 * head_dim) is the block's new
|
||||
cache, ``[current | cache]`` like upstream's. Upstream concatenates the
|
||||
whole cache behind the current keys and values, then both into the new
|
||||
cache, which the caller copied into ``kv``: four passes over Stage 2's
|
||||
largest tensor. Here only the current chunk is written and attention reads ``kv`` in
|
||||
place. A cache that already sits behind the current chunk in ``kv``
|
||||
(Whole-Euler) is not moved; any other is copied there once. Other
|
||||
devices keep upstream's attention and copy its cache into ``kv``.
|
||||
"""
|
||||
if kv.dtype != x.dtype or not x.is_cuda:
|
||||
# A reduced-precision cache attends in the activation dtype.
|
||||
x_att, new_att = attn.forward_chunk(x, att_cache, attn_mask)
|
||||
kv[:, :, : int(new_att.shape[2])].copy_(new_att)
|
||||
return x_att
|
||||
batch, frames, _ = x.shape
|
||||
q = attn.q_norm(attn.to_heads(attn.to_q(x)))
|
||||
k = attn.k_norm(attn.to_heads(attn.to_k(x)))
|
||||
v = attn.to_heads(attn.to_v(x))
|
||||
cached = 0 if att_cache is None else int(att_cache.shape[2])
|
||||
kv = kv[:, :, : frames + cached]
|
||||
behind = kv[:, :, frames:]
|
||||
if cached and (att_cache.data_ptr() != behind.data_ptr() or att_cache.stride() != behind.stride()):
|
||||
behind.copy_(att_cache)
|
||||
head_dim = int(k.shape[3])
|
||||
kv[..., :frames, :head_dim].copy_(k)
|
||||
kv[..., :frames, head_dim:].copy_(v)
|
||||
out = F.scaled_dot_product_attention(
|
||||
q,
|
||||
kv[..., :head_dim],
|
||||
kv[..., head_dim:],
|
||||
attn_mask=None if attn_mask is None else attn_mask.unsqueeze(1),
|
||||
)
|
||||
return attn.proj(out.transpose(1, 2).reshape(batch, frames, -1))
|
||||
|
||||
@staticmethod
|
||||
def _blocks_forward_chunk_ragged(
|
||||
self,
|
||||
estimator: nn.Module,
|
||||
estimator_input: torch.Tensor,
|
||||
time_embedding: torch.Tensor,
|
||||
@@ -599,14 +744,24 @@ class BatchedToken2Wav(nn.Module):
|
||||
att_cache: Any,
|
||||
cnn_cache_buffer: torch.Tensor,
|
||||
att_cache_buffer: torch.Tensor,
|
||||
valid_lengths: list[int],
|
||||
valid_lengths: list[int] | torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
"""Run one padded DiT batch while capturing exact per-row CNN state."""
|
||||
lengths = torch.tensor(
|
||||
(*valid_lengths, *valid_lengths),
|
||||
device=estimator_input.device,
|
||||
dtype=torch.long,
|
||||
)
|
||||
"""Run one padded DiT batch while capturing exact per-row CNN state.
|
||||
|
||||
``valid_lengths`` is one length per request, or the ``(2B,)`` CFG
|
||||
lengths tensor a CUDA graph captures as a static input. Upstream's
|
||||
``DiTBlock.forward_chunk``, with attention over the output cache in
|
||||
place (``_attend_in_place``) and the causal convolutions on the
|
||||
frames-major activations (``_causal_conv_frames_major``).
|
||||
"""
|
||||
if isinstance(valid_lengths, torch.Tensor):
|
||||
lengths = valid_lengths
|
||||
else:
|
||||
lengths = torch.tensor(
|
||||
(*valid_lengths, *valid_lengths),
|
||||
device=estimator_input.device,
|
||||
dtype=torch.long,
|
||||
)
|
||||
x = estimator.in_proj(estimator_input.transpose(1, 2))
|
||||
for block_index, block in enumerate(estimator.blocks):
|
||||
(
|
||||
@@ -622,49 +777,55 @@ class BatchedToken2Wav(nn.Module):
|
||||
) = block.adaLN_modulation(time_embedding).chunk(9, dim=-1)
|
||||
|
||||
normalized = block.norm1(x) * (1 + scale_msa) + shift_msa
|
||||
x_att, new_att = block.attn.forward_chunk(
|
||||
x_att = BatchedToken2Wav._attend_in_place(
|
||||
block.attn,
|
||||
normalized,
|
||||
att_cache[block_index],
|
||||
attn_mask,
|
||||
att_cache_buffer[block_index],
|
||||
)
|
||||
x = x + gate_msa * x_att
|
||||
|
||||
# CausalConvBlock: [T, conv1, T, LayerNorm, Mish, T, conv2, T]
|
||||
# with the transposes (T) dropped.
|
||||
conv = block.conv
|
||||
conv_input = block.norm3(x) * (1 + scale_conv) + shift_conv
|
||||
old_cnn = cnn_cache[block_index]
|
||||
if old_cnn is None:
|
||||
width = int(block.conv.block[1].causal_padding[0])
|
||||
old_cnn = conv_input.new_zeros(
|
||||
(
|
||||
int(conv_input.shape[0]),
|
||||
int(block.conv.in_channels + block.conv.out_channels),
|
||||
width,
|
||||
int(conv.in_channels + conv.out_channels),
|
||||
int(conv.block[1].causal_padding[0]),
|
||||
)
|
||||
)
|
||||
old_cnn1, old_cnn2 = old_cnn.split((block.conv.in_channels, block.conv.out_channels), dim=1)
|
||||
|
||||
conv1_input = block.conv.block[0](conv_input)
|
||||
conv1_output, _ = block.conv.block[1].forward_chunk(conv1_input, old_cnn1)
|
||||
new_cnn1 = self._gather_causal_cache(
|
||||
torch.cat((old_cnn1, conv1_input), dim=2),
|
||||
lengths,
|
||||
int(old_cnn1.shape[2]),
|
||||
)
|
||||
|
||||
conv2_input = block.conv.block[2:6](conv1_output)
|
||||
conv2_output, _ = block.conv.block[6].forward_chunk(conv2_input, old_cnn2)
|
||||
new_cnn2 = self._gather_causal_cache(
|
||||
torch.cat((old_cnn2, conv2_input), dim=2),
|
||||
lengths,
|
||||
int(old_cnn2.shape[2]),
|
||||
)
|
||||
x = x + gate_conv * block.conv.block[7](conv2_output)
|
||||
width = int(old_cnn.shape[2])
|
||||
history = torch.cat((old_cnn[:, : conv.in_channels].transpose(1, 2), conv_input), dim=1)
|
||||
new_cnn1 = BatchedToken2Wav._gather_causal_cache(history, lengths, width)
|
||||
hidden = conv.block[4](conv.block[3](BatchedToken2Wav._causal_conv_frames_major(conv.block[1], history)))
|
||||
history = torch.cat((old_cnn[:, conv.in_channels :].transpose(1, 2), hidden), dim=1)
|
||||
new_cnn2 = BatchedToken2Wav._gather_causal_cache(history, lengths, width)
|
||||
x = x + gate_conv * BatchedToken2Wav._causal_conv_frames_major(conv.block[6], history)
|
||||
x = x + gate_mlp * block.mlp(block.norm2(x) * (1 + scale_mlp) + shift_mlp)
|
||||
|
||||
cnn_cache_buffer[block_index].copy_(torch.cat((new_cnn1, new_cnn2), dim=1))
|
||||
att_cache_buffer[block_index][:, :, : int(new_att.shape[2]), :].copy_(new_att)
|
||||
cnn_cache_buffer[block_index][:, : conv.in_channels].copy_(new_cnn1)
|
||||
cnn_cache_buffer[block_index][:, conv.in_channels :].copy_(new_cnn2)
|
||||
|
||||
return estimator.final_layer(x, time_embedding).transpose(1, 2)
|
||||
|
||||
def _get_timeline(self, device: torch.device, dtype: torch.dtype) -> tuple[torch.Tensor, list[float]]:
|
||||
"""The Euler timeline and its step sizes, read to the host once.
|
||||
|
||||
``_euler_step`` takes ``dt`` as a Python float, so reading it
|
||||
from the device timeline on every decode would sync once per step.
|
||||
"""
|
||||
key = (device, dtype)
|
||||
cached = self._timeline_cache.get(key)
|
||||
if cached is None:
|
||||
cached = _euler_timeline(self.n_timesteps, device, dtype)
|
||||
self._timeline_cache[key] = cached
|
||||
return cached
|
||||
|
||||
def _decode_cfm(
|
||||
self,
|
||||
mu: torch.Tensor,
|
||||
@@ -672,30 +833,44 @@ class BatchedToken2Wav(nn.Module):
|
||||
cond: torch.Tensor,
|
||||
*,
|
||||
cnn_cache: torch.Tensor | None,
|
||||
att_cache: torch.Tensor | None,
|
||||
att_cache: torch.Tensor | list[torch.Tensor] | None,
|
||||
valid_lengths: list[int] | None = None,
|
||||
att_keep: tuple[int, int] | None = None,
|
||||
) -> tuple[
|
||||
torch.Tensor,
|
||||
torch.Tensor,
|
||||
torch.Tensor | list[torch.Tensor],
|
||||
]:
|
||||
"""Run the CFM Euler solve for one chunk.
|
||||
|
||||
``att_cache`` may be the stacked estimator cache or one tensor per
|
||||
request (the Whole-Euler path). A Whole-Euler solve returns the new
|
||||
cache in the same form, already trimmed by ``att_keep``; the eager
|
||||
path returns it stacked and untrimmed (ragged: per request, untrimmed).
|
||||
"""
|
||||
decoder = self.flow.decoder
|
||||
estimator = decoder.estimator
|
||||
batch_size = int(mu.shape[0])
|
||||
offset = int(att_cache.shape[4]) if att_cache is not None else 0
|
||||
att_rows = att_cache if isinstance(att_cache, list) else None
|
||||
if att_rows is not None:
|
||||
offset = int(att_rows[0].shape[4])
|
||||
else:
|
||||
offset = int(att_cache.shape[4]) if att_cache is not None else 0
|
||||
mel_frames = int(mu.shape[2])
|
||||
graphs_active = valid_lengths is None and (
|
||||
self._whole_euler_active()
|
||||
or (
|
||||
self._cfm_graph_wrapper is not None
|
||||
and getattr(self._cfm_graph_wrapper, "enabled", True)
|
||||
and self._trt_stepper is None
|
||||
)
|
||||
)
|
||||
pad_frames = _cfm_pad_frames(
|
||||
mel_frames=mel_frames,
|
||||
offset=offset,
|
||||
noise_capacity=int(decoder.rand_noise.shape[2]),
|
||||
bucket_frames=self._cfm_graph_bucket_frames,
|
||||
disabled=(
|
||||
valid_lengths is not None
|
||||
or self._cfm_graph_wrapper is None
|
||||
# `_disable` keeps the wrapper object alive, so check the flag
|
||||
# too: padding under a disabled wrapper is pure overhead.
|
||||
or not self._cfm_graph_wrapper.enabled
|
||||
),
|
||||
disabled=(valid_lengths is not None or not graphs_active),
|
||||
)
|
||||
if pad_frames:
|
||||
# Replicate rather than zero: a repeated last frame is a closer
|
||||
@@ -718,15 +893,7 @@ class BatchedToken2Wav(nn.Module):
|
||||
# is what removes them; zeroing alone would not, since a zero row
|
||||
# still occupies part of the softmax denominator.
|
||||
x[:, :, mel_frames:] = 0.0
|
||||
timeline = torch.linspace(
|
||||
0,
|
||||
1,
|
||||
self.n_timesteps + 1,
|
||||
device=mu.device,
|
||||
dtype=mu.dtype,
|
||||
)
|
||||
timeline = 1 - torch.cos(timeline * 0.5 * torch.pi)
|
||||
time = timeline[0].expand(batch_size)
|
||||
timeline, dt_steps = self._get_timeline(mu.device, mu.dtype)
|
||||
mu_cfg = torch.cat((mu, torch.zeros_like(mu)), dim=0)
|
||||
speakers_cfg = torch.cat((speakers, torch.zeros_like(speakers)), dim=0)
|
||||
cond_cfg = torch.cat((cond, torch.zeros_like(cond)), dim=0)
|
||||
@@ -761,19 +928,42 @@ class BatchedToken2Wav(nn.Module):
|
||||
device=mu.device,
|
||||
)
|
||||
attn_mask[:, :, mel_frames : mel_frames + pad_frames] = False
|
||||
|
||||
if self._whole_euler_active() and (valid_lengths is None or self._whole_euler_ragged_active()):
|
||||
whole_euler_result = self._whole_euler_graph_wrapper.replay(
|
||||
x=x,
|
||||
mu_cfg=mu_cfg,
|
||||
speakers_cfg=speakers_cfg,
|
||||
cond_cfg=cond_cfg,
|
||||
cnn_cache=cnn_cache,
|
||||
att_cache=att_cache,
|
||||
attn_mask=attn_mask,
|
||||
mel_frames=mel_frames,
|
||||
pad_frames=pad_frames,
|
||||
valid_lengths=valid_lengths,
|
||||
att_keep=att_keep,
|
||||
)
|
||||
if whole_euler_result is not None:
|
||||
return whole_euler_result
|
||||
if att_rows is not None:
|
||||
att_cache = self._stack_estimator_att(att_rows)
|
||||
|
||||
next_cnn: list[torch.Tensor] = []
|
||||
next_att_cache: torch.Tensor | None = None
|
||||
ragged_att_cache: list[torch.Tensor] | None = None
|
||||
dt = timeline[1] - timeline[0]
|
||||
time_embeddings = [
|
||||
estimator.t_embedder(timeline[s].expand(2 * batch_size)).unsqueeze(1) for s in range(self.n_timesteps)
|
||||
]
|
||||
with _token2wav_sdpa_context(mu.device):
|
||||
for step in range(self.n_timesteps):
|
||||
old_cnn = cnn_cache[step] if cnn_cache is not None else None
|
||||
old_att = att_cache[step] if att_cache is not None else None
|
||||
step_time = timeline[step].expand(2 * batch_size)
|
||||
estimate, step_cnn, step_att = self._estimator_step(
|
||||
estimator,
|
||||
x=torch.cat((x, x), dim=0),
|
||||
mu=mu_cfg,
|
||||
time=torch.cat((time, time), dim=0),
|
||||
time=step_time,
|
||||
speakers=speakers_cfg,
|
||||
cond=cond_cfg,
|
||||
cnn_cache=old_cnn,
|
||||
@@ -781,16 +971,13 @@ class BatchedToken2Wav(nn.Module):
|
||||
attn_mask=attn_mask,
|
||||
valid_lengths=valid_lengths,
|
||||
valid_frames=mel_frames if pad_frames else None,
|
||||
time_embedding=time_embeddings[step],
|
||||
)
|
||||
if pad_frames:
|
||||
_zero_padded_cnn_cache(step_cnn, estimator, pad_frames)
|
||||
conditional, unconditional = estimate.split(batch_size, dim=0)
|
||||
velocity = (1.0 + decoder.inference_cfg_rate) * conditional - decoder.inference_cfg_rate * unconditional
|
||||
x = x + dt * velocity
|
||||
_zero_padded_frames(x, mel_frames if pad_frames else None)
|
||||
time = time + dt
|
||||
if step + 1 < self.n_timesteps:
|
||||
dt = timeline[step + 2] - time[0]
|
||||
x = _euler_step(x, estimate, dt_steps[step], decoder.inference_cfg_rate, batch_size)
|
||||
if pad_frames:
|
||||
_zero_padded_frames(x, mel_frames)
|
||||
next_cnn.append(step_cnn)
|
||||
if valid_lengths is not None:
|
||||
if ragged_att_cache is None:
|
||||
@@ -842,28 +1029,40 @@ class BatchedToken2Wav(nn.Module):
|
||||
assert next_att_cache is not None
|
||||
return x, torch.stack(next_cnn), next_att_cache
|
||||
|
||||
def _split_flow_cache(self, cache: dict[str, torch.Tensor], batch_size: int) -> list[dict[str, torch.Tensor]]:
|
||||
def _split_flow_cache(
|
||||
self,
|
||||
cache: dict[str, Any],
|
||||
batch_size: int,
|
||||
*,
|
||||
estimator_att_keep: tuple[int, int] | None = None,
|
||||
) -> list[dict[str, torch.Tensor]]:
|
||||
"""Per-request flow caches from a batch's stacked caches.
|
||||
|
||||
``estimator_att_cache`` is stacked ``(n_t, depth, 2B, heads, L, d)``
|
||||
with CFG rows ``[cond x B | uncond x B]``, or already one tensor per
|
||||
request (Whole-Euler). The streaming trim ``estimator_att_keep`` is
|
||||
applied while each request is copied out, so no trimmed copy of the
|
||||
whole batch is built first: at 16 requests that copy alone was ~7 GiB.
|
||||
"""
|
||||
estimator_att = cache["estimator_att_cache"]
|
||||
stacked_att = isinstance(estimator_att, torch.Tensor)
|
||||
if stacked_att:
|
||||
keep_ranges = _att_keep_ranges(int(estimator_att.shape[4]), estimator_att_keep)
|
||||
request_shape = list(estimator_att.shape)
|
||||
request_shape[2] = 2
|
||||
request_shape[4] = sum(length for _, length in keep_ranges)
|
||||
att_rows = estimator_att.unflatten(2, (2, batch_size))
|
||||
result: list[dict[str, torch.Tensor]] = []
|
||||
for row in range(batch_size):
|
||||
estimator_att = cache["estimator_att_cache"]
|
||||
if estimator_att.dtype == self._estimator_att_cache_dtype:
|
||||
request_att = torch.cat(
|
||||
(
|
||||
estimator_att[:, :, row : row + 1],
|
||||
estimator_att[:, :, batch_size + row : batch_size + row + 1],
|
||||
),
|
||||
dim=2,
|
||||
).detach()
|
||||
else:
|
||||
request_shape = list(estimator_att.shape)
|
||||
request_shape[2] = 2
|
||||
if stacked_att:
|
||||
request_att = torch.empty(
|
||||
request_shape,
|
||||
device=estimator_att.device,
|
||||
dtype=self._estimator_att_cache_dtype,
|
||||
)
|
||||
request_att[:, :, 0:1].copy_(estimator_att[:, :, row : row + 1])
|
||||
request_att[:, :, 1:2].copy_(estimator_att[:, :, batch_size + row : batch_size + row + 1])
|
||||
_copy_frame_segments(request_att, att_rows[:, :, :, row], keep_ranges)
|
||||
else:
|
||||
request_att = estimator_att[row]
|
||||
result.append(
|
||||
{
|
||||
"conformer_cnn_cache": cache["conformer_cnn_cache"][row : row + 1].detach().clone(),
|
||||
@@ -880,20 +1079,45 @@ class BatchedToken2Wav(nn.Module):
|
||||
)
|
||||
return result
|
||||
|
||||
def _stack_flow_cache(self, states: list[BatchedToken2WavState]) -> dict[str, torch.Tensor]:
|
||||
@staticmethod
|
||||
def _stack_estimator_att(rows: list[torch.Tensor]) -> torch.Tensor:
|
||||
"""Stack per-request ``(n_t, depth, 2, ...)`` caches into CFG order ``[cond x B | uncond x B]``."""
|
||||
return torch.cat((*[row[:, :, 0:1] for row in rows], *[row[:, :, 1:2] for row in rows]), dim=2)
|
||||
|
||||
def _stack_flow_cache(
|
||||
self,
|
||||
states: list[BatchedToken2WavState],
|
||||
*,
|
||||
include_estimator_att: bool = True,
|
||||
) -> dict[str, torch.Tensor | None]:
|
||||
"""Stack per-request flow caches for one batched call.
|
||||
|
||||
The estimator attention cache is by far the largest (~0.5 GiB per
|
||||
request in fp32). The Whole-Euler path reads it per request, so it
|
||||
skips stacking it (``include_estimator_att=False``).
|
||||
"""
|
||||
flows = [state.flow_cache for state in states]
|
||||
conditional_cnn = [flow["estimator_cnn_cache"][:, :, 0:1] for flow in flows]
|
||||
unconditional_cnn = [flow["estimator_cnn_cache"][:, :, 1:2] for flow in flows]
|
||||
conditional_att = [flow["estimator_att_cache"][:, :, 0:1] for flow in flows]
|
||||
unconditional_att = [flow["estimator_att_cache"][:, :, 1:2] for flow in flows]
|
||||
estimator_att = torch.cat((*conditional_att, *unconditional_att), dim=2)
|
||||
return {
|
||||
"conformer_cnn_cache": torch.cat([flow["conformer_cnn_cache"] for flow in flows], dim=0),
|
||||
"conformer_att_cache": torch.cat([flow["conformer_att_cache"] for flow in flows], dim=1),
|
||||
"estimator_cnn_cache": torch.cat((*conditional_cnn, *unconditional_cnn), dim=2),
|
||||
"estimator_att_cache": estimator_att,
|
||||
"estimator_att_cache": (
|
||||
self._stack_estimator_att([flow["estimator_att_cache"] for flow in flows])
|
||||
if include_estimator_att
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
def _whole_euler_active(self) -> bool:
|
||||
wrapper = self._whole_euler_graph_wrapper
|
||||
return wrapper is not None and wrapper.enabled and self._trt_stepper is None
|
||||
|
||||
def _whole_euler_ragged_active(self) -> bool:
|
||||
"""Whether Whole-Euler graphs also solve ragged (per-row length) batches."""
|
||||
return self._whole_euler_active() and self._whole_euler_graph_wrapper.ragged_body is not None
|
||||
|
||||
def _create_initial_states(
|
||||
self,
|
||||
features: PromptFeatures,
|
||||
@@ -913,12 +1137,15 @@ class BatchedToken2Wav(nn.Module):
|
||||
att_cache=None,
|
||||
)
|
||||
projected_speakers = self.flow.spk_embed_affine_layer(F.normalize(speakers, dim=1))
|
||||
# The prompt cache never reaches the trim length; ``att_keep`` only
|
||||
# tells the Whole-Euler arena the steady cache length to reserve.
|
||||
_, estimator_cnn, estimator_att = self._decode_cfm(
|
||||
hidden.transpose(1, 2).contiguous(),
|
||||
projected_speakers,
|
||||
prompt_mels.transpose(1, 2).contiguous(),
|
||||
cnn_cache=None,
|
||||
att_cache=None,
|
||||
att_keep=(int(prompt_mels.shape[1]), _CACHE_TRIM_SUFFIX),
|
||||
)
|
||||
flow_cache = {
|
||||
"conformer_cnn_cache": conformer_cnn,
|
||||
@@ -941,7 +1168,17 @@ class BatchedToken2Wav(nn.Module):
|
||||
]
|
||||
|
||||
def setup_batch(self, features: PromptFeatures, batch_size: int) -> list[BatchedToken2WavState]:
|
||||
"""Reuse read-only prompt conditioning for the same shape and padding."""
|
||||
"""Initial states for ``batch_size`` requests sharing one prompt.
|
||||
|
||||
Every request starts from the same prompt tokens, speaker, mels and
|
||||
noise, so the prompt is solved once and all requests share that one
|
||||
read-only state. Decoding writes a state only where Whole-Euler updates
|
||||
an estimator cache in place, which needs a cache held by one request
|
||||
and allocated with room for its new length; the prompt cache is shared
|
||||
and allocated exactly, and the first chunk always grows it. Solving it
|
||||
per batch size recomputed identical rows and cached one ~0.3 GiB
|
||||
estimator cache copy per request.
|
||||
"""
|
||||
bucket_frames = (
|
||||
self._cfm_graph_bucket_frames
|
||||
if self._cfm_graph_wrapper is not None and self._cfm_graph_wrapper.enabled
|
||||
@@ -949,17 +1186,17 @@ class BatchedToken2Wav(nn.Module):
|
||||
)
|
||||
# Capture the policy before setup: graph capture may disable the wrapper
|
||||
# after padding has already been chosen for these initial states.
|
||||
cache_key = (features.cache_key, batch_size, bucket_frames)
|
||||
cached = self._setup_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
cache_key = (features.cache_key, bucket_frames)
|
||||
state = self._setup_cache.get(cache_key)
|
||||
if state is not None:
|
||||
self._setup_cache.move_to_end(cache_key)
|
||||
return list(cached)
|
||||
states = self._create_initial_states(features, batch_size)
|
||||
if self._setup_cache_size > 0:
|
||||
self._setup_cache[cache_key] = tuple(states)
|
||||
while len(self._setup_cache) > self._setup_cache_size:
|
||||
self._setup_cache.popitem(last=False)
|
||||
return states
|
||||
else:
|
||||
(state,) = self._create_initial_states(features, 1)
|
||||
if self._setup_cache_size > 0:
|
||||
self._setup_cache[cache_key] = state
|
||||
while len(self._setup_cache) > self._setup_cache_size:
|
||||
self._setup_cache.popitem(last=False)
|
||||
return [state] * batch_size
|
||||
|
||||
@staticmethod
|
||||
def _fade_in_out(
|
||||
@@ -1058,7 +1295,12 @@ class BatchedToken2Wav(nn.Module):
|
||||
flush_encoder: bool = False,
|
||||
) -> tuple[list[torch.Tensor], list[BatchedToken2WavState]]:
|
||||
batch_size = int(tokens.shape[0])
|
||||
flow_cache = self._stack_flow_cache(states)
|
||||
# Whole-Euler reads and writes each request's estimator cache directly,
|
||||
# so the batch never holds a stacked copy of it.
|
||||
per_request_att = self._whole_euler_active()
|
||||
flow_cache = self._stack_flow_cache(states, include_estimator_att=not per_request_att)
|
||||
prompt_len = int(features.mels.shape[1])
|
||||
att_keep = (prompt_len, _CACHE_TRIM_SUFFIX)
|
||||
speakers = features.speaker_embedding.expand(batch_size, -1)
|
||||
with self._autocast(tokens.device):
|
||||
hidden, conformer_cnn, conformer_att = self._encode_chunk(
|
||||
@@ -1074,20 +1316,15 @@ class BatchedToken2Wav(nn.Module):
|
||||
projected_speakers,
|
||||
cond,
|
||||
cnn_cache=flow_cache["estimator_cnn_cache"],
|
||||
att_cache=flow_cache["estimator_att_cache"],
|
||||
att_cache=(
|
||||
[state.flow_cache["estimator_att_cache"] for state in states]
|
||||
if per_request_att
|
||||
else flow_cache["estimator_att_cache"]
|
||||
),
|
||||
att_keep=att_keep,
|
||||
)
|
||||
|
||||
prompt_len = int(features.mels.shape[1])
|
||||
if estimator_att.shape[4] > prompt_len + 100:
|
||||
estimator_att = torch.cat(
|
||||
(estimator_att[..., :prompt_len, :], estimator_att[..., -100:, :]),
|
||||
dim=4,
|
||||
)
|
||||
if conformer_att.shape[3] > prompt_len + 100:
|
||||
conformer_att = torch.cat(
|
||||
(conformer_att[..., :prompt_len, :], conformer_att[..., -100:, :]),
|
||||
dim=3,
|
||||
)
|
||||
conformer_att = _trim_streaming_cache(conformer_att, prompt_len)
|
||||
new_flow = self._split_flow_cache(
|
||||
{
|
||||
"conformer_cnn_cache": conformer_cnn,
|
||||
@@ -1096,6 +1333,7 @@ class BatchedToken2Wav(nn.Module):
|
||||
"estimator_att_cache": estimator_att,
|
||||
},
|
||||
batch_size,
|
||||
estimator_att_keep=att_keep,
|
||||
)
|
||||
old_mel = torch.cat([state.hift_cache["mel"] for state in states], dim=0)
|
||||
old_source = torch.cat([state.hift_cache["source"] for state in states], dim=0)
|
||||
@@ -1164,10 +1402,17 @@ class BatchedToken2Wav(nn.Module):
|
||||
)
|
||||
|
||||
max_frames = self._max_encode_token_frames(states)
|
||||
if any(int(row_tokens.numel()) > max_frames for row_tokens in tokens):
|
||||
per_request_att = self._whole_euler_ragged_active()
|
||||
if (self._whole_euler_active() and not per_request_att) or any(
|
||||
int(row_tokens.numel()) > max_frames for row_tokens in tokens
|
||||
):
|
||||
# decode_batch owns the RelPos-safe encoder slicing used by
|
||||
# non-async Talker dumps. Preserve that path for overlong rows;
|
||||
# ordinary short duplex chunks continue through one padded DiT.
|
||||
# non-async Talker dumps. Preserve that path for overlong rows.
|
||||
# Whole-Euler graphs that cannot run the ragged kernel also take
|
||||
# this path: under concurrency most batches hold some request's
|
||||
# short final chunk, and one padded eager DiT would run the whole
|
||||
# batch ungraphed on a stacked copy of every cache. Otherwise short
|
||||
# duplex chunks share one padded DiT solve, graphed when possible.
|
||||
exact_groups: dict[tuple[int, bool], list[int]] = {}
|
||||
for row, (row_tokens, last_chunk) in enumerate(zip(tokens, last_chunks, strict=True)):
|
||||
exact_groups.setdefault((int(row_tokens.numel()), last_chunk), []).append(row)
|
||||
@@ -1193,7 +1438,8 @@ class BatchedToken2Wav(nn.Module):
|
||||
conformer_att_rows: list[torch.Tensor | None] = [None] * batch_size
|
||||
for (_, last_chunk), rows in encoder_groups.items():
|
||||
group_states = [states[row] for row in rows]
|
||||
group_cache = self._stack_flow_cache(group_states)
|
||||
# The encoder reads only the conformer caches.
|
||||
group_cache = self._stack_flow_cache(group_states, include_estimator_att=False)
|
||||
group_tokens = torch.stack([tokens[row] for row in rows], dim=0)
|
||||
with self._autocast(group_tokens.device):
|
||||
hidden, conformer_cnn, conformer_att = self._encode_chunk(
|
||||
@@ -1219,7 +1465,10 @@ class BatchedToken2Wav(nn.Module):
|
||||
for row, hidden in enumerate(resolved_hidden):
|
||||
padded_hidden[row, : int(hidden.shape[1])].copy_(hidden[0])
|
||||
|
||||
flow_cache = self._stack_flow_cache(states)
|
||||
# A graphed ragged solve reads and writes each request's estimator
|
||||
# cache directly (trimmed on the way out), like _decode_batch_once.
|
||||
flow_cache = self._stack_flow_cache(states, include_estimator_att=not per_request_att)
|
||||
prompt_len = int(features.mels.shape[1])
|
||||
speakers = features.speaker_embedding.expand(batch_size, -1)
|
||||
with self._autocast(padded_hidden.device):
|
||||
projected_speakers = self.flow.spk_embed_affine_layer(F.normalize(speakers, dim=1))
|
||||
@@ -1229,33 +1478,24 @@ class BatchedToken2Wav(nn.Module):
|
||||
projected_speakers,
|
||||
cond,
|
||||
cnn_cache=flow_cache["estimator_cnn_cache"],
|
||||
att_cache=flow_cache["estimator_att_cache"],
|
||||
att_cache=(
|
||||
[state.flow_cache["estimator_att_cache"] for state in states]
|
||||
if per_request_att
|
||||
else flow_cache["estimator_att_cache"]
|
||||
),
|
||||
valid_lengths=hidden_lengths,
|
||||
att_keep=(prompt_len, _CACHE_TRIM_SUFFIX),
|
||||
)
|
||||
|
||||
prompt_len = int(features.mels.shape[1])
|
||||
assert isinstance(estimator_att, list)
|
||||
new_flow: list[dict[str, torch.Tensor]] = []
|
||||
for row, row_estimator_att in enumerate(estimator_att):
|
||||
conformer_cnn = conformer_cnn_rows[row]
|
||||
conformer_att = conformer_att_rows[row]
|
||||
assert conformer_cnn is not None and conformer_att is not None
|
||||
if row_estimator_att.shape[4] > prompt_len + 100:
|
||||
row_estimator_att = torch.cat(
|
||||
(
|
||||
row_estimator_att[..., :prompt_len, :],
|
||||
row_estimator_att[..., -100:, :],
|
||||
),
|
||||
dim=4,
|
||||
)
|
||||
if conformer_att.shape[3] > prompt_len + 100:
|
||||
conformer_att = torch.cat(
|
||||
(
|
||||
conformer_att[..., :prompt_len, :],
|
||||
conformer_att[..., -100:, :],
|
||||
),
|
||||
dim=3,
|
||||
)
|
||||
# A graphed solve already trimmed the estimator cache; trimming is idempotent.
|
||||
row_estimator_att = _trim_streaming_cache(row_estimator_att, prompt_len)
|
||||
conformer_att = _trim_streaming_cache(conformer_att, prompt_len)
|
||||
new_flow.append(
|
||||
{
|
||||
"conformer_cnn_cache": conformer_cnn.detach().clone(),
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -268,14 +268,34 @@ class MiniCPMO45Code2Wav(nn.Module):
|
||||
raise ValueError(f"Invalid MiniCPM-o connector chunk configuration: {self._connector_config}")
|
||||
raw_capture_batch_sizes = extra.get("hift_graph_capture_batch_sizes")
|
||||
capture_batch_sizes = [1] if raw_capture_batch_sizes is None else raw_capture_batch_sizes
|
||||
max_serial_batch = extra.get("max_serial_batch")
|
||||
max_serial_batch = 4 if max_serial_batch is None else int(max_serial_batch)
|
||||
self._hift_graph_config = {
|
||||
"enabled": bool(extra.get("enable_hift_graph", False)),
|
||||
"capture_batch_sizes": capture_batch_sizes,
|
||||
"max_serial_batch": max_serial_batch,
|
||||
}
|
||||
enable_whole_euler = extra.get("enable_whole_euler")
|
||||
max_graph_batch_raw = extra.get("max_graph_batch")
|
||||
max_graph_batch = int(max_graph_batch_raw) if max_graph_batch_raw is not None else None
|
||||
micro_batch_size_raw = extra.get("micro_batch_size")
|
||||
if micro_batch_size_raw is not None:
|
||||
micro_batch_size = int(micro_batch_size_raw)
|
||||
else:
|
||||
# The Whole-Euler arena reserves one attention cache per micro-batch
|
||||
# row, so size it for the most requests this stage ever batches.
|
||||
max_num_seqs = getattr(getattr(vllm_config, "scheduler_config", None), "max_num_seqs", None)
|
||||
micro_batch_size = min(int(max_num_seqs), max_graph_batch or 16) if max_num_seqs else None
|
||||
self._cfm_graph_config = {
|
||||
"enabled": bool(extra.get("enable_cfm_graph", False)),
|
||||
"max_graphs": int(extra.get("cfm_max_graphs", 32)),
|
||||
"bucket_frames": int(extra.get("cfm_graph_bucket_frames", 0)),
|
||||
"capture_frames": extra.get("cfm_graph_capture_frames"),
|
||||
"enable_whole_euler": enable_whole_euler is None or bool(enable_whole_euler),
|
||||
"max_serial_batch": max_serial_batch,
|
||||
"max_graph_batch": max_graph_batch,
|
||||
"micro_batch_size": micro_batch_size,
|
||||
"pad_max_rows": extra.get("whole_euler_pad_max_rows"),
|
||||
}
|
||||
self._ref_max_seconds = float(extra.get("ref_audio_max_seconds", _REF_MAX_SECONDS))
|
||||
if self._ref_max_seconds <= 0:
|
||||
|
||||
Reference in new issue
Block a user