3 pontos por GN⁺ 3 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • GGUF é o formato de arquivo de modelos de linguagem usado pelo llama.cpp, que reúne em um único arquivo os metadados necessários para a execução e simplifica a distribuição e o carregamento de modelos
  • Os templates de chat são scripts Jinja2 que lidam com formato de conversa, chamadas de ferramentas e codificação de mensagens multimídia, mas seu comportamento varia entre implementações
  • O GGUF pode incluir tokens especiais, como tokens de término, e configurações recomendadas de sampler, e recentemente passou a permitir também especificar a ordem da cadeia de samplers
  • O formato de chamada de ferramentas ainda varia entre modelos, exigindo código hardcoded em cada engine de inferência, enquanto a geração de parsers baseados em gramática segue como candidata a melhorar o padrão
  • Ainda faltam think_token, empacotamento de modelos de projeção e flags de recursos, o que continua dificultando separar trechos de raciocínio, configurar multimodalidade e detectar recursos suportados

O que o GGUF inclui

  • GGUF é o formato de arquivo usado pelo llama.cpp para modelos de linguagem
  • A principal vantagem do GGUF é reunir em um único arquivo vários componentes necessários para executar o modelo
  • O GGUF coloca essas informações adicionais em um só arquivo, facilitando o uso do modelo

Templates de chat

  • Modelos de linguagem conversacionais são treinados com sequências de tokens em um formato específico, que parece uma estrutura de diálogo
  • Um exemplo do formato Gemma4 é o seguinte
<|turn>user
Hi there!<turn|>
<|turn>model
Hi there, how can I help you today?<turn|>
  • Um exemplo de template no formato LFM2 é o seguinte
<s>
<|im_start|>user Hi there!<|im_end|>
<|im_start|>assistant Hi there, how can I help you today?<|im_end|>
  • Na prática, os templates ficam muito mais complexos, incluindo blocos de raciocínio, descrições de ferramentas, chamadas e respostas de ferramentas, e até codificação de mensagens multimídia como imagens, áudio e vídeo
  • Esse trabalho fica a cargo dos templates de chat, que são scripts escritos na linguagem de template Jinja2
  • Um modelo pode ter vários templates de chat
    • Pode haver templates separados para suporte a chamadas de ferramentas e para uso sem ferramentas
    • A maioria dos modelos oferece um único template de chat gigante, que só ativa o processamento relacionado a ferramentas quando alguma ferramenta é especificada
    • Em alguns modelos, é preciso procurar separadamente um template de chat dedicado a ferramentas
  • Jinja2 é quase uma linguagem de programação, com loops, condicionais, atribuições, listas e dicionários
    • Aplicações de LLM conversacionais precisam incluir um interpretador que execute, a cada nova mensagem, programas como o script Jinja de cerca de 250 linhas fornecido pelo Gemma
  • A forma de processar Jinja também varia entre implementações
    • O Hugging Face transformers usa a biblioteca jinja2 existente em Python
    • O llama-server e o llama-cli do llama.cpp usam uma implementação própria de Jinja
    • A llama_chat_apply_template exposta na API libllama segue a abordagem antiga, com alguns formatos de chat diretamente hardcoded em C++
    • O NobodyWho usa o minijinja, uma reimplementação em Rust feita pelo autor original do Jinja
    • Isso é diferente do minja, a biblioteca minimalista de Jinja que o llama.cpp já usou no passado
  • Existem diferenças significativas de desempenho entre implementações de Jinja
    • Em aplicações locais com LLM, o processamento de template de chat não costuma ser gargalo, então isso não vira uma grande controvérsia

