Source code for mstar.model.whisper.config

"""Config for Whisper encoder-decoder ASR models (large-v3 and friends).

Values are read from the HF checkpoint's ``config.json`` +
``generation_config.json`` so the same class serves any Whisper size.
"""
from __future__ import annotations

import json
from dataclasses import dataclass, field, fields
from pathlib import Path

# ---------------------------------------------------------------------------
# Resource label constants (decoder node)
# ---------------------------------------------------------------------------
KV_CACHE = "kv_cache"
ATTN = "attn"
SAMPLER = "sampler"
# Positions: Whisper has no RoPE. The resource is declared for its
# per-(request, label) position counter, which drives the learned absolute
# ``embed_positions`` lookup; the attention layers never call it.
POS = "positions"

# The encoder context lives in its own KV cache so the self-attention
# resource, which plans a wrapper per label of the cache it names, never sees
# it. Written once at prefill, read (zero-span) by every later step.
CROSS_KV_CACHE = "cross_kv_cache"
CROSS_ATTN = "cross_attn"
CONTEXT_LABEL = "main"


[docs] @dataclass class WhisperModelConfig: # transformer dims (large-v3 defaults) d_model: int = 1280 decoder_layers: int = 32 decoder_attention_heads: int = 20 decoder_ffn_dim: int = 5120 encoder_layers: int = 32 num_mel_bins: int = 128 vocab_size: int = 51866 max_target_positions: int = 448 max_source_positions: int = 1500 activation_function: str = "gelu" scale_embedding: bool = False # special tokens decoder_start_token_id: int = 50258 eos_token_id: int = 50257 no_timestamps_token_id: int = 50364 # generation_config maps: "<|en|>" -> 50259, "transcribe" -> 50360 lang_to_id: dict[str, int] = field(default_factory=dict) task_to_id: dict[str, int] = field(default_factory=dict) # Logit suppression (HF generate parity): tokens never sampled, and # tokens additionally blocked for the first generated token. suppress_tokens: list[int] = field(default_factory=list) begin_suppress_tokens: list[int] = field(default_factory=list) @property def head_dim(self) -> int: return self.d_model // self.decoder_attention_heads # generation_config.json keys (not in config.json); the rest of the # dataclass fields map 1:1 to config.json keys by name. _GEN_KEYS = ( "lang_to_id", "task_to_id", "no_timestamps_token_id", "suppress_tokens", "begin_suppress_tokens", )
[docs] @classmethod def from_pretrained(cls, local_dir: str | Path) -> "WhisperModelConfig": local_dir = Path(local_dir) with open(local_dir / "config.json") as f: hf = json.load(f) gen: dict = {} gen_path = local_dir / "generation_config.json" if gen_path.exists(): with open(gen_path) as f: gen = json.load(f) # Every field is named to match its source key; pull from # generation_config.json for the _GEN_KEYS, else config.json. Fields # absent from both keep their dataclass default. names = {f.name for f in fields(cls)} values = { name: (gen if name in cls._GEN_KEYS else hf)[name] for name in names if name in (gen if name in cls._GEN_KEYS else hf) } return cls(**values)
[docs] def decoder_prompt_ids(self, language: str = "en", task: str = "transcribe") -> list[int]: """``<|startoftranscript|><|{lang}|><|{task}|><|notimestamps|>``.""" lang_token = f"<|{language}|>" if lang_token not in self.lang_to_id: raise ValueError( f"Unknown Whisper language {language!r}; " f"available: {sorted(t.strip('<|>') for t in self.lang_to_id)}" ) if task not in self.task_to_id: raise ValueError( f"Unknown Whisper task {task!r}; available: {list(self.task_to_id)}" ) return [ self.decoder_start_token_id, self.lang_to_id[lang_token], self.task_to_id[task], self.no_timestamps_token_id, ]