
Classificação com LLM Local em Escala
Source:vignettes/llm-local-em-escala.Rmd
llm-local-em-escala.RmdVisão geral
Esta vignette cobre o fluxo de ac_qual_code() quando o
modelo roda localmente (LM Studio, llama.cpp, vLLM,
Ollama em modo OpenAI) em vez de um provedor em nuvem: o cenário mais
comum quando os documentos não podem sair do ambiente do pesquisador
(dados sensíveis, acordos de confidencialidade institucional) ou quando
o volume de documentos tornaria o custo por token de um provedor pago
proibitivo. A orientação aqui vem de uso em produção classificando atos
normativos administrativos, com documentos de até 1,4 milhão de
caracteres e rodadas de milhares de documentos.
Classificar localmente troca custo monetário e risco de vazamento de dados por gerência própria de infraestrutura: tamanho de contexto, throughput e estabilidade do servidor passam a ser problema do pesquisador, não do provedor. As seções abaixo cobrem exatamente os pontos onde isso dá errado na prática.
1. Conectando a um servidor local
[ac_chat_local()] cria um Chat do ellmer
apontando para qualquer servidor compatível com a API da OpenAI, sem
exigir uma chave de API real:
library(acR)
# LM Studio, servindo um modelo na porta padrao (1234)
chat_local <- ac_chat_local(model = "qwen2.5-14b-instruct")
# llama.cpp (llama-server), porta customizada
chat_llama <- ac_chat_local(
model = "modelo-local",
base_url = "http://localhost:8080/v1"
)Use chat = chat_local em [ac_qual_code()] exatamente
como usaria qualquer outro Chat do ellmer:
coded <- ac_qual_code(corpus, codebook, chat = chat_local)2. Dimensionando o contexto antes de gastar uma chamada
Servidores locais costumam ter janelas de contexto menores que provedores em nuvem (4k–32k tokens é comum, contra 128k+ de modelos de ponta). Antes de rodar um corpus inteiro, estime quantos tokens cada documento vai consumir:
tamanhos <- ac_estimate_tokens(corpus$text)
summary(tamanhos)
# Quantos documentos provavelmente excedem uma janela de 8k tokens?
sum(tamanhos > 8000)ac_estimate_tokens() é uma heurística (caracteres por
token): não faz nenhuma chamada de rede. Para uma estimativa calibrada
contra o modelo e tokenizador real, rode
ac_calibrate_tokens() numa amostra pequena antes da rodada
completa:
chat_factory <- function(temperature, system_prompt) {
ac_chat_local(model = "qwen2.5-14b-instruct",
temperature = temperature, system_prompt = system_prompt)
}
razao <- ac_calibrate_tokens(chat_factory, sample(corpus$text, 10))
tamanhos_calibrados <- ac_estimate_tokens(corpus$text, chars_per_token = razao)Documentos que excedem a janela de contexto do servidor não devem ser
enviados como estão: considere dividir manualmente em partes menores
antes de montar o ac_corpus(), ou reduzir
reasoning_length para liberar espaço para a resposta.
3. Rodando em lotes com checkpoint
Uma rodada de milhares de documentos contra um servidor local pode
levar horas, e qualquer interrupção (queda de energia, servidor
reiniciado, notebook suspenso) sem checkpoint significa reclassificar
tudo. Use checkpoint_dir:
coded <- ac_qual_code(
corpus, codebook,
chat = chat_local,
checkpoint_dir = "checkpoints/rodada_2026_10",
max_retries = 2L,
live = "progress"
)Cada documento é salvo (gravação atômica) assim que classificado. Se
a rodada for interrompida, rodar o mesmo comando de novo pula os
documentos já concluídos e continua de onde parou.
live = "progress" mostra uma barra enxuta (documento atual
+ status) em vez de ecoar categoria e raciocínio a cada uma das
k_consistency rodadas: importante para não poluir o console
numa rodada de milhares de documentos.
Se os parâmetros da rodada mudarem (codebook revisado, modelo
diferente, k_consistency diferente), o
checkpoint_dir é invalidado e recomeça do zero
automaticamente (com aviso), para nunca misturar classificações de
codebooks diferentes sem que o pesquisador perceba.
4. Lidando com falhas técnicas
Servidores locais sob carga sustentada falham de formas que
provedores em nuvem bem estabelecidos raramente mostram: timeout por
sobrecarga de memória, resposta truncada, conexão recusada depois de
muitas horas rodando. A coluna status do resultado
diferencia os tipos de falha:
table(coded$status)
# ok -- classificado normalmente
# resposta_invalida -- o servidor respondeu, mas não em JSON valido
# estouro_contexto -- documento excedeu a janela de contexto
# timeout -- servidor não respondeu a tempo
# erro_servidor -- outro erro de rede/servidormax_retries tenta de novo (com espera exponencial) para
timeout e erro_servidor, mas não para
estouro_contexto: repetir a mesma chamada com o mesmo texto
não vai resolver um documento que simplesmente não cabe na janela do
modelo. Se mais da metade dos últimos documentos falhar, a rodada é
interrompida automaticamente: sinal de que o servidor está instável e
insistir só desperdiçaria tempo.
5. Validação humana por categoria (multilabel)
Quando o codebook é multilabel = TRUE, a métrica
agregada de [ac_qual_reliability()] mistura decisões binárias
independentes num único número: uma concordância “boa” pode esconder uma
categoria específica indo mal. Use by_variable = TRUE:
rel <- ac_qual_reliability(
llm = coded,
human = humano_df,
by_variable = TRUE,
exclude_status = c("estouro_contexto", "timeout", "erro_servidor")
)
attr(rel, "by_variable")exclude_status remove da comparação documentos cuja
rodada principal falhou: uma categoria vinda de uma resposta inválida ou
um timeout não é um dado confiável sobre concordância, e misturá-la nas
métricas por variável distorceria o diagnóstico.
Para montar a amostra de validação humana de forma representativa da
população (não só de cada categoria igualmente, como
strategy = "stratified" faz), use
strategy = "proportional":
amostra <- ac_qual_sample(
coded, n = 100,
strategy = "proportional", strata = "especie_normativa"
)A amostra ganha peso_amostral, útil se você for agregar
a validação numa estimativa populacional ponderada.
6. Auditando a configuração da execução
Toda chamada de [ac_qual_code()] anexa um atributo
run_info ao resultado, com a configuração efetiva da
rodada:
info <- attr(coded, "run_info")
info$model
info$params_efetivos$temperature # a temperatura que o PROVEDOR confirmou
info$temperatura_pedida # a temperatura que você PEDIU
info$codebook_hashCompare params_efetivos$temperature com
temperatura_pedida antes de reportar
confidence_score num artigo: se um Chat
pré-configurado não confirmar a temperatura pedida,
ac_qual_code() já emite um aviso explícito no momento da
rodada, mas run_info permite auditar isso depois, inclusive
em rodadas de terceiros.
Referências
Wang, X. et al. (2023). Self-Consistency Improves Chain of Thought Reasoning in Language Models. ICLR.
Gilardi, F.; Alizadeh, M.; Kubli, M. (2023). ChatGPT Outperforms Crowd Workers for Text-Annotation Tasks. PNAS, 120(30).
Gwet, K. L. (2014). Handbook of Inter-Rater Reliability (4th ed.). Advanced Analytics.