Tokens especiais

  • Um modelo de linguagem pode continuar emitindo o próximo token indefinidamente para uma sequência de entrada, então é preciso uma forma de interromper a geração
  • A solução mais comum é usar um token de término: quando o modelo o emite, a engine de inferência para a geração
  • Tokens de término são um exemplo de tokens especiais
    • Tokens especiais normalmente têm significado além dos caracteres tokenizados comuns
    • Em geral não deveriam ser mostrados ao usuário, embora muitas vezes tenham uma representação textual e possam ser exibidos
  • Alguns exemplos de tokens especiais do Gemma4 são os seguintes
    • 1 / <eos>: fim da sequência; o modelo o emite para parar a geração
    • 2 / <bos>: início da sequência; é adicionado antes da entrada
    • 46 / <|tool_call>: marca o início de uma chamada de ferramenta
    • 47 / <tool_call|>: marca o fim de uma chamada de ferramenta
    • 105 / <|turn>: marca o início de um turno de conversa
    • 106 / <turn|>: marca o fim de um turno de conversa

Configurações e ordem dos samplers

  • Um modelo de linguagem produz uma distribuição de probabilidades para o próximo token, e o processo de escolher um token dessa distribuição é chamado de sampling
  • A forma mais simples é escolher aleatoriamente a partir da distribuição ponderada
  • Na prática, costuma-se obter resultados melhores aplicando transformações à distribuição antes de selecionar o token específico
  • Quando um laboratório lança um novo modelo, muitas vezes fornece junto configurações recomendadas de sampler
  • Também é comum que usuários copiem e colem valores de arquivos Markdown e semelhantes para obter respostas melhores
  • Para reduzir esse trabalho manual, o NobodyWho publicou modelos selecionados em uma página no Hugging Face e empacotou configurações recomendadas de sampler em um formato próprio
    • Funcionava, mas exigia conversão do lado do NobodyWho para que o modelo fosse realmente útil
  • Um recurso adicionado recentemente ao formato GGUF agora permite especificar diretamente a cadeia de samplers dentro do arquivo do modelo
    • Com isso, o formato próprio do NobodyWho se tornou desnecessário, que era exatamente o objetivo
  • O webapp llm-sampling permite verificar rapidamente o papel de diferentes etapas de sampler
  • Ao arrastar e soltar etapas individuais, dá para ver que a ordem das etapas de sampling pode causar grande diferença na distribuição final
  • Muitos formatos de configuração de sampler, incluindo arquivos JSON de imagens do Ollama e o generation_config.json do Hugging Face, não têm como especificar a ordem das etapas de sampling
  • O padrão GGUF pode definir a ordem de sampling com o campo general.sampling.sequence
  • Ainda assim, muitos modelos GGUF omitem esse campo e dependem da ordem implícita do comportamento padrão do llama.cpp

O que ainda falta

  • Uma boa engine de inferência tenta oferecer uma interface unificada para vários modelos de linguagem
  • Se ela conseguir fazer parse e usar as informações extras dos metadados GGUF, dá para reduzir bastante os caminhos de código específicos por modelo
  • Formato de chamada de ferramentas

    • Quase toda engine de inferência mantém caminhos hardcoded para fazer parse de diferentes formatos de chamada de ferramentas
    • Um exemplo do formato de chamada de ferramenta do Qwen3 é o seguinte
<tool_call>{"name": "get_weather", "arguments": {"location": "Copenhagen"}}</tool_call>
  • Um exemplo do formato de chamada de ferramenta do Qwen3.5 é o seguinte
<tool_call>
<function=get_weather>
<parameter=city>
Copenhagen
</parameter>
</function>
</tool_call>
  • Um exemplo do formato de chamada de ferramenta do Gemma4 é o seguinte
