Transcrição de áudio local com Whisper
Transcrever áudio sem mandar nada para a nuvem é um problema resolvido hoje, e a peça central é o Whisper, o modelo de reconhecimento de fala da OpenAI, liberado em peso aberto. O que muda de uma instalação para outra não é o modelo, é a engine que o executa e como ela conversa com o hardware. Em uma máquina com GPU AMD, essa conversa é onde está toda a sutileza: a maior parte do material disponível assume CUDA, isto é, NVIDIA, e seguir esse caminho à risca leva a uma instalação que ignora a placa e roda tudo na CPU sem avisar. Este guia documenta o caminho que de fato aproveita uma Radeon, do zero, partindo de um Arch limpo.
Por que whisper.cpp, e por que Vulkan
Section titled “Por que whisper.cpp, e por que Vulkan”Existem três formas populares de rodar o Whisper, e a escolha entre elas, numa máquina AMD, é praticamente forçada. O openai-whisper original é PyTorch puro e depende de uma stack ROCm bem alinhada para tocar a GPU; funciona, mas é pesado e frágil quanto a versões. O faster-whisper é o mais rápido em geral, porém seu backend é o CTranslate2, que não tem suporte a ROCm, então numa Radeon ele cai para a CPU e perde justamente o que o tornava atraente. Sobra o whisper.cpp, a reimplementação em C/C++ da engine, e é ele a escolha certa aqui por um motivo concreto: além de CUDA e ROCm, ele tem um backend Vulkan, e Vulkan é o caminho de menor atrito numa placa AMD.
A razão para preferir Vulkan a ROCm não é desempenho, os dois são próximos, é operacional. O backend Vulkan fala com o driver Mesa (RADV) que já vem instalado em qualquer desktop Linux com vídeo funcionando. Não exige casar versões de ROCm com versões de PyTorch, não depende de a sua placa específica estar na lista de GPUs oficialmente suportadas por aquele release de ROCm, e compila contra headers leves. ROCm rende algo a mais em certos casos e faz sentido se a stack já estiver montada para outra coisa, mas como ponto de partida Vulkan entrega quase o mesmo resultado com uma fração do trabalho.
Vale registrar que ROCm e Vulkan não são excludentes na mesma máquina. É perfeitamente comum manter a stack ROCm instalada porque outro programa a usa (o Ollama com aceleração AMD, por exemplo) e ainda assim rodar o whisper.cpp por Vulkan. Nesse cenário a stack ROCm não é desperdício: ela está ali a serviço do outro programa, e o whisper simplesmente não a toca.
A armadilha: o pacote do AUR compila sem GPU
Section titled “A armadilha: o pacote do AUR compila sem GPU”Este é o detalhe que faz a diferença entre uma instalação que usa a placa e uma que não usa. O whisper.cpp está disponível no AUR como whisper.cpp-git, e instalá-lo com um helper qualquer parece resolver o problema. Mas o PKGBUILD padrão compila a engine sem nenhum backend de GPU. O resultado é um conjunto de bibliotecas que inclui apenas libggml-cpu.so, sem libggml-vulkan.so, e que portanto roda a transcrição inteira na CPU, por mais que a Radeon e o Vulkan estejam prontos ao lado.
O sintoma é silencioso, e é por isso que ele engana. A transcrição funciona, produz texto correto, só que devagar, e nada no comando indica que a GPU foi ignorada. A forma de flagrar isso é olhar os arquivos que o pacote instalou: se não houver um libggml-vulkan.so em /usr/lib, a engine é CPU-only. A correção é recompilar o pacote ativando o backend Vulkan, o que se faz editando o PKGBUILD.
Os pacotes envolvidos
Section titled “Os pacotes envolvidos”Convém separar o que normalmente já existe numa máquina de desktop do que provavelmente precisa ser instalado, porque numa reinstalação do zero a diferença importa.
O que costuma já estar presente, por fazer parte de qualquer sistema com vídeo e multimídia funcionando, é o driver Vulkan da AMD e o ffmpeg. Em termos de pacotes: vulkan-radeon (a implementação Vulkan do Mesa para Radeon, o RADV), vulkan-icd-loader (o carregador que liga a aplicação ao driver) e ffmpeg. Vale confirmar que estão lá, mas raramente faltam.
O que precisa ser instalado para compilar o whisper.cpp com Vulkan são as ferramentas de build e os headers do ecossistema Vulkan/SPIR-V. São pacotes que uma máquina de uso comum não tem por padrão:
sudo pacman -S --needed git cmake vulkan-headers shaderc glslang spirv-headers spirv-toolsA presença de shaderc, glslang, spirv-headers e spirv-tools não é acessória: o backend Vulkan do whisper.cpp compila shaders de computação em SPIR-V durante o build, e sem essas ferramentas o cmake falha logo na configuração, reclamando que não encontra o SPIRV-Headers. É um erro fácil de resolver depois que se sabe a causa, e é por isso que esses pacotes entram desde já na lista.
Para diagnóstico, vulkan-tools (que traz o vulkaninfo) ajuda a confirmar que o sistema enxerga a placa por Vulkan, mas não é obrigatório para compilar nem para rodar.
Recompilando o whisper.cpp com Vulkan
Section titled “Recompilando o whisper.cpp com Vulkan”O caminho mais limpo mantém o whisper.cpp como um pacote gerenciado pelo pacman, em vez de um binário solto compilado à mão. Para isso, baixe os arquivos de build do AUR, edite o PKGBUILD e reconstrua.
git clone https://aur.archlinux.org/whisper.cpp-git.gitcd whisper.cpp-gitA edição do PKGBUILD tem três partes, e cada uma tem uma razão. A primeira é acrescentar a flag que liga o backend Vulkan na chamada do cmake, dentro da função build(). A linha de configuração, que originalmente não passa nenhum flag de GPU, passa a incluir -DGGML_VULKAN=ON. Aproveita-se para garantir o build em modo Release, que ativa as otimizações de compilador e ajuda na parte do trabalho que ainda roda na CPU:
cmake -S "$srcdir/$pkgname" -B "$_BUILDDIR" -DCMAKE_INSTALL_PREFIX=/usr \ -W no-dev -D CMAKE_BUILD_TYPE=Release -D GGML_VULKAN=ONA segunda parte é declarar as dependências de build no array makedepends, para que o makepkg as instale automaticamente e para que o pacote seja reconstruível em outra máquina sem surpresas:
makedepends=("git" "cmake" "vulkan-headers" "shaderc" "glslang" "spirv-headers" "spirv-tools")A terceira é registrar a dependência de runtime no array depends. O binário compilado com Vulkan precisa do carregador Vulkan em tempo de execução para encontrar o driver:
depends=("vulkan-icd-loader")Com o PKGBUILD ajustado, a reconstrução e instalação é um comando só:
makepkg -siComo o pacote tem o mesmo nome do que já estava instalado, o pacman faz a substituição no lugar, trocando as bibliotecas antigas pelas novas, agora com o libggml-vulkan.so incluído. Não fica pacote duplicado e não é preciso remover o anterior antes. Por ser um pacote -git, a versão deriva do último commit do repositório; se em algum momento o pacman se recusar a instalar por achar que a versão não avançou, makepkg -sif força o build.
Confirmando que a GPU está sendo usada
Section titled “Confirmando que a GPU está sendo usada”Depois de instalar, duas verificações fecham a questão. A estática é olhar se a biblioteca Vulkan foi de fato instalada:
pacman -Ql whisper.cpp-git | grep vulkanA saída deve listar um libggml-vulkan.so. Se ele não aparecer, o build não pegou o backend e vale revisar o PKGBUILD.
A verificação que realmente importa é a dinâmica, no log de inicialização da primeira transcrição. O whisper.cpp imprime, ao subir, quais backends encontrou. Numa Radeon corretamente configurada aparece algo como:
ggml_vulkan: Found 1 Vulkan devices:ggml_vulkan: 0 = AMD Radeon RX 7900 GRE (RADV NAVI31) (radv) | fp16: 1 | matrix cores: KHR_coopmatwhisper_model_load: Vulkan0 total size = 1623.92 MBwhisper_backend_init_gpu: using Vulkan0 backendTrês sinais nessa saída confirmam que tudo está no lugar. O dispositivo Vulkan foi encontrado e nomeado, o que prova que a engine vê a placa. O Vulkan0 total size mostra que o modelo foi carregado na VRAM, não na RAM. E fp16: 1 com matrix cores: KHR_coopmat indica que a placa vai usar meia precisão e as instruções de multiplicação de matrizes do Vulkan, que é de onde vem boa parte da velocidade em GPUs RDNA3. Se em vez disso o log mostrar apenas backend de CPU, a GPU não foi ativada.
Escolhendo o modelo
Section titled “Escolhendo o modelo”O Whisper vem em vários tamanhos, e a engine os consome no formato GGML, um único arquivo .bin. Quanto maior o modelo, melhor a qualidade da transcrição e maior o consumo de memória e de tempo. A tabela abaixo dá a ordem de grandeza:
| Modelo | Parâmetros | Tamanho aproximado |
|---|---|---|
| tiny | 39M | ~75 MB |
| base | 74M | ~142 MB |
| small | 244M | ~466 MB |
| medium | 769M | ~1,5 GB |
| large-v3 | 1550M | ~3,1 GB |
| large-v3-turbo | 809M | ~1,6 GB |
A recomendação prática, para uma GPU com vários gigabytes de VRAM, é o large-v3-turbo. Ele entrega qualidade muito próxima do large-v3 completo, com tamanho na faixa do medium e velocidade bem maior, o que faz dele o melhor equilíbrio para transcrição em português. Baixá-lo é uma questão de pegar o arquivo do repositório de modelos do whisper.cpp:
wget https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.binHá ainda a questão da quantização. Modelos com sufixo como q8_0 são o mesmo modelo com os pesos comprimidos para 8 bits, ocupando perto da metade do espaço e da VRAM, com perda de qualidade quase imperceptível. Numa placa folgada de memória não há razão para abrir mão da precisão original, mas a versão quantizada é a escolha inteligente quando a VRAM é escassa ou quando se quer manter vários modelos em disco.
Extraindo o áudio do vídeo
Section titled “Extraindo o áudio do vídeo”O whisper.cpp não lê vídeo, e mesmo arquivos de áudio ele prefere num formato específico. O caminho confiável é extrair a faixa de áudio para um WAV PCM de 16 kHz, mono, antes de transcrever. Tomando um arquivo de exemplo qualquer, digamos aula.mkv:
ffmpeg -i aula.mkv -ar 16000 -ac 1 -c:a pcm_s16le aula.wavCada parâmetro responde a uma exigência do Whisper. O -ar 16000 reamostra para 16 kHz, que é a taxa em que o modelo foi treinado; alimentar outra taxa força uma reamostragem implícita ou degrada o resultado. O -ac 1 reduz para um canal, porque o modelo trabalha em mono e estéreo só desperdiçaria dados. O -c:a pcm_s16le grava PCM de 16 bits sem compressão, o formato que a engine lê diretamente sem depender de decodificadores adicionais.
Um ponto importante de segurança: essa operação é não destrutiva. O ffmpeg apenas lê o vídeo de entrada e escreve um arquivo novo e separado; ele nunca modifica o original, desde que o caminho de saída seja diferente do de entrada. A omissão da flag -y é uma proteção extra, porque sem ela o ffmpeg pergunta antes de sobrescrever um arquivo de saída que já exista, em vez de apagá-lo de imediato.
Se houver interesse em dividir um áudio longo em pedaços, por exemplo para testar a qualidade num trecho antes de processar tudo, a divisão também se faz com ffmpeg, em modo de cópia de stream, que é instantâneo e sem perda porque não recodifica:
ffmpeg -i aula.wav -f segment -segment_time 2362 -c copy parte_%02d.wavO -segment_time é o tamanho de cada pedaço em segundos, e o parte_%02d.wav é o padrão de nome dos arquivos gerados, numerados a partir de zero. O original permanece intacto, e os pedaços saem como arquivos novos.
Transcrevendo
Section titled “Transcrevendo”Com o modelo e o WAV em mãos, a transcrição é uma invocação do whisper-cli:
whisper-cli -m ggml-large-v3-turbo.bin -f aula.wav -l pt -otxt -osrt --print-progressOs flags relevantes têm efeito direto sobre o resultado. O -l pt força o português; sem ele a engine tenta autodetectar o idioma, o que quase sempre acerta mas adiciona incerteza desnecessária quando o idioma já se conhece. O -otxt gera a transcrição em texto puro e o -osrt gera uma legenda com marcações de tempo, no formato SubRip; os dois podem ser pedidos ao mesmo tempo, e existem ainda -ovtt para legenda web e -oj para JSON. O --print-progress mostra o avanço, útil em áudios longos. Há também -of para definir o nome base dos arquivos de saída, e -t para fixar o número de threads de CPU na parte do trabalho que não roda na GPU.
A diferença que o Vulkan faz aqui é grande. Numa Radeon RX 7900 GRE, o large-v3-turbo transcreve na ordem de vinte e poucas vezes mais rápido que o tempo real, o que significa que um áudio de duas horas e meia se resolve em torno de sete minutos. A mesma engine compilada sem GPU levaria um múltiplo disso.
A duração do áudio não satura a VRAM
Section titled “A duração do áudio não satura a VRAM”Há uma preocupação intuitiva, e equivocada, de que transcrever um áudio muito longo possa estourar a memória da placa. Não é o caso, e entender por quê evita tanto o medo desnecessário quanto a tentação de fatiar o áudio sem motivo.
O Whisper não carrega o áudio inteiro na GPU. Ele processa o som em janelas de trinta segundos, uma de cada vez. O que ocupa a VRAM é, portanto, fixo: o modelo, que tem um tamanho constante na faixa de um a dois gigabytes conforme a variante, mais os buffers de cálculo dimensionados para a janela de trinta segundos. Nenhum desses cresce com a duração total do áudio. Transcrever dois minutos ou três horas consome exatamente a mesma VRAM; o que muda é apenas o tempo de processamento. Na prática, o large-v3-turbo cabe com folga em menos de dois gigabytes durante a transcrição, e a única coisa que de fato cresce com a duração é o arquivo de texto de saída, cujo tamanho é trivial.
A divisão em pedaços, portanto, é útil para fluxo de trabalho, testar um trecho, paralelizar entre máquinas, isolar uma parte específica, mas nunca é necessária para evitar saturação de memória.
As alucinações do Whisper
Section titled “As alucinações do Whisper”Vale conhecer um comportamento do modelo para não se assustar com ele. Em trechos de silêncio, ruído de fundo ou música, o Whisper às vezes inventa texto, e a alucinação mais característica é gerar créditos de legenda como “Legenda por Fulano” ao final de um áudio. Isso vem dos dados de treino, cheios de legendas reais que terminavam assim. O efeito costuma ficar restrito às bordas, o comecinho e o fim, e em conteúdo com fala contínua é raro no meio. Para áudios com muito silêncio, dá para reduzir essas invenções ajustando limiares como --no-speech-thold ou ligando detecção de atividade de voz (VAD), mas para uma reunião ou aula comum costuma bastar apagar a linha espúria do final.
Reproduzindo do zero
Section titled “Reproduzindo do zero”Resumindo o caminho completo numa máquina recém-instalada, a sequência é: garantir o driver Vulkan e o ffmpeg, instalar as ferramentas de build com pacman -S --needed git cmake vulkan-headers shaderc glslang spirv-headers spirv-tools, baixar o whisper.cpp-git do AUR, editar o PKGBUILD para acrescentar -DGGML_VULKAN=ON ao cmake e declarar as dependências, rodar makepkg -si, confirmar a presença do libggml-vulkan.so e o uso do backend Vulkan no log, baixar o modelo large-v3-turbo, extrair o áudio com ffmpeg para WAV de 16 kHz mono, e transcrever com o whisper-cli. O ponto que mais economiza tempo de quem repete esse processo é lembrar da armadilha do build CPU-only: tudo o mais funciona mesmo se essa parte falhar, só que devagar e sem tocar a placa.
Palavras-chave
Section titled “Palavras-chave”Ferramentas: whisper.cpp, whisper-cli, ffmpeg, pacman, makepkg, AUR, PKGBUILD, cmake Conceitos: transcrição de áudio, speech-to-text, quantização, VRAM, SPIR-V, shaders de computação, build CPU-only, alucinação, VAD Hardware: GPU AMD, Radeon, RX 7900 GRE, RDNA3, Vulkan, RADV, ROCm, CUDA Modelos: Whisper, GGML, large-v3-turbo, large-v3, medium, q8_0 Formatos: WAV, PCM s16le, 16 kHz mono, SRT, VTT, JSON