Download export/_phonon.py from executorch-community/Phonon-31M-EN-ExecuTorch-WebGPU: direct link, hf CLI and curl.
- Browser
- Download file 34.7 kB
-
https://huggingface.co/executorch-community/Phonon-31M-EN-ExecuTorch-WebGPU/resolve/main/export/_phonon.py
- Command line
-
hf download hf://executorch-community/Phonon-31M-EN-ExecuTorch-WebGPU/export/_phonon.py
-
curl -L -o _phonon.py https://huggingface.co/executorch-community/Phonon-31M-EN-ExecuTorch-WebGPU/resolve/main/export/_phonon.py
34.7 kB
| """Phonon (gradium/phonon-31m-en) building blocks for recipes/phonon-31m-en.py: weights, eager reference, export programs. | |
| The eager reference follows the official runtime (the `ptts` crate, src/{flow_lm,tts_model, | |
| transformer,mlp,conditioners,mimi,seanet,conv}.rs) operation by operation: | |
| voice speaker_wavs [1,512,125] -> transpose -> speaker_proj (512x512, no bias) -> [1,125,512] | |
| prime transformer(voice) then transformer(text_embed(tokens)), both written to the KV cache | |
| step x = input_linear(prev latent or bos_emb) + cond (cond = num_speakers("1") LUT | |
| + duration_delta(0.0) sinusoid, each through its output_proj) | |
| h = out_norm(transformer(x))[:, -1]; eos_logit = out_eos(h) | |
| latent = noise + flow_net(h, s=0, t=1, noise) (LSD, 1 step; noise ~ N(0, temp)) | |
| decode (latent * emb_std + emb_mean) -> 1x1 conv 32->512 -> depthwise convtr x16 -> 2-layer | |
| transformer (window 250, layer scale) -> SEANet decoder (ratios 6,5,4) -> 1920 samples | |
| Export programs (WebGPU): | |
| FlowLMProgram forward(embeds[1,S,512] S<=128, input_pos[1] i64, noise[1,32]) | |
| -> latent[1,32], eos_logit[1,1], next_embeds[1,1,512] (KV cache in the program) | |
| MimiStepProgram forward(latent[1,1,32], *state) -> pcm[1,1,1920], *new_state (state explicit) | |
| Rewrites for the public WebGPU runtime (same function, checked against the eager reference): | |
| * RoPE: q/k rows of in_proj permuted from interleaved pairs to halves, so the runtime's fused | |
| HF RoPE (et_vk.apply_rotary_emb_hf) applies; q.k is unchanged. | |
| * flow-LM LayerNorm (dynamic S): mean as a 1/512 linear + the rms_norm pattern, because | |
| native_layer_norm has no resize hook and mean.dim's resize drops a keepdim'ed dim. | |
| * ELU = relu(x) + exp(min(x, 0)) - 1 and SiLU = x * sigmoid(x) (no aten.elu / aten.silu). | |
| * ConvTranspose1d as a 4-D (H = 1) conv_transpose2d (3-D convolution has no transposed path). | |
| * Mimi streaming state as explicit tensors: mutable buffers other than the llama KV cache are | |
| not kept live by the delegate. | |
| """ | |
| import math | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| D = 512 | |
| H = 8 | |
| HD = 64 | |
| LDIM = 32 | |
| FLOW_LAYERS = 6 | |
| MIMI_LAYERS = 2 | |
| MIMI_CONTEXT = 250 | |
| FRAME = 1920 # samples per latent frame at 24 kHz | |
| UPS = 16 # mimi transformer tokens per latent frame | |
| NEG = -1.0e30 | |
| # --------------------------------------------------------------------------------------------- | |
| # Weights: a module whose state_dict keys are exactly the checkpoint's keys. | |
| # --------------------------------------------------------------------------------------------- | |
| def _p(*shape): | |
| return nn.Parameter(torch.zeros(*shape), requires_grad=False) | |
| class _Lin(nn.Module): | |
| def __init__(self, i, o, bias): | |
| super().__init__() | |
| self.weight = _p(o, i) | |
| if bias: | |
| self.bias = _p(o) | |
| class _LN(nn.Module): | |
| def __init__(self, n): | |
| super().__init__() | |
| self.weight = _p(n) | |
| self.bias = _p(n) | |
| class _Attn(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.in_proj = _Lin(D, 3 * D, False) | |
| self.out_proj = _Lin(D, D, False) | |
| class _Scale(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.scale = _p(D) | |
| class _Layer(nn.Module): | |
| def __init__(self, layer_scale): | |
| super().__init__() | |
| self.self_attn = _Attn() | |
| self.norm1 = _LN(D) | |
| self.norm2 = _LN(D) | |
| self.linear1 = _Lin(D, 2048, False) | |
| self.linear2 = _Lin(2048, D, False) | |
| if layer_scale: | |
| self.layer_scale_1 = _Scale() | |
| self.layer_scale_2 = _Scale() | |
| class _Transformer(nn.Module): | |
| def __init__(self, n, layer_scale): | |
| super().__init__() | |
| self.layers = nn.ModuleList(_Layer(layer_scale) for _ in range(n)) | |
| class _Seq(nn.Module): | |
| """nn.Sequential-style numbered children with only the indices that hold weights.""" | |
| def __init__(self, items): | |
| super().__init__() | |
| for k, m in items.items(): | |
| self.add_module(str(k), m) | |
| def __getitem__(self, i): | |
| return getattr(self, str(i)) | |
| class _TimeEmbed(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.freqs = _p(128) | |
| self.mlp = _Seq({0: _Lin(256, D, True), 2: _Lin(D, D, True), 3: _Alpha()}) | |
| class _Alpha(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.alpha = _p(D) | |
| class _ResBlock(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.in_ln = _LN(D) | |
| self.mlp = _Seq({0: _Lin(D, D, True), 2: _Lin(D, D, True)}) | |
| self.adaLN_modulation = _Seq({1: _Lin(D, 3 * D, True)}) | |
| class _Final(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.linear = _Lin(D, LDIM, True) | |
| self.adaLN_modulation = _Seq({1: _Lin(D, 2 * D, True)}) | |
| class _FlowNet(nn.Module): | |
| def __init__(self, depth=6): | |
| super().__init__() | |
| self.time_embed = nn.ModuleList([_TimeEmbed(), _TimeEmbed()]) | |
| self.cond_embed = _Lin(D, D, True) | |
| self.input_proj = _Lin(LDIM, D, True) | |
| self.res_blocks = nn.ModuleList(_ResBlock() for _ in range(depth)) | |
| self.final_layer = _Final() | |
| class _Embed(nn.Module): | |
| def __init__(self, n, d): | |
| super().__init__() | |
| self.weight = _p(n, d) | |
| class _Cond(nn.Module): | |
| def __init__(self, embed=None, proj_in=None): | |
| super().__init__() | |
| self.learnt_padding = _p(1, 1, D) | |
| if embed is not None: | |
| self.embed = _Embed(*embed) | |
| if proj_in is not None: | |
| self.output_proj = _Lin(proj_in, D, False) | |
| class _Conditioners(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.duration_delta = _Cond(proj_in=128) | |
| self.num_speakers = _Cond(embed=(32, 16), proj_in=16) | |
| self.speaker_wavs = _Cond(proj_in=512) | |
| self.transcript_in_segment = _Cond(embed=(4001, D)) | |
| class _CondProvider(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.conditioners = _Conditioners() | |
| class _WST(nn.Module): | |
| """flow.w_s_t: a training-time loss-weighting net; the runtime drops it (loader.rs remap_key).""" | |
| def __init__(self): | |
| super().__init__() | |
| self.w_s_t = _Seq( | |
| { | |
| 0: _Lin(2, 32, True), | |
| 3: _Lin(32, 32, True), | |
| 6: _Lin(32, 32, True), | |
| 9: _Lin(32, 1, True), | |
| } | |
| ) | |
| class FlowLMWeights(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.bos_emb = _p(LDIM) | |
| self.emb_mean = _p(LDIM) | |
| self.emb_std = _p(LDIM) | |
| self.condition_provider = _CondProvider() | |
| self.flow = _WST() | |
| self.flow_net = _FlowNet() | |
| self.input_linear = _Lin(LDIM, D, False) | |
| self.out_eos = _Lin(D, 1, True) | |
| self.out_norm = _LN(D) | |
| self.transformer = _Transformer(FLOW_LAYERS, False) | |
| class _Conv(nn.Module): | |
| def __init__(self, i, o, k): | |
| super().__init__() | |
| self.conv = _ConvW(i, o, k, bias=True) | |
| class _ConvW(nn.Module): | |
| def __init__(self, i, o, k, bias): | |
| super().__init__() | |
| self.weight = _p(o, i, k) | |
| if bias: | |
| self.bias = _p(o) | |
| class _ConvTr(nn.Module): | |
| def __init__(self, i, o, k): | |
| super().__init__() | |
| self.convtr = _ConvTrW(i, o, k, True) | |
| class _ConvTrW(nn.Module): | |
| def __init__(self, i, o_per_group, k, bias): | |
| super().__init__() | |
| self.weight = _p(i, o_per_group, k) | |
| if bias: | |
| self.bias = _p(o_per_group) | |
| class _Res(nn.Module): | |
| def __init__(self, dim): | |
| super().__init__() | |
| self.block = _Seq({1: _Conv(dim, dim // 2, 3), 3: _Conv(dim // 2, dim, 1)}) | |
| class _SEANetDecoder(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.model = _Seq( | |
| { | |
| 0: _Conv(512, 512, 7), | |
| 2: _ConvTr(512, 256, 12), | |
| 3: _Res(256), | |
| 5: _ConvTr(256, 128, 10), | |
| 6: _Res(128), | |
| 8: _ConvTr(128, 64, 8), | |
| 9: _Res(64), | |
| 11: _Conv(64, 1, 3), | |
| } | |
| ) | |
| class _ProjT(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.transformer = _Transformer(MIMI_LAYERS, True) | |
| class _Quantizer(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.output_proj = _ConvW(LDIM, D, 1, bias=False) | |
| class _Upsample(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.convtr = _Seq({}) | |
| self.convtr.convtr = _ConvTrW(D, 1, 2 * UPS, False) | |
| class MimiWeights(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.decoder = _SEANetDecoder() | |
| self.decoder_transformer = _ProjT() | |
| self.quantizer = _Quantizer() | |
| self.upsample = _Upsample() | |
| class PhononWeights(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.flow_lm = FlowLMWeights() | |
| self.mimi = MimiWeights() | |
| def load_weights(safetensors_path): | |
| """Every checkpoint tensor, as float32 (bf16 -> f32 is exact), loaded with strict=True.""" | |
| from safetensors.torch import load_file | |
| sd = {k: v.float() for k, v in load_file(str(safetensors_path)).items()} | |
| w = PhononWeights() | |
| w.load_state_dict(sd, strict=True) | |
| return w.eval() | |
| def load_voice(path): | |
| """speaker_wavs [1, 512, T] (stored speaker-Mimi latents) from a voice .safetensors file.""" | |
| from safetensors.torch import load_file | |
| t = load_file(str(path)) | |
| assert list(t) == ["speaker_wavs"], list(t) | |
| return t["speaker_wavs"].float() | |
| # --------------------------------------------------------------------------------------------- | |
| # Shared math | |
| # --------------------------------------------------------------------------------------------- | |
| def lin(m, x): | |
| """nn.Linear on a 2-D view (the public WebGPU runtime's fp32 aten.linear takes 2-D input).""" | |
| shp = x.shape | |
| y = F.linear(x.reshape(-1, shp[-1]), m.weight, getattr(m, "bias", None)) | |
| return y.reshape(*shp[:-1], y.shape[-1]) | |
| def lin3(m, x): | |
| """nn.Linear on the tensor as it is (any rank); the Vulkan lowering adds its own 2-D views.""" | |
| return F.linear(x, m.weight, getattr(m, "bias", None)) | |
| def layer_norm_dyn(m, x, eps, mean_w): | |
| """LayerNorm for the dynamic-length flow-LM program, from ops that follow a resized sequence | |
| length on the public WebGPU runtime (its native_layer_norm has no resize hook, and its mean.dim | |
| resize drops a keepdim'ed dim): the mean is a linear with weight 1/512 (exact: a power of two), | |
| the rest matches the Vulkan rms_norm pattern (fused into et_vk.rms_norm).""" | |
| xc = x - F.linear(x, mean_w) | |
| return xc * torch.rsqrt((xc * xc).mean(-1, keepdim=True) + eps) * m.weight + m.bias | |
| def layer_norm(m, x, eps): | |
| return F.layer_norm(x, (x.shape[-1],), m.weight, m.bias, eps) | |
| def silu(x): | |
| return x * torch.sigmoid(x) | |
| def elu(x): | |
| """ELU(alpha=1) from ops the public WebGPU runtime registers (it has no aten.elu).""" | |
| return torch.relu(x) + torch.exp(torch.clamp(x, max=0.0)) - 1.0 | |
| def gelu(x): | |
| return F.gelu(x) # erf | |
| def inv_freq(): | |
| """RoPE frequencies as the runtime computes them (rope.rs): 1 / max_period^(i / half) in f32.""" | |
| half = HD // 2 | |
| i = torch.arange(half, dtype=torch.float32) | |
| return 1.0 / torch.pow(torch.tensor(10000.0, dtype=torch.float32), i / half) | |
| def rope_interleaved(x, pos): | |
| """rope_i on [B, T, H, HD] with absolute positions pos [T] (the runtime's convention: | |
| pairs (2i, 2i+1) rotate by pos * inv_freq[i]).""" | |
| ang = pos.to(torch.float32)[:, None] * inv_freq()[None, :] # [T, half] | |
| c, s = torch.cos(ang)[None, :, None, :], torch.sin(ang)[None, :, None, :] | |
| xr, xi = x[..., 0::2], x[..., 1::2] | |
| return torch.stack([xr * c - xi * s, xr * s + xi * c], dim=-1).flatten(-2) | |
| def time_embed_rms(te, t): | |
| """TimestepEmbedder with TimeRmsNorm::Rms (phonon config time_rms_norm: rms).""" | |
| args = t * te.freqs | |
| emb = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) | |
| x = lin(te.mlp[2], silu(lin(te.mlp[0], emb))) | |
| ms = (x * x).mean(-1, keepdim=True) | |
| return x * torch.rsqrt(ms + 1e-5) * te.mlp[3].alpha | |
| def flow_net(fn, c, x, t_comb): | |
| """SimpleMLPAdaLN: c [N, 512] backbone output, x [N, 32] noise, t_comb [N or 1, 512].""" | |
| x = lin(fn.input_proj, x) | |
| y = t_comb + lin(fn.cond_embed, c) | |
| sy = silu(y) | |
| for b in fn.res_blocks: | |
| ada = lin(b.adaLN_modulation[1], sy) | |
| shift, scale, gate = ada[..., :D], ada[..., D : 2 * D], ada[..., 2 * D :] | |
| h = layer_norm(b.in_ln, x, 1e-6) * (scale + 1.0) + shift | |
| h = lin(b.mlp[2], silu(lin(b.mlp[0], h))) | |
| x = x + gate * h | |
| fl = fn.final_layer | |
| ada = lin(fl.adaLN_modulation[1], sy) | |
| shift, scale = ada[..., :D], ada[..., D:] | |
| x = F.layer_norm(x, (D,), None, None, 1e-6) * (scale + 1.0) + shift | |
| return lin(fl.linear, x) | |
| def lsd_time(fn): | |
| """(time_embed[0](s=0) + time_embed[1](t=1)) / 2, the flow head's constant time condition | |
| for lsd_decode_steps = 1.""" | |
| s = torch.zeros(1, 1) | |
| t = torch.ones(1, 1) | |
| return ( | |
| time_embed_rms(fn.time_embed[0], s) + time_embed_rms(fn.time_embed[1], t) | |
| ) / 2 | |
| def sin_embedding(position, dim, max_period): | |
| half = dim // 2 | |
| i = torch.arange(half, dtype=torch.float32) | |
| ph = position / torch.pow( | |
| torch.tensor(max_period, dtype=torch.float32), i / (half - 1) | |
| ) | |
| return torch.cat([torch.cos(ph), torch.sin(ph)]) | |
| def summed_conditions(flw, num_speakers="1", duration_delta=0.0): | |
| """conditioners.rs Conditioners::sum with the runtime's defaults, [1, 1, 512].""" | |
| c = flw.condition_provider.conditioners | |
| idx = ["0", "1", "2"].index(num_speakers) | |
| ns = F.linear( | |
| c.num_speakers.embed.weight[idx : idx + 1], c.num_speakers.output_proj.weight | |
| ) | |
| dd = F.linear( | |
| sin_embedding(1000.0 * duration_delta, 128, 10000.0)[None], | |
| c.duration_delta.output_proj.weight, | |
| ) | |
| return (ns + dd).view(1, 1, D) | |
| def voice_embedding(flw, speaker_wavs): | |
| """[1, 512, T] speaker latents -> [1, T, 512] voice prompt (loader.rs project_latents).""" | |
| w = flw.condition_provider.conditioners.speaker_wavs.output_proj.weight | |
| return F.linear(speaker_wavs.transpose(1, 2), w) | |
| def text_embedding(flw, tokens): | |
| emb = flw.condition_provider.conditioners.transcript_in_segment.embed.weight | |
| return emb[torch.as_tensor(tokens, dtype=torch.long)][None] | |
| # --------------------------------------------------------------------------------------------- | |
| # Eager reference (Python-side state, mirrors the runtime) | |
| # --------------------------------------------------------------------------------------------- | |
| class FlowLMRef: | |
| def __init__(self, w: PhononWeights, num_speakers="1", duration_delta=0.0): | |
| self.w = w.flow_lm | |
| self.cond = summed_conditions(self.w, num_speakers, duration_delta) | |
| self.t_comb = lsd_time(self.w.flow_net) | |
| self.reset() | |
| def reset(self): | |
| self.k = [None] * FLOW_LAYERS | |
| self.v = [None] * FLOW_LAYERS | |
| self.pos = 0 | |
| def backbone(self, x): | |
| B, T, _ = x.shape | |
| pos = torch.arange(self.pos, self.pos + T) | |
| for i, L in enumerate(self.w.transformer.layers): | |
| h = layer_norm(L.norm1, x, 1e-5) | |
| qkv = lin(L.self_attn.in_proj, h).view(B, T, 3, H, HD) | |
| q, k, v = qkv[:, :, 0], qkv[:, :, 1], qkv[:, :, 2] | |
| q, k = rope_interleaved(q, pos), rope_interleaved(k, pos) | |
| self.k[i] = k if self.k[i] is None else torch.cat([self.k[i], k], 1) | |
| self.v[i] = v if self.v[i] is None else torch.cat([self.v[i], v], 1) | |
| kk, vv = self.k[i].transpose(1, 2), self.v[i].transpose(1, 2) | |
| qq = q.transpose(1, 2) | |
| Tk = kk.shape[2] | |
| qpos = torch.arange(Tk - T, Tk)[:, None] | |
| mask = torch.where(torch.arange(Tk)[None, :] <= qpos, 0.0, float("-inf")) | |
| att = torch.softmax(qq @ kk.transpose(-1, -2) / math.sqrt(HD) + mask, -1) | |
| o = (att @ vv).transpose(1, 2).reshape(B, T, D) | |
| x = x + lin(L.self_attn.out_proj, o) | |
| h = layer_norm(L.norm2, x, 1e-5) | |
| x = x + lin(L.linear2, gelu(lin(L.linear1, h))) | |
| self.pos += T | |
| return x | |
| def prompt(self, embeds): | |
| self.backbone(embeds) | |
| def step(self, prev_latent, noise): | |
| """prev_latent None (BOS) or [1, 1, 32]; noise [1, 32]. -> latent [1,1,32], eos_logit.""" | |
| seq = self.w.bos_emb.view(1, 1, LDIM) if prev_latent is None else prev_latent | |
| x = lin(self.w.input_linear, seq) + self.cond | |
| h = layer_norm(self.w.out_norm, self.backbone(x), 1e-5)[:, -1] | |
| eos = lin(self.w.out_eos, h) | |
| latent = noise + flow_net(self.w.flow_net, h, noise, self.t_comb) | |
| return latent.view(1, 1, LDIM), eos | |
| def _conv1d_stream(m, x, prev, dilation=1): | |
| xx = torch.cat([prev, x], 2) if prev is not None else x | |
| y = F.conv1d(xx, m.conv.weight, m.conv.bias, dilation=dilation) | |
| tp = prev.shape[2] if prev is not None else 0 | |
| return y, (xx[:, :, xx.shape[2] - tp :] if tp else None) | |
| def _convtr_stream(w, bias, x, partial, stride, groups=1): | |
| y = F.conv_transpose1d(x, w, bias, stride=stride, groups=groups) | |
| pt = partial.shape[2] | |
| y = torch.cat([y[:, :, :pt] + partial, y[:, :, pt:]], 2) | |
| new_partial = y[:, :, y.shape[2] - pt :] | |
| if bias is not None: | |
| new_partial = new_partial - bias.view(1, -1, 1) | |
| return y[:, :, : y.shape[2] - pt], new_partial | |
| class MimiRef: | |
| """Streaming Mimi decoder (MimiDecoder::decode_from_latent_step), one or more frames per call.""" | |
| def __init__(self, w: PhononWeights): | |
| self.w = w.mimi | |
| self.emb_std = w.flow_lm.emb_std | |
| self.emb_mean = w.flow_lm.emb_mean | |
| self.reset() | |
| def reset(self): | |
| z = lambda *s: torch.zeros(1, *s) # noqa: E731 | |
| self.up = z(D, UPS) | |
| self.kv = [[None, None] for _ in range(MIMI_LAYERS)] | |
| self.offset = 0 | |
| self.prev = { | |
| "init": z(512, 6), | |
| "r1": z(256, 2), | |
| "r2": z(128, 2), | |
| "r3": z(64, 2), | |
| "final": z(64, 2), | |
| } | |
| self.partial = {"u1": z(256, 6), "u2": z(128, 5), "u3": z(64, 4)} | |
| def transformer(self, x): # x [1, T, 512] | |
| T = x.shape[1] | |
| pos = torch.arange(self.offset, self.offset + T) | |
| for i, L in enumerate(self.w.decoder_transformer.transformer.layers): | |
| h = layer_norm(L.norm1, x, 1e-5) | |
| qkv = lin(L.self_attn.in_proj, h).view(1, T, 3, H, HD) | |
| q, k, v = qkv[:, :, 0], qkv[:, :, 1], qkv[:, :, 2] | |
| q, k = rope_interleaved(q, pos), rope_interleaved(k, pos) | |
| q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2) | |
| pk, pv = self.kv[i] | |
| kk = k if pk is None else torch.cat([pk, k], 2) | |
| vv = v if pv is None else torch.cat([pv, v], 2) | |
| kvlen = kk.shape[2] - T | |
| qi = torch.arange(T)[:, None] + kvlen | |
| kj = torch.arange(kk.shape[2])[None, :] | |
| ok = (kj <= qi) & (kj >= qi - MIMI_CONTEXT) | |
| mask = torch.where(ok, 0.0, float("-inf")) | |
| att = torch.softmax(q @ kk.transpose(-1, -2) / math.sqrt(HD) + mask, -1) | |
| o = (att @ vv).transpose(1, 2).reshape(1, T, D) | |
| self.kv[i] = [kk[:, :, -MIMI_CONTEXT:], vv[:, :, -MIMI_CONTEXT:]] | |
| x = x + L.layer_scale_1.scale * lin(L.self_attn.out_proj, o) | |
| h = layer_norm(L.norm2, x, 1e-5) | |
| x = x + L.layer_scale_2.scale * lin(L.linear2, gelu(lin(L.linear1, h))) | |
| self.offset += T | |
| return x | |
| def _res(self, blk, x, key): | |
| v, self.prev[key] = _conv1d_stream(blk.block[1], elu(x), self.prev[key]) | |
| v, _ = _conv1d_stream(blk.block[3], elu(v), None) | |
| return x + v | |
| def decode(self, latent): | |
| """latent [1, T, 32] (flow-LM space) -> pcm [1, 1, T * 1920].""" | |
| x = (latent * self.emb_std + self.emb_mean).transpose(1, 2) | |
| x = F.conv1d(x, self.w.quantizer.output_proj.weight) | |
| x, self.up = _convtr_stream( | |
| self.w.upsample.convtr.convtr.weight, None, x, self.up, UPS, groups=D | |
| ) | |
| x = self.transformer(x.transpose(1, 2)).transpose(1, 2) | |
| m = self.w.decoder.model | |
| x, self.prev["init"] = _conv1d_stream(m[0], x, self.prev["init"]) | |
| for up, res, s, ku, kr in [ | |
| (m[2], m[3], 6, "u1", "r1"), | |
| (m[5], m[6], 5, "u2", "r2"), | |
| (m[8], m[9], 4, "u3", "r3"), | |
| ]: | |
| x, self.partial[ku] = _convtr_stream( | |
| up.convtr.weight, up.convtr.bias, elu(x), self.partial[ku], s | |
| ) | |
| x = self._res(res, x, kr) | |
| x, self.prev["final"] = _conv1d_stream(m[11], elu(x), self.prev["final"]) | |
| return x | |
| # --------------------------------------------------------------------------------------------- | |
| # Generation policy (plan.rs, tts_model.rs) | |
| # --------------------------------------------------------------------------------------------- | |
| def prepare_text_prompt(text): | |
| text = " ".join(text.strip().replace("\n", " ").replace("\r", " ").split()) | |
| if not text: | |
| return text, 3 | |
| frames_after_eos = 3 if len(text.split()) <= 4 else 1 | |
| text = text[0].upper() + text[1:] | |
| if text[-1].isalnum(): | |
| text += "." | |
| return text, frames_after_eos | |
| def frame_budget(num_tokens, frame_rate=12.5): | |
| return math.ceil((num_tokens / 3.0 + 2.0) * frame_rate) | |
| class EosPolicy: | |
| def __init__(self, frames_after_eos): | |
| self.n, self.countdown = frames_after_eos, None | |
| def should_stop(self, is_eos): | |
| if is_eos and self.countdown is None: | |
| self.countdown = self.n | |
| if self.countdown is None: | |
| return False | |
| if self.countdown == 0: | |
| return True | |
| self.countdown -= 1 | |
| return False | |
| def generate(step_fn, tokens, frames_after_eos, noise_fn, eos_threshold=4.0): | |
| """The runtime's loop (synth.rs run_inline): BOS first, the EOS frame and its tail are kept. | |
| step_fn(prev_latent_or_None, noise) -> (latent [1,1,32], eos_logit). Returns latents, eos logits.""" | |
| eos = EosPolicy(frames_after_eos) | |
| prev, lats, logits = None, [], [] | |
| for i in range(frame_budget(len(tokens))): | |
| lat, e = step_fn(prev, noise_fn(i)) | |
| lats.append(lat) | |
| logits.append(float(e.reshape(-1)[0])) | |
| if eos.should_stop(logits[-1] > eos_threshold): | |
| break | |
| prev = lat | |
| return lats, logits | |
| def tokenizer(tokenizer_json): | |
| from tokenizers import Tokenizer | |
| return Tokenizer.from_file(str(tokenizer_json)) | |
| def tokenize(tok, text): | |
| prepared, frames_after_eos = prepare_text_prompt(text) | |
| return ( | |
| tok.encode(prepared, add_special_tokens=False).ids, | |
| frames_after_eos, | |
| prepared, | |
| ) | |
| # --------------------------------------------------------------------------------------------- | |
| # Export programs | |
| # --------------------------------------------------------------------------------------------- | |
| MAX_CTX = 512 # flow-LM KV cache positions: voice prompt + text tokens + generated frames | |
| PREFILL_MAX = 128 # most positions one flow-LM call may write (the 125-frame voice prompt fits) | |
| def _half_split_perm(): | |
| """Within each head, reorder q/k channels from interleaved pairs (2i, 2i+1) to halves | |
| (i, i + 32): rope_i on the old layout equals rotate_half RoPE on the new one, and q.k is | |
| unchanged because q and k are permuted alike.""" | |
| per = torch.cat([torch.arange(0, HD, 2), torch.arange(1, HD, 2)]) | |
| return torch.cat([h * HD + per for h in range(H)]) | |
| def _permuted_in_proj(w): | |
| """in_proj weight [3*512, 512] with the q and k row blocks permuted, v untouched.""" | |
| perm = _half_split_perm() | |
| return torch.cat([w[:D][perm], w[D : 2 * D][perm], w[2 * D :]], 0).contiguous() | |
| def rope_tables(n): | |
| """cos/sin [n, 64] in the half-split layout, angle = f32(pos) * f32(inv_freq) as rope.rs.""" | |
| ang = torch.arange(n, dtype=torch.float32)[:, None] * inv_freq()[None, :] | |
| c, s = torch.cos(ang), torch.sin(ang) | |
| return torch.cat([c, c], -1).contiguous(), torch.cat([s, s], -1).contiguous() | |
| def _rope_hf(q, k, cos, sin): | |
| from executorch.backends.vulkan.patterns.rope_hf import HfRotaryEmbeddingPattern | |
| return HfRotaryEmbeddingPattern()(q, k, cos, sin) | |
| class _LinP(nn.Module): | |
| def __init__(self, weight, bias=None): | |
| super().__init__() | |
| self.weight = nn.Parameter(weight.detach().clone().contiguous(), requires_grad=False) | |
| if bias is not None: | |
| self.bias = nn.Parameter(bias.detach().clone().contiguous(), requires_grad=False) | |
| class FlowLMProgram(nn.Module): | |
| """forward(embeds[1,S,512], input_pos[1] i64, noise[1,32]) | |
| -> latent[1,32], eos_logit[1,1], next_embeds[1,1,512]. | |
| Writes the S positions [input_pos, input_pos + S) of the in-program KV cache (llama | |
| sdpa_with_kv_cache, causal), then runs the head on the last position: eos_logit = out_eos(h), | |
| latent = noise + flow_net(h, s=0, t=1, noise) and next_embeds = input_linear(latent) + cond, | |
| the embedding of the next step's input. Prompt calls (voice, text) ignore the outputs.""" | |
| def __init__(self, w: PhononWeights, num_speakers="1", duration_delta=0.0, attn="sdpa_kv"): | |
| super().__init__() | |
| from executorch.extension.llm.custom_ops import custom_ops # noqa: F401 | |
| f = w.flow_lm | |
| self.attn = attn | |
| self.layers = nn.ModuleList() | |
| for L in f.transformer.layers: | |
| m = nn.Module() | |
| m.norm1, m.norm2 = L.norm1, L.norm2 | |
| m.in_proj = _LinP(_permuted_in_proj(L.self_attn.in_proj.weight)) | |
| m.out_proj, m.linear1, m.linear2 = L.self_attn.out_proj, L.linear1, L.linear2 | |
| self.layers.append(m) | |
| self.out_norm, self.out_eos, self.input_linear = f.out_norm, f.out_eos, f.input_linear | |
| self.fn = f.flow_net | |
| with torch.no_grad(): | |
| self.register_buffer("cond", summed_conditions(f, num_speakers, duration_delta).clone(), persistent=False) | |
| self.register_buffer("t_comb", lsd_time(f.flow_net).clone(), persistent=False) | |
| self.register_buffer("mean_w", torch.full((1, D), 1.0 / D), persistent=False) | |
| cos, sin = rope_tables(MAX_CTX) | |
| self.register_buffer("rope_cos", cos, persistent=False) | |
| self.register_buffer("rope_sin", sin, persistent=False) | |
| for i in range(FLOW_LAYERS): | |
| self.register_buffer(f"k_cache_{i}", torch.zeros(1, MAX_CTX, H, HD), persistent=False) | |
| self.register_buffer(f"v_cache_{i}", torch.zeros(1, MAX_CTX, H, HD), persistent=False) | |
| def reset(self): | |
| for i in range(FLOW_LAYERS): | |
| getattr(self, f"k_cache_{i}").zero_() | |
| getattr(self, f"v_cache_{i}").zero_() | |
| def forward(self, embeds, input_pos, noise): | |
| S = embeds.shape[1] | |
| p = input_pos[0].item() | |
| torch._check_is_size(p) | |
| torch._check(p + S <= MAX_CTX) | |
| cos = self.rope_cos.narrow(0, p, S) | |
| sin = self.rope_sin.narrow(0, p, S) | |
| x = embeds | |
| for i, L in enumerate(self.layers): | |
| h = layer_norm_dyn(L.norm1, x, 1e-5, self.mean_w) | |
| qkv = lin3(L.in_proj, h) | |
| q = qkv[..., :D].reshape(1, S, H, HD) | |
| k = qkv[..., D : 2 * D].reshape(1, S, H, HD) | |
| v = qkv[..., 2 * D :].reshape(1, S, H, HD) | |
| q, k = _rope_hf(q, k, cos, sin) | |
| kc, vc = getattr(self, f"k_cache_{i}"), getattr(self, f"v_cache_{i}") | |
| o = torch.ops.llama.sdpa_with_kv_cache(q, k, v, kc, vc, p, S, None, 0.0, True, None) | |
| x = x + lin3(L.out_proj, o.reshape(1, S, D)) | |
| h = layer_norm_dyn(L.norm2, x, 1e-5, self.mean_w) | |
| x = x + lin3(L.linear2, gelu(lin3(L.linear1, h))) | |
| h = layer_norm(self.out_norm, x[:, -1, :], 1e-5) | |
| eos = lin3(self.out_eos, h) | |
| latent = noise + flow_net(self.fn, h, noise, self.t_comb) | |
| nxt = lin3(self.input_linear, latent).reshape(1, 1, D) + self.cond | |
| return latent, eos, nxt | |
| MIMI_STATE = [ | |
| ("upsample_partial", (1, D, UPS)), | |
| ("k0", (1, H, MIMI_CONTEXT, HD)), | |
| ("v0", (1, H, MIMI_CONTEXT, HD)), | |
| ("k1", (1, H, MIMI_CONTEXT, HD)), | |
| ("v1", (1, H, MIMI_CONTEXT, HD)), | |
| ("kv_seen", (1, 1, 1, MIMI_CONTEXT)), | |
| ("rope_offset", (1,)), | |
| ("init_conv_prev", (1, 512, 6)), | |
| ("up1_partial", (1, 256, 6)), | |
| ("res1_prev", (1, 256, 2)), | |
| ("up2_partial", (1, 128, 5)), | |
| ("res2_prev", (1, 128, 2)), | |
| ("up3_partial", (1, 64, 4)), | |
| ("res3_prev", (1, 64, 2)), | |
| ("final_conv_prev", (1, 64, 2)), | |
| ] | |
| def mimi_zero_state(): | |
| return [torch.zeros(s) for _, s in MIMI_STATE] | |
| def _convtr(x, w, b, s, groups=1): | |
| """ConvTranspose1d as a 4-D (H = 1) conv_transpose2d: the public runtime's 3-D convolution | |
| has no transposed path, its 4-D one does (with groups).""" | |
| y = F.conv_transpose2d(x.unsqueeze(2), w.unsqueeze(2), b, stride=(1, s), groups=groups) | |
| return y.squeeze(2) | |
| def _convtr_step(x, w, b, partial, s, groups=1): | |
| y = _convtr(x, w, b, s, groups) | |
| pt = partial.shape[2] | |
| n = y.shape[2] | |
| head = y[:, :, :pt] + partial | |
| out = torch.cat([head, y[:, :, pt : n - pt]], 2) | |
| tail = y[:, :, n - pt :] | |
| if b is not None: | |
| tail = tail - b.view(1, -1, 1) | |
| return out, tail | |
| def _conv_step(m, x, prev): | |
| xx = torch.cat([prev, x], 2) | |
| y = F.conv1d(xx, m.conv.weight, m.conv.bias) | |
| return y, xx[:, :, xx.shape[2] - prev.shape[2] :] | |
| class MimiStepProgram(nn.Module): | |
| """forward(latent[1,1,32], *state) -> pcm[1,1,1920], *new_state (MIMI_STATE order). | |
| One latent frame per call, exactly the runtime's streaming decoder: every piece of streaming | |
| state (conv tails, transposed-conv partial sums, the 2-layer transformer's last 250 keys and | |
| values, which of them are filled, and the RoPE offset) is an explicit input and output. The | |
| first call takes all-zero state.""" | |
| def __init__(self, w: PhononWeights): | |
| super().__init__() | |
| m = w.mimi | |
| self.emb_std = nn.Parameter(w.flow_lm.emb_std.detach().clone().view(1, 1, LDIM), requires_grad=False) | |
| self.emb_mean = nn.Parameter(w.flow_lm.emb_mean.detach().clone().view(1, 1, LDIM), requires_grad=False) | |
| self.qproj = _LinP(m.quantizer.output_proj.weight.view(D, LDIM)) | |
| self.up_w = nn.Parameter(m.upsample.convtr.convtr.weight.detach().clone(), requires_grad=False) | |
| self.layers = nn.ModuleList() | |
| for L in m.decoder_transformer.transformer.layers: | |
| q = nn.Module() | |
| q.norm1, q.norm2 = L.norm1, L.norm2 | |
| q.in_proj = _LinP(_permuted_in_proj(L.self_attn.in_proj.weight)) | |
| q.out_proj, q.linear1, q.linear2 = L.self_attn.out_proj, L.linear1, L.linear2 | |
| q.ls1 = nn.Parameter(L.layer_scale_1.scale.detach().clone(), requires_grad=False) | |
| q.ls2 = nn.Parameter(L.layer_scale_2.scale.detach().clone(), requires_grad=False) | |
| self.layers.append(q) | |
| self.dec = m.decoder.model | |
| self.register_buffer("inv_freq", inv_freq().view(1, HD // 2), persistent=False) | |
| self.register_buffer("steps", torch.arange(UPS, dtype=torch.float32).view(UPS, 1), persistent=False) | |
| i = torch.arange(UPS)[:, None] | |
| j = torch.arange(MIMI_CONTEXT + UPS)[None, :] | |
| band = torch.where((j >= i) & (j <= i + MIMI_CONTEXT), 0.0, NEG) | |
| self.register_buffer("band", band.view(1, 1, UPS, MIMI_CONTEXT + UPS), persistent=False) | |
| self.register_buffer("ones_seen", torch.ones(1, 1, 1, UPS), persistent=False) | |
| def _attn(self, L, x, kc, vc, seen, cos, sin): | |
| T = UPS | |
| h = layer_norm(L.norm1, x, 1e-5) | |
| qkv = lin(L.in_proj, h) | |
| q = qkv[..., :D].reshape(1, T, H, HD) | |
| k = qkv[..., D : 2 * D].reshape(1, T, H, HD) | |
| v = qkv[..., 2 * D :].reshape(1, T, H, HD) | |
| rh = lambda t: torch.cat([-t[..., HD // 2 :], t[..., : HD // 2]], -1) # noqa: E731 | |
| q = q * cos + rh(q) * sin | |
| k = k * cos + rh(k) * sin | |
| q = q.permute(0, 2, 1, 3) # [1, H, T, HD] | |
| k = k.permute(0, 2, 1, 3) | |
| v = v.permute(0, 2, 1, 3) | |
| kk = torch.cat([kc, k], 2) # [1, H, 266, HD] | |
| vv = torch.cat([vc, v], 2) | |
| seen_all = torch.cat([seen, self.ones_seen], 3) | |
| mask = self.band + (seen_all - 1.0) * 1.0e30 | |
| sc = torch.bmm(q.reshape(H, T, HD), kk.reshape(H, MIMI_CONTEXT + T, HD).transpose(1, 2)) * (HD**-0.5) | |
| pr = torch.softmax(sc.reshape(1, H, T, MIMI_CONTEXT + T) + mask, -1) | |
| o = torch.bmm(pr.reshape(H, T, MIMI_CONTEXT + T), vv.reshape(H, MIMI_CONTEXT + T, HD)) | |
| o = o.reshape(1, H, T, HD).permute(0, 2, 1, 3).reshape(1, T, D) | |
| x = x + L.ls1 * lin(L.out_proj, o) | |
| h = layer_norm(L.norm2, x, 1e-5) | |
| x = x + L.ls2 * lin(L.linear2, gelu(lin(L.linear1, h))) | |
| return x, kk[:, :, UPS:], vv[:, :, UPS:], seen_all[..., UPS:] | |
| def _res(self, blk, x, prev): | |
| v, prev = _conv_step(blk.block[1], elu(x), prev) | |
| v = F.conv1d(elu(v), blk.block[3].conv.weight, blk.block[3].conv.bias) | |
| return x + v, prev | |
| def forward(self, latent, up_p, k0, v0, k1, v1, seen, off, c0, u1, r1, u2, r2, u3, r3, cf): | |
| x = latent * self.emb_std + self.emb_mean # [1, 1, 32] | |
| x = lin(self.qproj, x).reshape(1, D, 1) | |
| x, up_p = _convtr_step(x, self.up_w, None, up_p, UPS, groups=D) # [1, 512, 16] | |
| pos = self.steps + off # [16, 1] | |
| ang = pos * self.inv_freq # [16, 32] | |
| cos = torch.cat([torch.cos(ang), torch.cos(ang)], -1).reshape(1, UPS, 1, HD) | |
| sin = torch.cat([torch.sin(ang), torch.sin(ang)], -1).reshape(1, UPS, 1, HD) | |
| x = x.permute(0, 2, 1) # [1, 16, 512] | |
| x, k0, v0, seen0 = self._attn(self.layers[0], x, k0, v0, seen, cos, sin) | |
| x, k1, v1, _ = self._attn(self.layers[1], x, k1, v1, seen, cos, sin) | |
| x = x.permute(0, 2, 1) # [1, 512, 16] | |
| m = self.dec | |
| x, c0 = _conv_step(m[0], x, c0) | |
| x, u1 = _convtr_step(elu(x), m[2].convtr.weight, m[2].convtr.bias, u1, 6) | |
| x, r1 = self._res(m[3], x, r1) | |
| x, u2 = _convtr_step(elu(x), m[5].convtr.weight, m[5].convtr.bias, u2, 5) | |
| x, r2 = self._res(m[6], x, r2) | |
| x, u3 = _convtr_step(elu(x), m[8].convtr.weight, m[8].convtr.bias, u3, 4) | |
| x, r3 = self._res(m[9], x, r3) | |
| pcm, cf = _conv_step(m[11], elu(x), cf) | |
| return pcm, up_p, k0, v0, k1, v1, seen0, off + float(UPS), c0, u1, r1, u2, r2, u3, r3, cf | |