<|tool_call>call:get_weather{city:<|"|>Copenhagen<|"|>}<tool_call|>
  • Quando surge um modelo novo, várias engines de inferência precisam implementar seu próprio parser
  • Se o arquivo do modelo incluísse uma gramática, e fosse possível derivar um parser a partir dela, isso seria um excelente acréscimo ao padrão GGUF
  • O NobodyWho adiciona ainda uma etapa extra que gera uma gramática restritiva adaptada às ferramentas específicas fornecidas
    • Isso permite garantir segurança de tipos nas chamadas de ferramentas
    • É especialmente útil porque modelos pequenos, principalmente abaixo de 1B, podem errar e passar um float onde deveria haver um inteiro
  • Mesmo que exista uma gramática capaz de gerar um parser geral de chamadas de ferramentas, o NobodyWho ainda precisaria manter a função que gera gramáticas específicas para cada conjunto de ferramentas passado
  • Um formato de metagramática capaz de gerar gramáticas concretas para ferramentas específicas, e então derivar parsers a partir delas, continua sendo uma questão interessante
  • Token Think

    • É provavelmente o item faltante mais fácil de adicionar
    • O repositório upstream no Hugging Face começou a incluir o campo think_token
    • O think_token é muito útil para separar a seção de raciocínio da saída gerada
      • Em geral, essa seção de raciocínio deve ser removida ou renderizada de forma diferente da saída principal
    • Os GGUFs convertidos downstream normalmente não incluem esse campo
    • Como resultado, engines de inferência baseadas em GGUF não conseguem separar o stream de raciocínio da saída principal sem escrever código específico para certas famílias de modelos
    • Adicionar think_token ao pipeline padrão de conversão para GGUF resolveria esse problema
  • Modelos de projeção

    • Interações com LLMs multimodais que conseguem ver imagens e ouvir áudio nativamente, em vez de tratar tudo como texto, exigem um modelo adicional para processar entradas não textuais
    • Esse modelo adicional é chamado de modelo de projeção
    • A prática atual é fornecer dois arquivos GGUF
      • um GGUF para o modelo principal de linguagem
      • outro para um modelo menor, responsável por processar imagem e áudio
    • Isso quebra a conveniência de arquivo único do GGUF
    • Seria uma grande melhoria se um único arquivo GGUF pudesse empacotar também os pesos e as configurações do modelo de projeção dentro do arquivo principal
    • Modelos de projeção costumam ter cerca de 1 GB
      • é grande o bastante para querer evitar esse overhead quando ele não for usado
    • Oferecer duas variantes, uma com pesos de projeção incluídos e outra sem eles, parece razoável
    • Assim, seria possível voltar a ter apenas uma URL para download e um arquivo para manter em cache no disco
  • Lista de recursos suportados

    • Cada modelo suporta recursos diferentes, e não é fácil detectar só pelo arquivo GGUF quais recursos ele realmente oferece
    • Alguns modelos suportam entrada de imagem e outros não
      • Hoje, o melhor que se faz é assumir suporte a imagem quando um modelo de projeção é fornecido
    • Alguns modelos suportam chamada de ferramentas nativa e outros não
      • Hoje, a melhor abordagem é verificar por correspondência parcial de string se o template de chat tenta renderizar uma lista de esquemas JSON de ferramentas
      • Isso é claramente um paliativo
    • Alguns modelos produzem blocos de raciocínio e outros não
      • Como as tags de raciocínio normalmente não estão nos metadados GGUF, não há uma boa forma de saber se faz sentido esperar blocos de raciocínio do modelo
    • Se a comunidade GGUF adicionar flags de recursos ao arquivo do modelo, bibliotecas de inferência independentes do modelo poderão fornecer mensagens de erro e avisos mais consistentes
      • Por exemplo, dar orientações mais apropriadas ao tentar usar chamada de ferramentas com um modelo que não oferece suporte nativo a isso

Conclusão

  • O GGUF reúne em um único arquivo as informações adicionais necessárias para executar corretamente o modelo, sem exigir a criação de muitos caminhos de código específicos por modelo
  • O GGUF é um formato aberto e extensível, com uma comunidade forte
  • Se o padrão continuar sendo fortalecido em conjunto, será possível manter uma boa experiência de desenvolvedor e, ao mesmo tempo, facilitar a troca de modelos dentro das aplicações
  • Os metadados do GGUF já são úteis em muitos pontos, mas ainda há espaço para melhorias como gramáticas de chamada de ferramentas, think_token, empacotamento de modelos de projeção e flags de recursos

