🧠 Memórias: Dissecando o Motor LLM (Baixo Nível) | Governança
🧠 Memórias: Dissecando o Motor LLM (Baixo Nível)
Este documento serve como registro arquitetural e memória técnica da fase de implementação manual dos bindings C/C++ do llama.cpp para a Erlang VM via Rust. Embora a arquitetura atual utilize a abstração de alto nível (llama-cpp-2), o entendimento destas entranhas é fundamental para o design do Gleam-BR.
1. O Abismo do Linker (C++ e SO)
A compilação cruzada de um motor de IA moderno revelou as pesadas dependências de sistema operacional ocultas sob os tensores:
-
*Padrões de Nomenclatura GNU:* Compiladores Rust no Windows (MinGW) exigem o prefixo estrito
libnos arquivos estáticos (.a), forçando manipulação física de arquivos (std::fs::rename) para alinhar saídas do CMake com as expectativas do linker. -
*ABI do C++ e Orientação a Objetos:* O
llama.cpputiliza classes virtuais (vtable,__cxxabiv1). O linker C padrão do Rust falha ao fundi-las sem a injeção explícita da Biblioteca Padrão do C++ (libstdc++). -
*Multithreading em Baixo Nível:* A ativação de cálculos paralelos na CPU pelo motor GGML invoca o GNU OpenMP (
GOMP_barrier). A ausência dalibgompimpede a geração da DLL. -
*Acesso Direto ao Hardware (Win32 API):* Para otimizar o cache L1/L2/L3 e a topologia de núcleos, o backend de CPU do GGML lê chaves físicas do Registro do Windows. Isso obriga a linkagem da API nativa avançada do Windows (
advapi32.dll).
2. A Dança do Buffer (Alocação Dinâmica C/Rust)
Linguagens seguras (Rust) e inseguras (C++) tratam memória de forma diametralmente oposta.
Na API do llama.cpp, funções como llama_tokenize não retornam simplesmente um erro se o buffer fornecido for pequeno. Elas retornam um *número negativo* representando exatamente quantos bytes faltam.
* *Lição:* O padrão ouro de FFI (Foreign Function Interface) exige uma chamada otimista, captura do erro negativo, realocação dinâmica (redimensionamento do vetor) e uma segunda chamada exata.
3. O Laço de Inferência Nu e Cru
Geralmente escondido por bibliotecas (como o método .generate()), o laço real de inferência de uma rede neural possui 5 etapas físicas estritas:
-
*Tokenização:* Conversão de strings para IDs inteiros baseados no vocabulário do modelo (usualmente Byte Pair Encoding - BPE). Requer atenção especial a tokens de controle (
BOS- Begin of Stream). -
*Formação de Lote (Batching):* Os tokens não entram soltos na rede. Eles são empacotados num struct
llama_batchque dita a posição deles no contexto temporal da IA. -
*Avaliação (Decode):* A execução da matemática pesada. A função
llama_decodeacende os núcleos do processador, passando o lote pela matriz de pesos (tensores) para atualizar o contexto (KV Cache). -
*Amostragem (Sampling & Logits):* A rede não retorna uma palavra, ela retorna uma matriz de probabilidades (
Logits) para cada token possível no vocabulário (ex: 128.000 probabilidades). Um Greedy Sampler (Amostrador Guloso) varre essa matriz inteira manualmente em busca do maior valor (probabilidade máxima) para decidir o próximo token. -
*Detokenização e Fim de Fluxo:* O ID numérico vencedor é testado contra o token de parada (
EOS- End of Stream). Se não for o fim, ele é convertido de volta para caracteres (char*) e reinjetado no Passo 2.
4. Referências e Leituras Essenciais
- *Llama.cpp Internals:* ggml architecture and memory
- *Rust FFI (Unsafe):* The Rustonomicon - FFI
- *Understanding LLM Logits & Sampling:* Hugging Face: How to generate text
- *Byte Pair Encoding (BPE):* Training a Tokenizer