| # 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` λ‘ |
| ν΅μΌ)μ λλ νΈμ΄ μμ νλ€. |
| |