VoxCPM-BACKUP_2 / docs /voxcpm_patch_flow.md
Jiwon-Kang's picture
Add files using upload-large-folder tool
9cd59fc verified
|
Raw
History Blame Contribute Delete
22.8 kB
# 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]` ν•œ 덩어리λ₯Ό 생성**ν•œλ‹€.
```text
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. 전체 νŒŒμ΄ν”„λΌμΈ λ‹€μ΄μ–΄κ·Έλž¨
```text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 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 쑰건 역할을 ν•œλ‹€.
```text
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`
```python
# 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`
```python
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`
```python
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`
```python
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`
```python
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`
```python
@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`
```python
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`
```python
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`
```python
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 λ‚΄λΆ€ `P` frame 을 μ–‘λ°©ν–₯ 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` 둜
톡일)을 λ‘λŠ” 편이 μ•ˆμ „ν•˜λ‹€.