KEN’S CAT LOG
▣

Criei do zero uma LLM para RAG em croata com 300 milhões de parâmetros. Roda na CPU

Depois do GPT-2 da vez anterior, desta vez criei do zero uma LLM em croata usando uma única RTX 4090. O objetivo era RAG: um modelo que encontra a resposta no texto fornecido e responde citando uma frase que sirva de evidência. E partindo do princípio de que, em produção, ele rodará na CPU.

Primeiro, a conclusão.

  • Cerca de 68 horas na GPU, do zero até a conclusão (incluindo tentativa e erro). Destas, o pré-treinamento levou 51 horas
  • 320 milhões de parâmetros. Acerta 71% das perguntas sobre parágrafos e 44% das perguntas sobre textos longos, da ordem de 3.000 tokens
  • Responde cada pergunta em 3 a 16 segundos usando apenas a CPU. Os caracteres aparecem em uma velocidade que dá para acompanhar a olho nu

Tempo gasto no treinamento

Etapa Tempo Conteúdo
Geração dos dados de treinamento Cerca de 4,5 horas 45 mil pares de QA da Wikipedia e 50 mil traduções de conversas com EuroLLM-9B (vLLM)
Tokenizador e tokenização Cerca de 2,5 horas (CPU) Criei uma vocabulário próprio de 32 mil termos para o croata
Pré-treinamento 50 horas e 46 minutos 7 bilhões de tokens. No último 10%, ampliei o contexto para 4.096
SFT Cerca de 35 minutos Ensinei a responder com citações e a recusar quando não houver resposta
Aprendizado por reforço (GRPO) Cerca de 11 horas 400 etapas, mais ajustes para outras 300 etapas

O tokenizador próprio consegue representar o croata com aproximadamente metade dos tokens do tokenizador do GPT-2. Com 4.096 tokens, ele consegue ler cerca de 17 mil caracteres.

O modelo criado

Item Conteúdo
Parâmetros 320 milhões (24 camadas, 1024 dimensões)
Arquitetura Família Llama / Qwen3 (GQA, SwiGLU, RoPE, RMSNorm + QK-Norm)
Comprimento de contexto 4.096 tokens
Formato de saída Resposta + Izvor: „uma frase extraída diretamente do contexto“. Se não houver resposta: “U tekstu nema odgovora na ovo pitanje. (Não há resposta para esta pergunta no texto.)”
Execução Pensado para CPU (cerca de 1,3 GB em fp32)

Além disso, adicionei externamente um mecanismo que verifica por código se a citação gerada realmente está no contexto e, se não estiver, a substitui por “sem resposta”. O ponto é não confiar totalmente no modelo e tornar possível verificar a evidência.

Problemas que descobri ao criar

1. Mesmo sem resposta, ele inventa uma toda vez. No início, ensinei apenas perguntas e respostas comuns. A taxa de acerto aumentou, mas, ao receber um texto que não continha a resposta, ele inventava uma resposta plausível em 100% dos casos. O pior cenário para RAG. Ao ensinar citações e como “recusar”, as invenções caíram para alguns %.

2. Ele recusa demais. Quando a avaliação do aprendizado por reforço era “−0,5 se errar; 0 se recusar”, ele aprendeu que, em textos longos e difíceis, era vantajoso escapar recusando, e passou a recusar em 70% dos casos. Ele explora perfeitamente a brecha na regra de avaliação. Ao mudar a penalidade de recusa para −0,3, ele oscilou para o lado oposto, quase nunca recusava e inventava respostas em 40% dos casos. Ficou indo e voltando como uma gangorra.

3. Erros ao copiar a citação. Em textos longos, a resposta estava certa, mas ele trocava a ordem das palavras da citação ou citava a primeira frase do artigo. Em contexto longo, a citação também estava correta em apenas cerca da metade dos casos.

4. Erros que passam pela verificação. Ele citou com precisão uma frase como “nasceu em ○○”, pertencente a outro artigo misturado ao contexto, e respondeu o local de nascimento de outra pessoa. Como a citação era real, a verificação não conseguiu barrar isso.

5. Falhas nos próprios dados de avaliação. Em alguns casos, contextos que supostamente não tinham resposta ainda continham parte dela; o modelo estava correto, mas era considerado como tendo “inventado” a resposta.

Desempenho final

300 perguntas para cada avaliação. Foi usado o modelo da etapa 100 do aprendizado por reforço ajustado, que tinha o melhor equilíbrio entre recusar e responder.

Contexto Correta Incorreta Recusou
Parágrafo (cerca de 100 a 400 tokens) 71,3% 16,7% 12,0%
Contexto longo (1.500 a 3.500 tokens) 44,0% 20,3% 35,7%
Contexto sem resposta Recusou corretamente Inventou
Parágrafo 97,7% 2,3%
Contexto longo 92,3% 7,7%

Após passar pela verificação, a “proporção de respostas corretas entre as que foram respondidas” foi de 73,8%. Ainda está longe da meta de 90%.

Aliás, se cada pergunta fosse resolvida 8 vezes e bastasse acertar uma delas, o desempenho chegava a 91% em parágrafos e 74% em textos longos (medido no último checkpoint). Esse número quase não mudou antes e depois do aprendizado por reforço; o que mudou foi a capacidade de “resolver sempre”. Os números deixaram claro que o aprendizado por reforço, mais do que acrescentar uma nova habilidade, serve para fazer o modelo resolver sempre problemas que ele só consegue resolver ocasionalmente.

Testando na CPU

Criei uma interface Web de teste e rodei tudo apenas na CPU (i7-13700K). A velocidade com que os caracteres aparecem é a própria velocidade de geração.

Parágrafo (263 tokens): 0,3 segundo até o primeiro caractere, cerca de 11 tokens/s, 7 segundos no total

Demonstração respondendo a uma pergunta sobre um parágrafo na CPU再生

Contexto longo (3.215 tokens): cerca de 5 segundos para ler o texto, cerca de 5 tokens/s, 19 segundos no total. O amarelo marca a frase citada, e o modelo encontra corretamente uma frase certa entre 3.000 tokens

Demonstração respondendo a partir de um contexto longo na CPU再生

O vídeo ficou um pouco mais lento porque foi gravado durante a execução. Medindo 20 perguntas por vez sem gravar, obtive 15 tokens/s e 3,4 segundos por pergunta para parágrafos, e 7 tokens/s e 16 segundos por pergunta para contextos longos. Como as respostas são curtas, em torno de 50 tokens, parece uma velocidade prática até na CPU.

Considerações

  • Para modelos pequenos, o mais importante é fazê-los dizer “não sei o que não sei”. Em RAG, reduzir invenções é mais eficaz do que aumentar a taxa de acerto. A combinação de citações + recusa + verificação externa funcionou muito bem
  • 300 milhões é o limite para textos longos. Mesmo com 8 tentativas, o desempenho em textos longos chega apenas a 74%. Para buscar 90% em textos longos, provavelmente seriam necessários cerca de 1 bilhão de parâmetros
  • Não basta verificar se “a citação realmente existe”. É preciso conferir se ela contém o número ou nome que responde à pergunta e se o sujeito é a mesma pessoa mencionada na pergunta
  • Um modelo de 300 milhões que roda na CPU é ideal para perguntas e respostas sobre documentos que não podem ir para a internet. Em um notebook ou servidor sem GPU, ele pode responder com base apenas nos documentos locais. A velocidade é de alguns segundos a dezenas de segundos por pergunta e, com quantização, deve ficar ainda mais rápido (não verificado)

A próxima tentativa será uma versão de 1 bilhão de parâmetros, buscando 90% em contextos longos.