Download scripts/audio8_infinite_ref.py from Masterx/Audio8-ASR-Infinite-ONNX: direct link, hf CLI and curl.
- Browser
- Download file 25.2 kB
-
https://huggingface.co/Masterx/Audio8-ASR-Infinite-ONNX/resolve/main/scripts/audio8_infinite_ref.py
- Command line
-
hf download hf://Masterx/Audio8-ASR-Infinite-ONNX/scripts/audio8_infinite_ref.py
-
curl -L -o audio8_infinite_ref.py https://huggingface.co/Masterx/Audio8-ASR-Infinite-ONNX/resolve/main/scripts/audio8_infinite_ref.py
25.2 kB
| #!/usr/bin/env python3 | |
| """Audio8-ASR-Infinite -> ONNX: weight reader, torch reference, graph builder, streaming session. | |
| One module so the export, the parity harness and the (Rust-mirroring) streaming session share a | |
| single definition of the graph contract. | |
| GRAPH CONTRACT | |
| -------------- | |
| audio_encoder.onnx (mel + causal conv embedder + 32-layer sliding-window tower + projector) | |
| in : audio f32 [B, S] B raw 16 kHz windows of equal length S (one per text token | |
| group, the reference `streaming_audio_embeds_by_definition` | |
| window incl. its 52.5 ms look-back / 2.5 ms look-ahead) | |
| frame_len i64 [1] encoder frames per text token (4 = 80 ms gear) | |
| cos, sin f32 [T, 64] RoPE factors of the T = B*K new encoder frames (host f64) | |
| attn_bias f32 [T, C + T] additive mask: past ring slots then the new frames | |
| past_key_i f32 [1, 32, C, 64] ring of ROTATED keys (any slot order; C = 749 fixed) | |
| past_value_i f32 [1, 32, C, 64] | |
| out: audio_embeds f32 [1, T/frame_len, 2048] | |
| key_i, value_i f32 [1, 32, T, 64] rotated keys / values of the new frames | |
| decoder.onnx (Qwen2.5-3B text backbone with Voxtral delay modulation + tied LM head + VAD heads) | |
| in : inputs_embeds f32 [1, n, 2048] token embedding (host table) + audio embedding | |
| ada_scale f32 [36, 2048] per-layer (1 + ada_rms_norm(t_cond)) -- constant per session | |
| cos, sin f32 [n, 128] | |
| attn_bias f32 [n, L + n] | |
| past_key_i f32 [1, 2, L, 128] rotated keys (L = 375 fixed capacity, masked when empty) | |
| past_value_i f32 [1, 2, L, 128] | |
| out: logits f32 [1, 151936] last position only | |
| vad_logits f32 [1, 4, 8] semantic-VAD heads (horizons 0.5/1/2/3 s), last position | |
| key_i, value_i f32 [1, 2, n, 128] | |
| Keys are stored ALREADY ROTATED with host-computed (f64, reduced mod 2*pi) angles, so no cache ever | |
| needs re-rotation except the decoder's rolling-window trim (suffix re-based by -trim positions). | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import math | |
| import os | |
| import struct | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Callable | |
| import numpy as np | |
| # ---------------------------------------------------------------------------------------------- | |
| # constants (from config.json of Edge0/Audio8-ASR-Infinite) | |
| # ---------------------------------------------------------------------------------------------- | |
| SR = 16000 | |
| N_FFT = 400 | |
| HOP = 160 | |
| N_MELS = 128 | |
| LOG_MEL_FLOOR = 1.5 - 8.0 | |
| BOS, EOS, PAD = 151644, 151645, 151643 | |
| STREAM_PAD, STREAM_WORD, LANG_ZH, LANG_EN = 151665, 151666, 151667, 151668 | |
| VOCAB = 151936 | |
| LOOK_BACK = 840 # 52.5 ms | |
| LOOK_AHEAD = 40 # 2.5 ms | |
| RIGHT_PAD_TEXT_TOKENS = 10 | |
| LEFT_PAD_BY_FL = {4: 18, 6: 12, 8: 9} | |
| DELAYS_BY_FL = {4: {240: 3, 320: 4, 480: 6, 560: 7}, 6: {240: 2, 480: 4}, 8: {320: 2, 480: 3}} | |
| SUPPORTED_FL = (4, 6, 8) | |
| MAX_FL = 8 | |
| VAD_HORIZONS = (0.5, 1.0, 2.0, 3.0) | |
| # vLLM / MLX rolling 30 s window | |
| ROLL_CONTEXT = 375 | |
| ROLL_TRIM = 38 | |
| ROLL_STABLE = 16 | |
| class Dims: | |
| a_hidden: int = 1280 | |
| a_layers: int = 32 | |
| a_heads: int = 32 | |
| a_head_dim: int = 64 | |
| a_inter: int = 5120 | |
| a_eps: float = 1e-5 | |
| a_theta: float = 1e6 | |
| a_window: int = 750 | |
| t_hidden: int = 2048 | |
| t_layers: int = 36 | |
| t_heads: int = 16 | |
| t_kv: int = 2 | |
| t_head_dim: int = 128 | |
| t_inter: int = 11008 | |
| t_eps: float = 1e-6 | |
| t_theta: float = 1e6 | |
| vocab: int = VOCAB | |
| n_mels: int = N_MELS | |
| vad_heads: int = 4 | |
| vad_classes: int = 8 | |
| def projection(self) -> int: | |
| return MAX_FL * self.a_hidden | |
| def enc_cache(self) -> int: | |
| return self.a_window - 1 | |
| def from_config(cls, cfg: dict) -> "Dims": | |
| a, t = cfg["audio_config"], cfg["text_config"] | |
| return cls( | |
| a_hidden=a["hidden_size"], a_layers=a["num_hidden_layers"], a_heads=a["num_attention_heads"], | |
| a_head_dim=a.get("head_dim") or a["hidden_size"] // a["num_attention_heads"], | |
| a_inter=a["intermediate_size"], a_eps=a["rms_norm_eps"], | |
| a_theta=a["rope_parameters"]["rope_theta"], a_window=a["sliding_window"], | |
| t_hidden=t["hidden_size"], t_layers=t["num_hidden_layers"], t_heads=t["num_attention_heads"], | |
| t_kv=t["num_key_value_heads"], t_head_dim=t.get("head_dim") or t["hidden_size"] // t["num_attention_heads"], | |
| t_inter=t["intermediate_size"], t_eps=t["rms_norm_eps"], t_theta=t["rope_parameters"]["rope_theta"], | |
| vocab=t["vocab_size"], n_mels=a["num_mel_bins"], | |
| vad_heads=len(cfg.get("semantic_vad_horizons_seconds") or VAD_HORIZONS), | |
| vad_classes=cfg.get("semantic_vad_num_classes", 8), | |
| ) | |
| # ---------------------------------------------------------------------------------------------- | |
| # weights: zero-copy safetensors memmap (bf16 upcast on access) | |
| # ---------------------------------------------------------------------------------------------- | |
| _ST_DT = {"BF16": (np.uint16, "bf16"), "F32": (np.float32, None), "F16": (np.float16, None)} | |
| class SafeTensors: | |
| """name -> float32 ndarray, reading lazily from memmapped safetensors files.""" | |
| def __init__(self, paths: list[str | Path]): | |
| self.index: dict[str, tuple[np.memmap, str, list[int], int, int]] = {} | |
| for p in paths: | |
| with open(p, "rb") as f: | |
| (hlen,) = struct.unpack("<Q", f.read(8)) | |
| header = json.loads(f.read(hlen)) | |
| mm = np.memmap(p, dtype=np.uint8, mode="r") | |
| base = 8 + hlen | |
| for name, meta in header.items(): | |
| if name == "__metadata__": | |
| continue | |
| s, e = meta["data_offsets"] | |
| self.index[name] = (mm, meta["dtype"], meta["shape"], base + s, base + e) | |
| def __contains__(self, name: str) -> bool: | |
| return name in self.index | |
| def raw(self, name: str) -> tuple[np.ndarray, str]: | |
| mm, dt, shape, s, e = self.index[name] | |
| np_dt, tag = _ST_DT[dt] | |
| return mm[s:e].view(np_dt).reshape(shape), (tag or dt) | |
| def __getitem__(self, name: str) -> np.ndarray: | |
| arr, tag = self.raw(name) | |
| if tag == "bf16": | |
| return (arr.astype(np.uint32) << 16).view(np.float32) | |
| return np.asarray(arr, dtype=np.float32) | |
| def keys(self): | |
| return self.index.keys() | |
| class DictWeights(dict): | |
| """Same interface for in-memory weights (tiny-config tests).""" | |
| def raw(self, name): | |
| return self[name], "F32" | |
| def load_weights(ckpt: Path): | |
| files = sorted(Path(ckpt).glob("model*.safetensors")) | |
| vad = Path(ckpt) / "semantic_vad_heads.safetensors" | |
| st = SafeTensors(files + ([vad] if vad.exists() else [])) | |
| return st | |
| # ---------------------------------------------------------------------------------------------- | |
| # frontend constants | |
| # ---------------------------------------------------------------------------------------------- | |
| def mel_filters() -> np.ndarray: | |
| """[201, 128] slaney mel bank (VoxtralRealtimeFeatureExtractor).""" | |
| from transformers.audio_utils import mel_filter_bank | |
| return mel_filter_bank( | |
| num_frequency_bins=1 + N_FFT // 2, num_mel_filters=N_MELS, min_frequency=0.0, | |
| max_frequency=8000.0, sampling_rate=SR, norm="slaney", mel_scale="slaney", | |
| ).astype(np.float32) | |
| def dft_basis() -> np.ndarray: | |
| """[2*201, 1, 400] periodic-Hann-windowed real/imag DFT rows as Conv1d kernels.""" | |
| n = np.arange(N_FFT, dtype=np.float64) | |
| win = 0.5 - 0.5 * np.cos(2 * np.pi * n / N_FFT) # torch.hann_window(periodic=True) | |
| k = np.arange(N_FFT // 2 + 1, dtype=np.float64)[:, None] | |
| ang = 2 * np.pi * k * n[None, :] / N_FFT | |
| re = np.cos(ang) * win | |
| im = -np.sin(ang) * win | |
| return np.concatenate([re, im], 0)[:, None, :].astype(np.float32) | |
| def rope_inv_freq(head_dim: int, theta: float) -> np.ndarray: | |
| return 1.0 / (theta ** (np.arange(0, head_dim, 2, dtype=np.float64) / head_dim)) | |
| def rope_cos_sin(positions, head_dim: int, theta: float) -> tuple[np.ndarray, np.ndarray]: | |
| """f64 angles reduced mod 2*pi -> f32 [n, head_dim] (rotate_half layout).""" | |
| pos = np.asarray(positions, dtype=np.float64)[:, None] | |
| ang = np.mod(pos * rope_inv_freq(head_dim, theta)[None, :], 2 * np.pi) | |
| ang = np.concatenate([ang, ang], -1) | |
| return np.cos(ang).astype(np.float32), np.sin(ang).astype(np.float32) | |
| def modulation(W, dims: Dims, delay_tokens: int, frame_len: int) -> np.ndarray: | |
| """[t_layers, t_hidden] = 1 + ada_rms_norm(t_cond) per layer (constant per session).""" | |
| import torch | |
| h = dims.t_hidden | |
| inv = torch.exp(-math.log(10000.0) * torch.arange(h // 2).float() / (h // 2)) | |
| phase = float(delay_tokens) * inv | |
| t = torch.cat([phase.cos(), phase.sin()]) | |
| t = t + torch.from_numpy(W["frame_len_embedding.weight"][SUPPORTED_FL.index(frame_len)].copy()) | |
| out = [] | |
| for i in range(dims.t_layers): | |
| p = f"language_model.model.layers.{i}.ada_rms_norm." | |
| w1 = torch.from_numpy(W[p + "linear1.weight"].copy()) | |
| w2 = torch.from_numpy(W[p + "linear2.weight"].copy()) | |
| out.append(1.0 + (torch.nn.functional.gelu(t @ w1.T) @ w2.T)) | |
| return torch.stack(out).numpy().astype(np.float32) | |
| def embed_rows(W, ids) -> np.ndarray: | |
| """Exact bf16 -> f32 token embedding rows.""" | |
| arr, tag = W.raw("language_model.model.embed_tokens.weight") | |
| rows = np.asarray(arr[np.asarray(ids)]) | |
| if tag == "bf16": | |
| return (rows.astype(np.uint32) << 16).view(np.float32) | |
| return rows.astype(np.float32) | |
| # ---------------------------------------------------------------------------------------------- | |
| # torch functional reference (same contract as the ONNX graphs, fp32 math) | |
| # ---------------------------------------------------------------------------------------------- | |
| class TorchRef: | |
| def __init__(self, W, dims: Dims): | |
| import torch | |
| self.torch = torch | |
| self.W = W | |
| self.d = dims | |
| self._mel = torch.from_numpy(mel_filters()) | |
| self._cache: dict[str, "torch.Tensor"] = {} | |
| def w(self, name): | |
| t = self._cache.get(name) | |
| if t is None: | |
| t = self.torch.from_numpy(np.ascontiguousarray(self.W[name])) | |
| return t | |
| def keep(self, names): | |
| for n in names: | |
| self._cache[n] = self.torch.from_numpy(np.ascontiguousarray(self.W[n])) | |
| def rms(x, w, eps): | |
| return x * (x.pow(2).mean(-1, keepdim=True) + eps).rsqrt() * w | |
| def rot(x, cos, sin): | |
| import torch | |
| h = x.shape[-1] // 2 | |
| return x * cos + torch.cat([-x[..., h:], x[..., :h]], -1) * sin | |
| def lin(self, x, name, bias=True): | |
| y = x @ self.w(name + ".weight").T | |
| if bias and (name + ".bias") in self.W: | |
| y = y + self.w(name + ".bias") | |
| return y | |
| def mel(self, audio): | |
| torch = self.torch | |
| st = torch.stft(audio, N_FFT, HOP, window=torch.hann_window(N_FFT), return_complex=True, center=True) | |
| mag = st[..., :-1].abs() ** 2 | |
| m = self._mel.T @ mag | |
| lg = torch.clamp(m, min=1e-10).log10() | |
| lg = torch.maximum(lg, torch.tensor(LOG_MEL_FLOOR)) | |
| return (lg + 4.0) / 4.0 | |
| def attend(self, q, k_new, v_new, past_k, past_v, bias, groups): | |
| """q [1,H,T,D]; k/v [1,KV,*,D]; bias [T, C+T].""" | |
| torch = self.torch | |
| k = torch.cat([past_k, k_new], 2) | |
| v = torch.cat([past_v, v_new], 2) | |
| if groups > 1: | |
| k = k.repeat_interleave(groups, 1) | |
| v = v.repeat_interleave(groups, 1) | |
| s = (q @ k.transpose(-1, -2)) * (q.shape[-1] ** -0.5) + bias | |
| return torch.softmax(s, -1) @ v | |
| def encoder(self, audio, frame_len, cos, sin, attn_bias, past_k, past_v): | |
| torch, d = self.torch, self.d | |
| F = torch.nn.functional | |
| audio = torch.as_tensor(audio) | |
| B = audio.shape[0] | |
| T = cos.shape[0] | |
| K = T // B | |
| x = self.mel(audio) # [B,128,F] | |
| x = F.gelu(F.conv1d(F.pad(x, (2, 0)), self.w("audio_tower.embedder.conv1.weight"), self.w("audio_tower.embedder.conv1.bias"))) | |
| x = F.gelu(F.conv1d(F.pad(x, (1, 0)), self.w("audio_tower.embedder.conv2.weight"), self.w("audio_tower.embedder.conv2.bias"), stride=2)) | |
| x = x.transpose(1, 2)[:, -K:, :].reshape(1, T, d.a_hidden) | |
| cos, sin = torch.as_tensor(cos), torch.as_tensor(sin) | |
| bias = torch.as_tensor(attn_bias) | |
| new_k, new_v = [], [] | |
| for i in range(d.a_layers): | |
| p = f"audio_tower.layers.{i}." | |
| h = self.rms(x, self.w(p + "self_attn_layer_norm.weight"), d.a_eps) | |
| q = self.lin(h, p + "self_attn.q_proj").view(1, T, d.a_heads, d.a_head_dim).transpose(1, 2) | |
| k = self.lin(h, p + "self_attn.k_proj").view(1, T, d.a_heads, d.a_head_dim).transpose(1, 2) | |
| v = self.lin(h, p + "self_attn.v_proj").view(1, T, d.a_heads, d.a_head_dim).transpose(1, 2) | |
| q, k = self.rot(q, cos, sin), self.rot(k, cos, sin) | |
| new_k.append(k) | |
| new_v.append(v) | |
| o = self.attend(q, k, v, torch.as_tensor(past_k[i]), torch.as_tensor(past_v[i]), bias, 1) | |
| o = o.transpose(1, 2).reshape(1, T, -1) | |
| x = x + self.lin(o, p + "self_attn.o_proj") | |
| h = self.rms(x, self.w(p + "final_layer_norm.weight"), d.a_eps) | |
| g = self.lin(h, p + "mlp.gate_proj") | |
| u = self.lin(h, p + "mlp.up_proj") | |
| x = x + self.lin(F.silu(g) * u, p + "mlp.down_proj") | |
| x = self.rms(x, self.w("audio_tower.norm.weight"), d.a_eps) | |
| g = x.reshape(T // frame_len, frame_len * d.a_hidden) | |
| g = F.pad(g, (0, d.projection - frame_len * d.a_hidden)) | |
| e = self.lin(F.gelu(self.lin(g, "multi_modal_projector.linear_1")), "multi_modal_projector.linear_2") | |
| return e[None], new_k, new_v | |
| def decoder(self, embeds, ada, cos, sin, attn_bias, past_k, past_v, all_logits=False): | |
| torch, d = self.torch, self.d | |
| F = torch.nn.functional | |
| x = torch.as_tensor(embeds) | |
| n = x.shape[1] | |
| ada = torch.as_tensor(ada) | |
| cos, sin = torch.as_tensor(cos), torch.as_tensor(sin) | |
| bias = torch.as_tensor(attn_bias) | |
| new_k, new_v = [], [] | |
| for i in range(d.t_layers): | |
| p = f"language_model.model.layers.{i}." | |
| h = self.rms(x, self.w(p + "input_layernorm.weight"), d.t_eps) | |
| q = self.lin(h, p + "self_attn.q_proj").view(1, n, d.t_heads, d.t_head_dim).transpose(1, 2) | |
| k = self.lin(h, p + "self_attn.k_proj").view(1, n, d.t_kv, d.t_head_dim).transpose(1, 2) | |
| v = self.lin(h, p + "self_attn.v_proj").view(1, n, d.t_kv, d.t_head_dim).transpose(1, 2) | |
| q, k = self.rot(q, cos, sin), self.rot(k, cos, sin) | |
| new_k.append(k) | |
| new_v.append(v) | |
| o = self.attend(q, k, v, torch.as_tensor(past_k[i]), torch.as_tensor(past_v[i]), bias, d.t_heads // d.t_kv) | |
| o = o.transpose(1, 2).reshape(1, n, -1) | |
| x = x + self.lin(o, p + "self_attn.o_proj") | |
| h = self.rms(x, self.w(p + "post_attention_layernorm.weight"), d.t_eps) * ada[i] | |
| g = self.lin(h, p + "mlp.gate_proj") | |
| u = self.lin(h, p + "mlp.up_proj") | |
| x = x + self.lin(F.silu(g) * u, p + "mlp.down_proj") | |
| x = self.rms(x, self.w("language_model.model.norm.weight"), d.t_eps) | |
| last = x if all_logits else x[:, -1:] | |
| logits = last @ self.w("language_model.model.embed_tokens.weight").T | |
| vad = torch.stack([self.lin(last, f"semantic_vad_heads.{j}") for j in range(d.vad_heads)], -2) | |
| if not all_logits: | |
| logits, vad = logits[:, 0], vad[:, 0] | |
| return logits, vad, new_k, new_v | |
| # ---------------------------------------------------------------------------------------------- | |
| # streaming session (mirrors the Rust engine) | |
| # ---------------------------------------------------------------------------------------------- | |
| NEG = np.float32(-1e9) # finite "-inf" keeps fully-masked padding rows NaN-free | |
| class StepLog: | |
| tokens: list = field(default_factory=list) | |
| logits: list = field(default_factory=list) | |
| vad: list = field(default_factory=list) | |
| class Session: | |
| """HF-reference streaming definition (prefill = left_pad + delay + 1) + rolling 30 s window. | |
| backend.encoder(audio[B,S], frame_len, cos, sin, bias, past_k, past_v) -> (embeds, new_k, new_v) | |
| backend.decoder(embeds, ada, cos, sin, bias, past_k, past_v) -> (logits, vad, new_k, new_v) | |
| (numpy in / numpy-or-torch out). | |
| """ | |
| def __init__(self, backend, W, dims: Dims, *, delay_tokens=6, frame_len=4, language=LANG_EN, | |
| rolling=True, ada=None, enc_batch=1, keep_logits=False): | |
| self.b, self.W, self.d = backend, W, dims | |
| self.fl = frame_len | |
| self.P = 320 * frame_len | |
| self.left = LEFT_PAD_BY_FL[frame_len] | |
| self.delay = delay_tokens | |
| self.prefill = self.left + delay_tokens + 1 | |
| self.lang = language | |
| self.rolling = rolling | |
| self.enc_batch = enc_batch | |
| self.keep_logits = keep_logits | |
| self.ada = modulation(W, dims, delay_tokens, frame_len) if ada is None else ada | |
| C, L = dims.enc_cache, ROLL_CONTEXT | |
| self.C = C | |
| self.ek = [np.zeros((1, dims.a_heads, C, dims.a_head_dim), np.float32) for _ in range(dims.a_layers)] | |
| self.ev = [np.zeros_like(self.ek[0]) for _ in range(dims.a_layers)] | |
| self.epos = np.full(C, -1, np.int64) | |
| self.enc_frames = 0 | |
| self.L = L if rolling else 4096 | |
| self.dk = [np.zeros((1, dims.t_kv, self.L, dims.t_head_dim), np.float32) for _ in range(dims.t_layers)] | |
| self.dv = [np.zeros_like(self.dk[0]) for _ in range(dims.t_layers)] | |
| self.dlen = 0 | |
| self.trims = 0 | |
| self.log = StepLog() | |
| # -- encoder --------------------------------------------------------------------------- | |
| def window_bounds(self, k: int) -> tuple[int, int]: | |
| if k == 0: | |
| return 0, self.prefill * self.P + LOOK_AHEAD | |
| return (self.prefill + k - 1) * self.P - LOOK_BACK, (self.prefill + k) * self.P + LOOK_AHEAD | |
| def encode(self, windows: list[np.ndarray], tokens_per_window: int) -> np.ndarray: | |
| d = self.d | |
| B = len(windows) | |
| K = tokens_per_window * self.fl | |
| T = B * K | |
| pos = np.arange(self.enc_frames, self.enc_frames + T, dtype=np.int64) | |
| cos, sin = rope_cos_sin(pos, d.a_head_dim, d.a_theta) | |
| bias = np.full((T, self.C + T), NEG, np.float32) | |
| for t in range(T): | |
| q = pos[t] | |
| valid = (self.epos >= 0) & (self.epos > q - d.a_window) | |
| bias[t, : self.C][valid] = 0.0 | |
| j = np.arange(T) | |
| ok = (j <= t) & (pos[j] > q - d.a_window) | |
| bias[t, self.C:][ok] = 0.0 | |
| audio = np.stack(windows).astype(np.float32) | |
| emb, nk, nv = self.b.encoder(audio, self.fl, cos, sin, bias, self.ek, self.ev) | |
| nk = [np.asarray(x) for x in nk] | |
| nv = [np.asarray(x) for x in nv] | |
| start = max(0, T - self.C) | |
| for t in range(start, T): | |
| slot = int(pos[t] % self.C) | |
| for i in range(d.a_layers): | |
| self.ek[i][:, :, slot] = nk[i][:, :, t] | |
| self.ev[i][:, :, slot] = nv[i][:, :, t] | |
| self.epos[slot] = pos[t] | |
| self.enc_frames += T | |
| return np.asarray(emb)[0] | |
| # -- decoder --------------------------------------------------------------------------- | |
| def maybe_trim(self, incoming: int): | |
| if not self.rolling: | |
| return | |
| d = self.d | |
| while self.dlen + incoming > ROLL_CONTEXT: | |
| s, r = ROLL_STABLE, ROLL_TRIM | |
| cos, sin = rope_cos_sin([-r], d.t_head_dim, d.t_theta) | |
| for i in range(d.t_layers): | |
| suf_k = self.dk[i][:, :, s + r: self.dlen].copy() | |
| h = d.t_head_dim // 2 | |
| rot = np.concatenate([-suf_k[..., h:], suf_k[..., :h]], -1) | |
| suf_k = suf_k * cos[0] + rot * sin[0] | |
| self.dk[i][:, :, s: self.dlen - r] = suf_k | |
| self.dv[i][:, :, s: self.dlen - r] = self.dv[i][:, :, s + r: self.dlen] | |
| self.dk[i][:, :, self.dlen - r: self.dlen] = 0 | |
| self.dv[i][:, :, self.dlen - r: self.dlen] = 0 | |
| self.dlen -= r | |
| self.trims += 1 | |
| def decode(self, embeds: np.ndarray, all_logits=False): | |
| d = self.d | |
| n = embeds.shape[0] | |
| self.maybe_trim(n) | |
| assert self.dlen + n <= self.L | |
| pos = np.arange(self.dlen, self.dlen + n) | |
| cos, sin = rope_cos_sin(pos, d.t_head_dim, d.t_theta) | |
| bias = np.full((n, self.L + n), NEG, np.float32) | |
| bias[:, : self.dlen] = 0.0 | |
| bias[:, self.L:] = np.where(np.tril(np.ones((n, n), bool)), 0.0, NEG) | |
| if all_logits: | |
| logits, vad, nk, nv = self.b.decoder(embeds[None], self.ada, cos, sin, bias, self.dk, self.dv, all_logits=True) | |
| else: | |
| logits, vad, nk, nv = self.b.decoder(embeds[None], self.ada, cos, sin, bias, self.dk, self.dv) | |
| for i in range(d.t_layers): | |
| self.dk[i][:, :, self.dlen: self.dlen + n] = np.asarray(nk[i]) | |
| self.dv[i][:, :, self.dlen: self.dlen + n] = np.asarray(nv[i]) | |
| self.dlen += n | |
| return np.asarray(logits), np.asarray(vad) | |
| # -- full utterance ---------------------------------------------------------------------- | |
| def stream_of(self, wav: np.ndarray) -> np.ndarray: | |
| right = (self.delay + 1 + RIGHT_PAD_TEXT_TOKENS) * self.P | |
| return np.concatenate([np.zeros(self.left * self.P, np.float32), wav.astype(np.float32), | |
| np.zeros(right, np.float32)]) | |
| def n_windows(self, stream_len: int) -> int: | |
| k = 0 | |
| while self.window_bounds(k)[1] <= stream_len: | |
| k += 1 | |
| return k | |
| def run(self, wav: np.ndarray, forced: list[int] | None = None, max_steps: int | None = None, | |
| on_step: Callable | None = None) -> list[int]: | |
| """Greedy streaming decode (or teacher-forced when `forced` is given: logits are recorded, | |
| the forced tokens are fed back, decoder calls are chunked up to the next trim boundary).""" | |
| stream = self.stream_of(wav) | |
| nwin = self.n_windows(len(stream)) | |
| if max_steps: | |
| nwin = min(nwin, max_steps) | |
| prompt = [BOS, self.lang] + [STREAM_PAD] * (self.prefill - 2) | |
| # window 0 | |
| s, e = self.window_bounds(0) | |
| a0 = self.encode([stream[s:e]], self.prefill) | |
| emb = embed_rows(self.W, prompt) + a0 | |
| logits, vad = self.decode(emb) | |
| out = [] | |
| self._emit(logits, vad, forced, out) | |
| k = 1 | |
| pending_audio: list[np.ndarray] = [] | |
| while k < nwin: | |
| # encoder in batches of windows (audio-only dependency) | |
| if not pending_audio: | |
| kk = list(range(k, min(nwin, k + self.enc_batch))) | |
| wins = [stream[self.window_bounds(j)[0]: self.window_bounds(j)[1]] for j in kk] | |
| pending_audio = list(self.encode(wins, 1)) | |
| if forced is None: | |
| emb = embed_rows(self.W, [out[-1]]) + pending_audio.pop(0)[None] | |
| logits, vad = self.decode(emb) | |
| self._emit(logits, vad, None, out) | |
| k += 1 | |
| else: | |
| room = (ROLL_CONTEXT - self.dlen) if self.rolling else 10**9 | |
| if room <= 0: | |
| self.maybe_trim(1) | |
| room = ROLL_CONTEXT - self.dlen | |
| m = min(len(pending_audio), room, nwin - k) | |
| toks = [forced[k - 1 + j] for j in range(m)] | |
| emb = embed_rows(self.W, toks) + np.stack(pending_audio[:m]) | |
| del pending_audio[:m] | |
| logits, vad = self.decode(emb, all_logits=True) | |
| for j in range(m): | |
| self._emit(logits[0, j][None], vad[0, j][None], forced, out) | |
| k += m | |
| if on_step: | |
| on_step(k, nwin, out) | |
| return out | |
| def _emit(self, logits, vad, forced, out): | |
| lg = np.asarray(logits, dtype=np.float32).reshape(-1).copy() | |
| lg[EOS] = -np.inf | |
| tok = int(lg.argmax()) | |
| self.log.tokens.append(tok) | |
| if self.keep_logits: | |
| self.log.logits.append(np.asarray(logits, dtype=np.float32).reshape(-1)) | |
| self.log.vad.append(np.asarray(vad, dtype=np.float32).reshape(-1)) | |
| out.append(forced[len(out)] if forced is not None else tok) | |
| def visible_text(tokenizer, ids) -> str: | |
| keep = [] | |
| for t in ids: | |
| if t == EOS: | |
| break | |
| if t in (STREAM_PAD, STREAM_WORD, BOS, PAD, LANG_ZH, LANG_EN): | |
| continue | |
| keep.append(t) | |
| return tokenizer.decode(keep) | |
| def eot_probability(vad_row: np.ndarray, horizon_index: int = 2) -> float: | |
| z = vad_row.reshape(-1, 8)[horizon_index].astype(np.float64) | |
| z = np.exp(z - z.max()) | |
| return float(z[0] / z.sum()) | |