Automatic Speech Recognition
Transformers
Safetensors
English
asr_model
feature-extraction
asr
speech-recognition
speech-to-text
audio
speech-llm
word-timestamps
speaker-diarization
qwen
granite-speech
custom_code
Instructions to use mazesmazes/tiny-audio with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use mazesmazes/tiny-audio with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("automatic-speech-recognition", model="mazesmazes/tiny-audio", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("mazesmazes/tiny-audio", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 6,844 Bytes
26ce2ed | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | """Qwen3-ASR's audio encoder behind ASRModel's encoder interface.
`Qwen/Qwen3-ASR-1.7B-hf` (Apache-2.0) is #9 on the Open ASR leaderboard (mean
of 9 4.936, 2026-10-20), the best system whose encoder loads natively in our
transformers, and its encoder was trained to feed a Qwen3 decoder.
transformers' `Qwen3ASREncoder` owns its preprocessing contract, which is why
it is used as-is rather than reimplemented:
- its convolutions run on independent 1 s chunks (100 mel frames, 13 tokens
each) and attention is limited to 8 s windows, both inside `forward`;
- it takes the feature extractor's mel mask and drops padding itself.
Two things differ from what ASRModel expects, and this module bridges them:
- the encoder returns the valid frames of the whole batch packed into one
`(sum(tokens), d_model)` tensor, which is split back into a padded
`(batch, tokens, d_model)` tensor here;
- it lives inside the full ASR checkpoint, so only its `model.audio_tower.*`
tensors are read.
"""
import json
from dataclasses import dataclass
from pathlib import Path
from typing import cast
import torch
from huggingface_hub import hf_hub_download
from huggingface_hub.errors import EntryNotFoundError
from safetensors import safe_open
from torch import nn
from transformers.models.qwen3_asr import Qwen3ASRConfig, Qwen3ASREncoder, Qwen3ASREncoderConfig
from transformers.utils.generic import ModelOutput
try:
from .asr_config import (
QWEN3_ASR_CHUNK_FRAMES,
QWEN3_ASR_CONV_LAYERS,
compute_encoder_output_length,
)
except ImportError: # flat layout on the Hub: sibling modules, no package
from asr_config import (
QWEN3_ASR_CHUNK_FRAMES,
QWEN3_ASR_CONV_LAYERS,
compute_encoder_output_length,
)
# The encoder's tensors inside the ASR checkpoint.
_WEIGHT_PREFIX = "model.audio_tower."
@dataclass
class Qwen3ASRAudioOutput(ModelOutput):
"""Padded encoder states and the mask of their valid frames."""
last_hidden_state: torch.Tensor | None = None
# (batch, tokens), True on valid frames. ASRModel checks the prompt's audio
# token counts against it, as it does for Granite's own returned mask.
attention_mask: torch.Tensor | None = None
class Qwen3ASRAudioEncoder(nn.Module):
"""`Qwen3ASREncoder` with its packed output split back into a padded batch."""
def __init__(self, encoder: Qwen3ASREncoder) -> None:
"""Wrap a loaded encoder; `config` is exposed for encoder_dim detection."""
super().__init__()
self.encoder: Qwen3ASREncoder = encoder
self.config: Qwen3ASREncoderConfig = encoder.config
def forward(
self, input_features: torch.Tensor, attention_mask: torch.Tensor | None = None
) -> Qwen3ASRAudioOutput:
"""Encode `(batch, n_mels, frames)` mel features to `(batch, tokens, d_model)`.
`frames` must be a multiple of 100, which Qwen3-ASR's feature extractor
guarantees. Without `attention_mask` every frame counts as audio.
"""
batch = input_features.shape[0]
if attention_mask is None:
attention_mask = torch.ones(
batch, input_features.shape[-1], dtype=torch.long, device=input_features.device
)
mask = attention_mask.to(device=input_features.device, dtype=torch.long)
packed = self.encoder(input_features=input_features, input_features_mask=mask)
flat = packed.last_hidden_state
lengths = compute_encoder_output_length(
mask.sum(dim=-1), QWEN3_ASR_CONV_LAYERS, QWEN3_ASR_CHUNK_FRAMES
)
if int(lengths.sum()) != flat.shape[0]:
msg = (
f"Qwen3-ASR encoder packed {flat.shape[0]} frames but the chunked length "
f"formula expects {int(lengths.sum())} ({lengths.tolist()} per clip)."
)
raise ValueError(msg)
rows = list(flat.split(lengths.tolist()))
hidden = nn.utils.rnn.pad_sequence(rows, batch_first=True)
valid = torch.arange(hidden.shape[1], device=hidden.device)[None, :] < lengths[:, None]
return Qwen3ASRAudioOutput(last_hidden_state=hidden, attention_mask=valid)
@classmethod
def from_pretrained(
cls, repo_id: str, dtype: torch.dtype, attn_implementation: str | None
) -> "Qwen3ASRAudioEncoder":
"""Build the encoder from `repo_id`'s audio config and load only its tensors.
Built on CPU, not the meta device: the sinusoidal position table is a
non-persistent buffer that is absent from the checkpoint, so a meta
build would leave it without values.
"""
audio_config = Qwen3ASRConfig.from_pretrained(repo_id).audio_config
if not isinstance(audio_config, Qwen3ASREncoderConfig):
msg = f"{repo_id} has no Qwen3-ASR audio_config (got {type(audio_config).__name__})"
raise TypeError(msg)
encoder = Qwen3ASREncoder(audio_config)
state: dict[str, torch.Tensor] = {}
for path, names in _encoder_shards(repo_id).items():
with safe_open(path, framework="pt") as f:
for name in names:
state[name.removeprefix(_WEIGHT_PREFIX)] = f.get_tensor(name)
# strict: a renamed or missing tensor must fail here, not train on random weights.
encoder.load_state_dict(state, strict=True)
# PreTrainedModel.to is functools.wraps'd, which pyright cannot bind as a
# method; it casts in place.
cast(nn.Module, encoder).to(dtype=dtype)
# After the cast: transformers warns when FA2 is set on an fp32 model.
# Its other warning, "not initialized on GPU", is expected here; the
# Trainer moves the whole ASRModel to the GPU before the first step.
if attn_implementation:
encoder.set_attn_implementation(attn_implementation)
return cls(encoder)
def _encoder_shards(repo_id: str) -> dict[str, list[str]]:
"""Local path of each checkpoint file holding encoder tensors -> those tensor names."""
try:
index_path = hf_hub_download(repo_id, "model.safetensors.index.json")
except EntryNotFoundError: # a single-file checkpoint (the 1.7B and 0.6B are) has no index
path = hf_hub_download(repo_id, "model.safetensors")
with safe_open(path, framework="pt") as f:
keys = cast("list[str]", f.keys())
names = [k for k in keys if k.startswith(_WEIGHT_PREFIX)]
return {path: names}
weight_map: dict[str, str] = json.loads(Path(index_path).read_text())["weight_map"]
shards: dict[str, list[str]] = {}
for name, shard in weight_map.items():
if name.startswith(_WEIGHT_PREFIX):
shards.setdefault(shard, []).append(name)
return {hf_hub_download(repo_id, shard): names for shard, names in shards.items()}
|