VoxCPM patch_size νλ¦κ³Ό DiT μν€ν
μ²
λ³Έ λ¬Έμλ
/data/mm-llm-backbone_890/personal/sirius/VoxCPM/μ½λμμ νμΈν μ¬μ€μ κΈ°λ°μΌλ‘,patch_sizeκ° AudioVAE latent β LM β LocDiT β AudioVAE.decode κΉμ§ μ΄λ»κ² νλ₯΄λμ§λ₯Ό μ 리ν κ²μ΄λ€. SimWhisper-Codec μμ²΄κ° μλλΌ VoxCPM μ latent-AR + flow-matching diffusion ꡬ쑰μ λν λ ΈνΈμ΄λ©°, λ³Έ SimWhisper-Codec λ ν¬μaudiocodec/μλ μ§μ μ μΈ μ½λ 곡μ λ μλ€.
0. ν μ€ μμ½
patch_size(μ΄ν P)λ DiT κ° ν step μ μμΈ‘νλ AudioVAE latent frame κ°μλ€.
LM/Residual LM μ latent frame λ¨μκ° μλλΌ patch ν ν° λ¨μ(T_seq = ceil(T_vae / P))λ‘ λλ€.
DiT λ μ 체 μ€λμ€ μνμ€λ₯Ό ν λ²μ λ³΄μ§ μκ³ , κ° LM step λ§λ€ local patch [P, D] ν λ©μ΄λ¦¬λ₯Ό μμ±νλ€.
wav
-> AudioVAE.encode
-> latent [B, D, T_vae]
-> patching
-> audio_feats [B, T_seq, P, D]
-> feat_encoder -> [B, T_seq, lm_dim]
-> base_lm + residual_lm
-> dit_hidden [B, T_seq, dit_dim]
-> flatten [(B*T_seq), dit_dim] (ΞΌ for DiT)
-> LocDiT (flow-matching, in_channels=D, sequence length=P)
-> feat_pred_seq [(B*T_seq), D, P]
-> unpatch [B, D, T_seq*P] = [B, D, T_vae]
-> AudioVAE.decode
-> wav
1. ν μ shape cheat-sheet
| λ¨κ³ | shape | μμΉ |
|---|---|---|
| wav | [B, 1, T_wav] |
input |
| latent (VAE) | [B, D=1280, T_vae] (50 Hz) |
audio_whisper_vae.py:174 |
| patched feats | [B, T_seq, P, D] |
packers.py:51-68, voxcpm2.py:421-422 |
| feat_encoder μΆλ ₯ | [B, T_seq, lm_dim] |
local_encoder.py:7-30 |
| base_lm hidden | [B, T_seq, lm_dim] (shift-right) |
voxcpm2.py:325-331 |
| residual_lm hidden | [B, T_seq, lm_dim] (shift-right) |
voxcpm2.py:334-342 |
| dit_hidden (ΞΌ) | [(BΒ·T_seq), dit_dim] |
voxcpm2.py:344-345 |
| feat_gt / feat_cond | [(BΒ·T_seq), D, P] |
voxcpm2.py:348-352 |
| DiT noise z | [(BΒ·T_seq), D, P] |
unified_cfm.py:65 |
| DiT μΆλ ₯ | [(BΒ·T_seq), D, P] |
local_dit_v2.py:108-114 |
| unpatch κ²°κ³Ό | [B, D, T_seqΒ·P] = [B, D, T_vae] |
voxcpm2.py:382 |
| recon wav | [B, 1, T_wav] |
audio_whisper_vae.py:189 |
2. μ 체 νμ΄νλΌμΈ λ€μ΄μ΄κ·Έλ¨
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TRAINING / INFERENCE FORWARD β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
wav [B, 1, T_wav] AudioVAE = WhisperAudioVAE
β sample_rate = 16000
β hop_length = 320 ββ 50 Hz latent
β pad to multiple of patch_len chunk_size = 320 β
hopκ³Ό λμΌ
β train: patch_len = audio_vae.hop_length * P latent_dim = 1280
β packers.py:22
β infer: patch_len = self.patch_size * self.chunk_size
β voxcpm2.py:416
βΌ
AudioVAE.encode(wav, sr=16000) audio_whisper_vae.py:132-174
β feats = Whisper.encoder(mel).transpose(1,2)
βΌ
latent z : [B, D=1280, T_vae] (50 Hz)
β
β ββ PATCHING ββ
β train (packers + collate):
β feat = z.transpose(1,2) # [B, T_vae, D]
β view β [B, T_seq, P, D]
β infer (_encode_wav): voxcpm2.py:421-422
β feat.view(D, -1, P).permute(1,2,0) # [T_seq, P, D]
βΌ
audio_feats : [B, T_seq, P, D] T_seq = ceil(T_vae / P)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LM-side (patch-token sequence, T_seq tokens) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β audio_feats [B, T, P, D] β
β β β
β β feat_encoder = VoxCPMLocEnc local_encoder.py:7-30 β
β β β’ in_proj : Linear(D=64, H_lm) β
β β β’ prepend learnable [CLS] token β
β β β’ MiniCPMModel (bi-dir, is_causal=False) β
β β β’ take CLS: [(B*T), P+1, H] β [(B*T), H] β
β βΌ β
β feat_embed [B, T, H_lm] β enc_to_lm_proj β [B, T, lm_hidden] β
β β β
β β combined_embed = text_maskΒ·text_embed + audio_maskΒ·feat_embed β
β βΌ β
β base_lm (MiniCPM, is_causal=True) voxcpm2.py:325-326 β
β β enc_outputs [B, T, lm_dim] β
β β β³ fsq_layer on audio positions voxcpm2.py:327 β
β β β³ shift-right β lm_hidden β
β βΌ β
β residual path: voxcpm2.py:334-342 β
β residual_inputs = fusion_concat_proj([enc_outputs, audio_maskΒ·feat_embed]) β
β residual_lm(... is_causal=True) β residual_outputs [B, T, lm_dim] β
β shift-right β residual_hidden β
β β β
β βΌ β
β dit_hidden = cat( lm_to_dit_proj(lm_hidden), β
β res_to_dit_proj(residual_hidden) ) [B, T, dit_dim] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββ FLATTEN ββ voxcpm2.py:345-352
dit_hidden : [B, T, dit_dim] βββΊ [(B*T), dit_dim] (mu)
feat_gt : [B, T, P, D] βββΊ [(B*T), P, D] βα΅β [(B*T), D, P]
feat_cond : shift-right of audio_feats, same reshape [(B*T), D, P]
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DiT / Flow-matching (local, per-patch) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β UnifiedCFM.forward(mu, n_steps, P, cond, β¦) unified_cfm.py:54-79 β
β z = randn((b, in_channels=D, t=P)) β
β iterate t_span via solve_euler β
β β
β estimator = VoxCPMLocDiT local_dit_v2.py:50-116 β
β x [N, D, P] cond [N, D, P] mu [N, H] t,dt [N] β
β β β
β β in_proj : Linear(D=64 β H) on xα΅ β [N, P, H] β
β β cond_proj: Linear(D=64 β H) on condα΅ β [N, P, H] β
β β time emb : SinPosEmb(t) + SinPosEmb(dt) β time_mlp β [N, H] β
β β mu : view to [N, 1, H] β
β β seq : cat([mu, t_token, cond, x], dim=1) length 1+1+P+P β
β β MiniCPMModel(seq, is_causal=False) β
β β slice last P tokens β out_proj β [N, P, D] βα΅β [N, D, P] β
β βΌ β
β feat_pred_seq : [(B*T), D, P] β
β β
β Training loss: UnifiedCFM.compute_loss(x1=feat_gt, mu, cond) β
β unified_cfm.py:174-248 β
β y = (1-r)Β·z + rΒ·x1 v = x1 - z β
β mse(model(y,r,t), v) , masked by tgt_mask β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββ UNPATCH ββ voxcpm2.py:382
feat_pred_seq [(B*T), D, P]
β transpose to [(B*T), P, D]
β rearrange "(b t) p d -> b d (t p)"
β feat_pred : [B, D, T*P] (= [B, D, T_vae])
β
βΌ
AudioVAE.decode(feat_pred) audio_whisper_vae.py:176
feats = z.transpose(1,2).unsqueeze(2) # [B, T, 1, D]
recon = generator.inference(feats) # [B, 1, T_wav]
β
βΌ
Ε΅av [B, 1, T_wav]
3. LocDiT ν step λ΄λΆ (token λ¨μ)
mu λ ν κ° ν ν° μΌλ‘, timestep λ λ³λμ ν κ° ν ν° μΌλ‘ prepend λλ€. μΆκ°λ‘ μ΄μ patch
(feat_cond) κ° P ν ν°μ prefix λ‘ νμ ν¨κ» λ€μ΄κ°μ in-context 쑰건 μν μ νλ€.
Local sequence (length = 1 + 1 + P + P = 2 + 2P)
ββββββββββ¬βββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββ
input tokens ββ β ΞΌ β tΜ β cond[0..P-1] β x[0..P-1] β
β [1,H] β [1,H] β [P,H] β [P,H] β
ββββββββββ΄βββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββ
β β β β
LM hidden sinusoidal prefix noisy / current
(== ΞΌ) time emb (prev patch) sample x_r
+ Ξt emb
β bi-directional MiniCPM transformer β
βΌ
ββββββββββ¬βββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββ
output slice ββ β Β· β Β· β Β· β out[0..P-1] β β out_proj
ββββββββββ΄βββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββ
β
βΌ
[N, P, D] βα΅β [N, D, P]
= predicted velocity
4. ν΅μ¬ μ½λ μΈμ©
4.1 Patching (latent β audio_feats)
Training packer β VoxCPM/src/voxcpm/training/packers.py:22, 51-68
# packers.py:22
self.patch_len = audio_vae.hop_length * self.patch_size
# packers.py:51-68
def encode_audio(self, wav: torch.Tensor):
wav = wav.unsqueeze(0).unsqueeze(1) # [1, 1, T]
if wav.size(-1) % self.patch_len != 0:
padding_size = self.patch_len - wav.size(-1) % self.patch_len
wav = torch.nn.functional.pad(wav, (0, padding_size))
with torch.no_grad():
z = self.audio_vae.encode(wav, self.audio_vae.sample_rate) # [1, D, T']
feat = z.transpose(1, 2) # [1, T', D]
return feat
Inference path β VoxCPM/src/voxcpm/model/voxcpm2.py:416-422
patch_len = self.patch_size * self.chunk_size
if audio.size(1) % patch_len != 0:
padding_size = patch_len - audio.size(1) % patch_len
pad = (padding_size, 0) if padding_mode == "left" else (0, padding_size)
audio = torch.nn.functional.pad(audio, pad)
feat = self.audio_vae.encode(audio.to(self.device), self._encode_sample_rate).cpu()
return feat.view(self.audio_vae.latent_dim, -1, self.patch_size).permute(1, 2, 0) # [T_seq, P, D]
4.2 feat_encoder (VoxCPMLocEnc)
VoxCPM/src/voxcpm/modules/locenc/local_encoder.py:7-30
class VoxCPMLocEnc(nn.Module):
def __init__(self, config: MiniCPM4Config, input_dim: int = 64):
super().__init__()
self.special_token = nn.Parameter(torch.randn(1, 1, 1, config.hidden_size))
self.in_proj = nn.Linear(input_dim, config.hidden_size, bias=True)
self.encoder = MiniCPMModel(config)
def forward(self, x): # x: [B, T, P, D]
B, T, P, D = x.shape
x = self.in_proj(x) # [B, T, P, H]
special_tokens = self.special_token.expand(B, T, 1, -1)
x = torch.cat([special_tokens, x], dim=2)
x = rearrange(x, "b t p c -> (b t) p c")
outputs, _ = self.encoder(x, is_causal=False)
cls_output = outputs[:, 0, :]
return rearrange(cls_output, "(b t) c -> b t c", b=B)
β λ¨μ Linear κ° μλλΌ mini-transformer + [CLS] λ€.
4.3 LM + Residual LM
VoxCPM/src/voxcpm/model/voxcpm2.py:319-345
B, T, P, D = audio_feats.shape
feat_embed = self.feat_encoder(audio_feats) # [B, T, H_enc]
feat_embed = self.enc_to_lm_proj(feat_embed) # [B, T, lm_dim]
text_embed = self.base_lm.embed_tokens(text_tokens) * scale_emb
combined_embed = text_mask.unsqueeze(-1) * text_embed + audio_mask.unsqueeze(-1) * feat_embed
enc_outputs, _ = self.base_lm(inputs_embeds=combined_embed, is_causal=True)
enc_outputs = self.fsq_layer(enc_outputs) * audio_mask.unsqueeze(-1) \
+ enc_outputs * text_mask.unsqueeze(-1)
lm_hidden = torch.cat((torch.zeros_like(enc_outputs[:, 0:1, :]), enc_outputs[:, :-1, :]), dim=1)
residual_inputs = self.fusion_concat_proj(
torch.cat((enc_outputs, audio_mask.unsqueeze(-1) * feat_embed), dim=-1)
)
residual_outputs, _ = self.residual_lm(inputs_embeds=residual_inputs, is_causal=True)
residual_hidden = torch.cat(
(torch.zeros_like(residual_outputs[:, 0:1, :]), residual_outputs[:, :-1, :]), dim=1,
)
dit_hidden = torch.cat(
(self.lm_to_dit_proj(lm_hidden), self.res_to_dit_proj(residual_hidden)), dim=-1
)
dit_hidden = rearrange(dit_hidden, "b t c -> (b t) c") # [(B*T), dit_dim]
4.4 DiT μ λ ₯ λ§λ€κΈ°
VoxCPM/src/voxcpm/model/voxcpm2.py:348-358
feat_gt = rearrange(audio_feats.to(target_dtype), "b t p d -> (b t) p d")
feat_cond = torch.cat(
(torch.zeros_like(audio_feats[:, 0:1, ...]), audio_feats[:, :-1, ...]), dim=1,
)
feat_cond = rearrange(feat_cond.to(target_dtype), "b t p d -> (b t) p d")
loss_seq_mask = loss_mask.unsqueeze(-1).repeat(1, 1, self.patch_size)
loss_seq_mask = rearrange(loss_seq_mask, "b t p -> (b t) p 1").to(target_dtype)
4.5 UnifiedCFM β noise/sampling
VoxCPM/src/voxcpm/modules/locdit/unified_cfm.py:54-79
@torch.inference_mode()
def forward(self, mu, n_timesteps, patch_size, cond, ...):
b, _ = mu.shape
t = patch_size
z = torch.randn((b, self.in_channels, t),
device=mu.device, dtype=mu.dtype) * temperature
t_span = torch.linspace(1, 0, n_timesteps + 1, device=mu.device, dtype=mu.dtype)
t_span = t_span + sway_sampling_coef * (torch.cos(torch.pi / 2 * t_span) - 1 + t_span)
return self.solve_euler(x=z, t_span=t_span, mu=mu, cond=cond, ...)
νμ΅ μμ€ β unified_cfm.py:174-248 μ compute_loss(x1, mu, cond, tgt_mask, progress).
4.6 LocDiT (VoxCPMLocDiT)
VoxCPM/src/voxcpm/modules/locdit/local_dit_v2.py:50-116
def forward(self, x, mu, t, cond, dt):
"""
x: [N, C, P] noisy / current sample
mu: [N, C] LM hidden
t: [N] diffusion timestep
cond: [N, C, P] previous-patch condition
dt: [N] delta for mean velocity
"""
x = self.in_proj(x.transpose(1, 2).contiguous()) # [N, P, H]
cond = self.cond_proj(cond.transpose(1, 2).contiguous())# [N, P, H]
prefix = cond.size(1)
t = self.time_mlp(self.time_embeddings(t).to(x.dtype))
dt = self.delta_time_mlp(self.time_embeddings(dt).to(x.dtype))
t = t + dt
mu = mu.view(x.size(0), -1, x.size(-1)) # [N, 1, H]
x = torch.cat([mu, t.unsqueeze(1), cond, x], dim=1) # [N, 1+1+P+P, H]
hidden, _ = self.decoder(x, is_causal=False)
hidden = hidden[:, prefix + mu.size(1) + 1:, :]
hidden = self.out_proj(hidden) # [N, P, D]
return hidden.transpose(1, 2).contiguous() # [N, D, P]
4.7 Unpatch
VoxCPM/src/voxcpm/model/voxcpm2.py:382
feat_pred = rearrange(
feat_pred_seq.transpose(1, 2),
"(b t) d p -> b d (t p)",
b=B, p=self.patch_size,
) # [B, D, T_vae]
μ€νΈλ¦¬λ° inference μμλ ν step λ¨μ [B, 1, P, D] β [B, D, P] (voxcpm2.py:1083),
non-streaming μμλ T_seq λ§νΌ λͺ¨μ λ€ ν λ²μ (b t) p d -> b d (t p) (voxcpm2.py:1106).
4.8 WhisperAudioVAE
VoxCPM/src/voxcpm/modules/audiovae/audio_whisper_vae.py:28-87
self.sample_rate = 16000
self.hop_length = 320 # 16000 / 50 Hz Whisper latent
self.chunk_size = 320 # β
VoxCPM μ patch_len κ³μ°μμ hop_length μ λμΌν΄μΌ ν¨
self.latent_dim = 1280 # Whisper-large hidden size
self.max_audio_samples = self.sample_rate * 30 # SimWhisper extractor 30s νκ³
encode λ [B, D=1280, T_lat], decode λ [B, 1, T_wav] λ₯Ό λλ €μ€λ€.
5. chunk_size vs hop_length μ 리
| μμΉ | μμ | μ¬μ© attribute |
|---|---|---|
Training packer (packers.py:22) |
patch_len = audio_vae.hop_length * patch_size |
hop_length |
Inference _encode_wav (voxcpm2.py:416) |
patch_len = self.patch_size * self.chunk_size |
chunk_size |
β λ κ²½λ‘κ° κ°μ μλ―Έ("VAE ν frame λΉ raw sample μ Γ P")μ¬μΌ νλ―λ‘ chunk_size == hop_length μ¬μΌ ν¨.
WhisperAudioVAE μμλ λ λ€ 320 μΌλ‘ νλμ½λλμ΄ μμ΄ μ€ν¨μ μΌλ‘ μΌμΉ
(audio_whisper_vae.py:84-85).
30 μ΄ μ νμ chunk_size κ° μλλΌ λ³λ max_audio_samples = sample_rate * 30 μ΄λ€. μλ―Έκ°
λ€λ₯Έ λ κ°μ νλμ λ¬Άμ§ μλλ‘ μ£Όμ.
6. μ¬μ©μ μμ½ vs μ€μ μ½λ β μ§μ΄λ μ
feat_encoderλ Linear κ° μλλΌ mini-transformer + CLS μ΄λ€ (VoxCPMLocEnc). patch λ΄λΆPframe μ μλ°©ν₯ self-attention μΌλ‘ μμ λ€[CLS]ν ν°λ§ λΉΌμ[B, T, lm_dim]μ λ§λ λ€.- Residual LM μ base LM κ³Ό λ³κ° λͺ¨λ (
voxcpm2.py:334-342). base LM μΆλ ₯ + audio embed λ₯Όfusion_concat_projμΌλ‘ ν©μ³ λ€μ causal LM ν λ² λ λλ €μdit_hiddenμ μ λ°μ λ§λ€κ³ , λλ¨Έμ§ μ λ°μ base LM μΆλ ₯. λ hidden μ μ±λ μΆμΌλ‘ concat νμ¬ μ΅μ’dit_dimμ μ±μ΄λ€. - DiT prefix λ
[ΞΌ, tΜ, cond, x]λ‘ 4 μ’ λ₯ ν ν° μ΄ prepend λλ€.ΞΌμtimestepμ ν©μ³μ§ λ¨μΌ ν ν°μ΄ μλλΌ λ³κ°μ λ ν ν°. μΆκ°λ‘ μ΄μ patch(feat_cond) κ°Pκ° prefix ν ν°μΌλ‘ λ€μ΄κ°μ in-context conditioning μ μν. - λ
Έμ΄μ¦
zλrandn((b, in_channels=D, P))β DiT μ "local time" κΈΈμ΄λ μ ννpatch_sizeμ΄λ€. chunk_size == hop_lengthλμΉλ WhisperAudioVAE μ νμ¬ νλμ½λ μμλ§ μ±λ¦½. μ AudioVAE λ₯Ό λΆμΌ λλ λ κ°μ΄ λΆλ¦¬λμ§ μλλ‘ λ¨μΈλ¬Έ(λλchunk_size = hop_lengthλ‘ ν΅μΌ)μ λλ νΈμ΄ μμ νλ€.