jvscribe-ptbr-zipformer-ctc-64m
ASR de português brasileiro que roda em tempo real sobre CPU, sem GPU e sem chamada de rede. Zipformer-CTC de 64M parâmetros, quantizado int8, com cabeça de fonema auxiliar.
A aposta é especialização: um modelo que só faz PT-BR cabe em dezenas de milhões de parâmetros onde um multilíngue de 600M não fecha real-time em CPU.
Resultados
[MEDIDO] 2026-07-30 — FLEURS pt_br test[0:100], 2.552 palavras, decode greedy CTC,
ONNX Runtime int8, 4 threads, i7 12-core com load average < 1.
| métrica | valor |
|---|---|
| WER | 15,99% |
| CER | 7,30% |
| RTFx | 40,0× |
RTFx é a razão entre duração do áudio e tempo de processamento — 40× significa que 40 segundos de áudio são transcritos em 1 segundo. O requisito do produto é ≥ 6×.
Como este peso foi escolhido
Dois candidatos foram medidos no mesmo conjunto, com a mesma régua de normalização:
| peso | WER | CER | RTFx |
|---|---|---|---|
model.int8.onnx (média de 112k+124k) |
15,99% | 7,30% | 40,0× |
alternates/ckpt124k.int8.onnx (checkpoint único) |
17,32% | 7,39% | 34,3× |
Bootstrap pareado de 5.000 reamostragens: IC95% do delta = [−2,25, −0,43] pp. O intervalo não cruza zero — a vantagem do checkpoint averaging é real, não ruído amostral.
O model_card.json é a autoridade legível por máquina sobre qual arquivo é o canônico.
Não deduza o peso oficial pelo nome do arquivo — neste projeto, escolher por nome já
entregou o modelo pior em silêncio.
Uso
import onnxruntime as ort
sess = ort.InferenceSession("model.int8.onnx", providers=["CPUExecutionProvider"])
tokens = [l.split()[0] for l in open("tokens.txt", encoding="utf-8")]
# entrada: log-mel fbank 80-bin, kaldi/HTK, 16 kHz -> (N, T, 80)
logits = sess.run(None, {"x": feats, "x_lens": lens})[0] # (N, T, 500)
# decode: CTC greedy — argmax, colapsa repetições, remove o blank (id 0)
O pipeline completo (fbank, segmentação, decode, lote) está em
jvscribe:
python3 jvscribe/batch/batch_transcribe.py --input-dir audios/ --out-dir saida/
Arquivos
| arquivo | tamanho | papel |
|---|---|---|
model.int8.onnx |
68 MB | modelo oficial — WER 15,99% |
tokens.txt |
5 KB | 500 tokens BPE emitíveis; fingerprint SHA-256 9fcb45e4… |
model_card.json |
1 KB | contrato legível por máquina (arquivo canônico, sha256, fingerprint do vocabulário, WER e condição da medição) |
alternates/ckpt124k.int8.onnx |
68 MB | checkpoint único, WER 17,32% |
alternates/ckpt124k.fp32.onnx |
249 MB | fp32 do acima — ponto de partida para requantizar |
finetune/ |
2,6 GB | tudo para retomar o treino — ver finetune/README.md |
O par (modelo, vocabulário) não é intercambiável. Dois artefatos deste projeto têm 500
tokens cada e 492 dos 500 ids mapeiam para tokens diferentes: trocar o tokens.txt produz
texto plausível e errado, sem erro algum. Valide pelo vocab_fingerprint do model_card.json,
nunca pela contagem de tokens.
Arquitetura
| encoder | Zipformer, 6 stacks |
num-encoder-layers |
2,2,3,4,3,2 |
feedforward-dim |
512,768,1024,1536,1024,768 |
encoder-dim |
192,256,384,512,384,256 |
encoder-unmasked-dim |
192,192,256,256,256,192 |
| decoder | CTC greedy (sem transducer) |
| cabeça auxiliar | CTC de fonema (--use-phoneme-ctc 1) |
| tokenização | SentencePiece BPE, 500 unidades, PT-BR dedicado |
| features | log-mel fbank 80-bin, kaldi/HTK, 16 kHz |
| parâmetros | ~64M |
| quantização | int8 dinâmica (ONNX Runtime) |
A cabeça de fonema é auxiliar de treino — ela regulariza o encoder e sai do grafo de inferência. Ganho medido em M4: −4,74% relativo de WER (IC95% [2,79, 6,72]).
Treino
Receita icefall zipformer sobre
egs/commonvoice/ASR. Corpus: TAGARELA (8.972 h, 91% PT-BR, pseudo-rotulado) com
finetune em CORAA. Suíte de avaliação do projeto inclui Common Voice 21.0 (CC0) e
MLS-PT (CC-BY-4.0).
Para retomar o treino, ver finetune/README.md. Ele traz os comandos
reais, as quatro flags de arquitetura sem as quais o checkpoint não carrega, e as armadilhas
que já custaram runs inteiros neste projeto.
Limitações — leia antes de usar em produção
Telefonia 8 kHz não foi medida. Todos os números aqui são banda larga 16 kHz. A literatura indica penalidade de 2–3× em canal telefônico; para este modelo isso é
[DESCONHECIDO].Fala espontânea de call center não foi medida. FLEURS é leitura de notícias, um regime mais fácil que conversa real.
Só português brasileiro. Não há suporte multilíngue nem code-switching validado.
Overfitting ao professor. O finetune de M5 apresenta overfitting aos pseudo-rótulos do Whisper presentes no TAGARELA — o gargalo é qualidade de dado, não capacidade do encoder.
Este modelo NÃO é streaming. É não-causal: a atenção enxerga o contexto inteiro nos dois sentidos. Três confirmações independentes — o metadado do artefato diz
comment: "non-streaming zipformer2 CTC"; o grafo ONNX não tem entrada nem saída de estado (sóx,x_lens→log_probs,log_probs_len); e os scripts de treino nunca passam--causal 1, cujo default no icefall éFalse.Consequência prática: dá para usá-lo ao vivo com janela deslizante + LocalAgreement-2 (é o que
jvscribe/realtime/faz, técnica do Whisper-Streaming), mas cada atualização reprocessa a janela inteira. Medido: 121 ms para reprocessar 6 s contra 11 ms que custaria processar só os 0,5 s novos — 10,6× de trabalho redundante por hop, que é o piso de latência do desenho. Um modelo com cache de estado remove esse termo, e isso exige retreinar com--causal 1e reexportar com entradas de estado; não é ajuste de runtime.avg-124k-112k.ptfoi reconstruído. O.ptoriginal chegou truncado da nuvem; verfinetune/README.md § Incidente. A equivalência bit a bit com o original não foi verificada.
Licença
other — os termos de redistribuição são herdados dos corpora de treino e ainda não foram
formalmente apurados. O TAGARELA carrega pseudo-rótulos gerados por Whisper; CORAA, Common
Voice e MLS-PT têm licenças próprias e distintas entre si. Este repositório é privado
justamente porque essa apuração está pendente.
Não redistribua nem use comercialmente sem antes revisar a licença de cada corpus de origem.
Citação
@misc{jvscribe2026,
title = {jvscribe: ASR PT-BR em tempo real sobre CPU},
author = {Paulo Henrique},
year = {2026},
note = {Zipformer-CTC 64M, int8, WER 15,99\% em FLEURS pt_br}
}
Evaluation results
- WER on FLEURS pt_br (test[0:100])test set self-reported15.990
- CER on FLEURS pt_br (test[0:100])test set self-reported7.300