1 comentários

 
GN⁺ 3 시간 전
Comentários do Hacker News
  • É uma pena que o modelo de projeção tenha acabado separado em um arquivo à parte, e eu também preferia que ficasse em um único arquivo
    Não sei exatamente por que isso aconteceu, mas foge bastante da filosofia de arquivo único que se tinha em mente ao projetar o GGUF
    Espero que alguém lidere o esforço para unir os dois, porque desta vez sinto que estou um pouco fora do fluxo :-)

    • Vendo que o suporte a MTP está em desenvolvimento agora, parece que durante essa discussão surgiu a ideia de separar modelos MTP do GGUF principal, como no Mmproj, mas ela foi rejeitada
      Gostei dessa decisão. Então não me parece forçado supor que também haja abertura para incluir o arquivo Mmproj dentro do GGUF
      O único problema que me vem à cabeça é qual formato colocar lá dentro. Há opções como BF16, F16 etc.
  • GGML e GGUF foram muito importantes para o ecossistema open source de machine learning/IA
    Projetos como llama.cpp, whisper.cpp e stable-diffusion.cpp em geral funcionam muito bem de cara em várias plataformas e backends de hardware

    • O llama.cpp é meio que um produto do pessoal da Meta, e eu realmente detesto a Meta, mas reconheço que ele é o mais fácil comparado aos outros
      É só compilar, colocar o modelo e executar. Aí você já ganha também uma UI web e uma API
  • > <|turn>user Hi there!<|turn>model Hi there, how can I help you today
    Meu Deus, conseguiram criar um formato ainda menos legível do que XML

    • Não é um formato feito para humanos lerem. Na prática, você quase nunca vai precisar olhar para isso
      Esse formato foi projetado para não se confundir com o conteúdo real, e esse conteúdo pode ser qualquer texto vindo da internet
      Para isso, é preciso usar um formato que não apareça em nenhum outro lugar
    • Sim. Em termos de eficiência de uso de memória, realmente não parece um formato ideal
  • Acho que o que mais faz falta hoje é uma forma de definir a arquitetura do modelo sem hardcode na build atual
    Não precisa ter equivalência total de desempenho 1:1 com os modelos totalmente suportados
    Ter suporte adequado e validado pelo fornecedor já no dia do lançamento é o que faz um modelo parecer excelente ou parecer horrível. Os lançamentos recentes de Gemma e Qwen mostram isso
    Não sei qual seria a solução, mas uma possibilidade seria escrever uma DSL que descreva o grafo do modelo e colocá-la no GGUF
    Outra alternativa seria ler o módulo PyTorch do release oficial do modelo e de algum jeito convertê-lo para operações do GGML

    • A especificação do GGUF deixou de propósito algum espaço para incluir o grafo computacional, e eu esperava que alguém desse continuidade a isso
      Eu queria colocar isso na primeira versão, mas na época a prioridade foi lançar uma especificação mínima funcional e fazer com que fosse implementada
      Eu ainda gostaria de ver isso, mas seria preciso alguém com muita familiaridade com o estado atual do IR do GGML para tocar a iniciativa
    • Parece que daria para embutir o grafo computacional no arquivo de pesos, como no ONNX
      Depois disso, seria possível expor uma interface comum que recebe parâmetros comuns, deixando parâmetros extras e customizados como extensões, à maneira do Wayland
      Assim, seria possível suportar não só modelos da família transformer como o LLaMa, mas também redes neurais recorrentes como RWKV, modelos multimodais etc.
      Não sei bem como ficaria a implementação na prática, mas a ideia parece ótima. Só me preocupa que, se o grafo computacional ficar gravado no arquivo do modelo, melhorias de arquitetura ou otimizações que não exigem troca de pesos talvez não possam ser aplicadas aos arquivos existentes sem conversão
  • > O aspecto realmente elegante do GGUF é que ele é um único arquivo. Em comparação com um repositório safetensors típico do Hugging Face, os arquivos JSON necessários ficam espalhados por todo lado [...]
    Curiosamente, para mim os modelos de IA “sempre” foram um único arquivo. No lado da geração local de imagens, esse era o padrão
    Arquivos safetensors também conseguem guardar todo tipo de coisa internamente, então não é como se o GGUF fosse estritamente necessário para isso
    Só que os codificadores de texto dos modelos modernos são, por si só, modelos de linguagem de vários gigabytes, então ninguém coloca cópias duplicadas em todos os checkpoints

    • Distribuição em arquivo único era um objetivo de projeto que defini de propósito
      A maioria dos modelos de imagem era ou ainda é de arquivo único, mas os safetensors de LLM, pelo menos na época, não eram assim, e eu queria impor isso no nível estrutural
      Eu também não queria exigir que runners, como o llama.cpp, tivessem leitor de JSON, e o esquema do ST teria exigido isso
      Um problema ainda maior, se bem me lembro, era que na época o ST não conseguia suportar os novos formatos de quantização do GGML, e ter um formato de arquivo próprio permitia uma flexibilidade que seria difícil obter com ST
    • Dizer que “na geração local de imagens os modelos de IA sempre foram um único arquivo” também não faz sentido nessa área
      Para realmente executar a arquitetura com os pesos, você não usa só um arquivo de pesos, mas também vários encoders e decoders, entre outros componentes
      A ferramenta que você usa pode esconder isso, mas por baixo da superfície essas peças continuam existindo
  • Sobre o llama_chat_apply_template, aquela função meio estranha exposta na API do libllama que faz hardcode em C++ de alguns formatos de chat, do ponto de vista de alguém mexendo num app desktop de inferência com FLTK[0], eu gostaria que isso usasse o parser real de templates Jinja2 que o llama.cpp usa
    Ou então que houvesse outra função em C que fizesse esse trabalho. Para fazer o parsing direito, parece necessário conseguir passar vários dados para que o template saiba, por exemplo, se há chamada de ferramentas
    Por enquanto estou usando essa função improvisada, mas acho que no fim vou acabar usando um interpretador Jinja2 diretamente ou copiando e colando o código do llama.cpp
    Ainda assim, a abordagem all-in-one do GGUF é muito conveniente. Concordo que é estranho o modelo de projeção ficar em arquivo separado
    Quando baixei pela primeira vez um modelo com suporte a visão, peguei só o GGUF que parecia adequado, e o llama.cpp não conseguiu processar o modelo; só bem depois percebi que precisava de um arquivo adicional
    Na hora pensei literalmente: “GGUF não era um formato para colocar tudo?” :-P
    [0] https://i.imgur.com/GiTBE1j.png

  • Eu sempre usei um formato de safetensors + arquivos de metadados parecido com o de repositórios do Hugging Face
    Não é um grande incômodo, mas parece bacana que o GGUF tenha um formato mais compacto e bom suporte

  • Ao ver o que ainda não existe no GGUF, acabei aprendendo mais sobre o próprio GGUF
    O formato de chamada de ferramentas parece muito natural e dá a sensação de ser um marco na transição de LLM para agentes

  • Recentemente tentei baixar o Mistral 7B do TheBloke para testar, e tenho uma 4070

    • Eu gosto do Mistral, mas esse modelo não é dos melhores
      Vale a pena testar o Gemma 4 e4b. Ele tem tamanho parecido com o Mistral 7B e deve rodar bem numa 4070
      O nome “E4B” pode ser um pouco enganoso
    • O Mistral 7B já é bem antigo
      Numa 4070 de 12 GB, você consegue rodar Qwen 3.5 9B q4km ou Qwen 3.6 35B. O segundo é muito mais inteligente, mas também bem mais lento por causa do offloading de memória
      Testando os dois no LM Studio, a capacidade deles é realmente impressionante
    • Também confirmei que roda muito bem e bem rápido até numa 2070
      Gosto do TheBloke e ainda queria que ele continuasse empacotando modelos