Skip to content

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.

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.

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:

Terminal window
sudo pacman -S --needed git cmake vulkan-headers shaderc glslang spirv-headers spirv-tools

A 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.

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.

Terminal window
git clone https://aur.archlinux.org/whisper.cpp-git.git
cd whisper.cpp-git

A 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:

Terminal window
cmake -S "$srcdir/$pkgname" -B "$_BUILDDIR" -DCMAKE_INSTALL_PREFIX=/usr \
-W no-dev -D CMAKE_BUILD_TYPE=Release -D GGML_VULKAN=ON

A 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:

Terminal window
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:

Terminal window
depends=("vulkan-icd-loader")

Com o PKGBUILD ajustado, a reconstrução e instalação é um comando só:

Terminal window
makepkg -si

Como 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.

Depois de instalar, duas verificações fecham a questão. A estática é olhar se a biblioteca Vulkan foi de fato instalada:

Terminal window
pacman -Ql whisper.cpp-git | grep vulkan

A 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_coopmat
whisper_model_load: Vulkan0 total size = 1623.92 MB
whisper_backend_init_gpu: using Vulkan0 backend

Trê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.

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:

ModeloParâmetrosTamanho aproximado
tiny39M~75 MB
base74M~142 MB
small244M~466 MB
medium769M~1,5 GB
large-v31550M~3,1 GB
large-v3-turbo809M~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:

Terminal window
wget https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin

Há 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.

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:

Terminal window
ffmpeg -i aula.mkv -ar 16000 -ac 1 -c:a pcm_s16le aula.wav

Cada 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:

Terminal window
ffmpeg -i aula.wav -f segment -segment_time 2362 -c copy parte_%02d.wav

O -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.

Com o modelo e o WAV em mãos, a transcrição é uma invocação do whisper-cli:

Terminal window
whisper-cli -m ggml-large-v3-turbo.bin -f aula.wav -l pt -otxt -osrt --print-progress

Os 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.

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.

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.

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.

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