3 puntos por GN⁺ 3 시간 전 | 1 comentarios | Compartir por WhatsApp
  • GGUF es el formato de archivo de modelos de lenguaje que usa llama.cpp, y reúne en un solo archivo los metadatos necesarios para la ejecución, lo que simplifica la distribución y carga del modelo
  • Las plantillas de chat son scripts Jinja2 que manejan el formato de conversación, las llamadas a herramientas y la codificación de mensajes multimedia, pero su comportamiento varía entre implementaciones
  • GGUF puede incluir tokens especiales como el token de fin, además de configuraciones recomendadas del sampler, y recientemente también puede indicar el orden de la cadena de sampling
  • Aún no existe un formato de llamadas a herramientas unificado, por lo que cada modelo sigue requiriendo lógica hardcodeada en los motores de inferencia, y la generación de parsers basados en gramáticas sigue siendo una posible mejora del estándar
  • Siguen faltando think_token, el empaquetado de modelos de proyección y banderas de capacidades, por lo que todavía es difícil separar bloques de razonamiento, configurar componentes multimodales y detectar funciones compatibles

Lo que incluye GGUF

  • GGUF es el formato de archivo que llama.cpp usa para modelos de lenguaje
  • La ventaja central de GGUF es que reúne en un solo archivo varios componentes necesarios para ejecutar el modelo
  • GGUF mete esta información adicional en un solo archivo, lo que hace que el modelo sea más fácil de manejar

Plantillas de chat

  • Los modelos de lenguaje conversacionales se entrenan con secuencias de tokens en un formato específico, que suele parecer una estructura de diálogo
  • Un ejemplo del formato de Gemma4 es el siguiente
<|turn>user
Hi there!<turn|>
<|turn>model
Hi there, how can I help you today?<turn|>
  • Un ejemplo de plantilla del formato LFM2 es el siguiente
<s>
<|im_start|>user Hi there!<|im_end|>
<|im_start|>assistant Hi there, how can I help you today?<|im_end|>
  • En la práctica, las plantillas se vuelven mucho más complejas, ya que incluyen bloques de razonamiento, descripciones de herramientas, llamadas y respuestas de herramientas, e incluso codificación de mensajes multimedia como imágenes, audio y video
  • De todo esto se encargan las plantillas de chat, que son scripts escritos con el lenguaje de plantillas Jinja2
    • Como ejemplo, está la chat template incluida en Gemma 4
    • En los metadatos de GGUF, la plantilla de chat predeterminada se guarda bajo la clave tokenizer.chat_template
  • Un modelo puede tener varias plantillas de chat
    • Puede haber una plantilla que soporte llamadas a herramientas y otra que no
    • La mayoría de los modelos ofrece una sola plantilla de chat gigante, que solo activa el manejo de herramientas cuando se especifican herramientas
    • En algunos modelos hay que buscar por separado una plantilla de chat dedicada a herramientas
  • Jinja2 se parece más a un lenguaje de programación con bucles, condicionales, asignaciones, listas y diccionarios
    • Las aplicaciones conversacionales con LLM tienen que incluir un intérprete que ejecute, cada vez que se agrega un nuevo mensaje, programas como el script Jinja de unas 250 líneas que trae Gemma
  • La forma de procesar Jinja también cambia según la implementación
    • Hugging Face transformers usa la biblioteca jinja2 existente de Python
    • llama-server y llama-cli de llama.cpp usan una implementación propia de Jinja
    • llama_chat_apply_template, expuesto en la API de libllama, es un enfoque antiguo que hardcodea directamente en C++ unos pocos formatos de chat
    • NobodyWho usa minijinja, una reimplementación en Rust hecha por el autor original de Jinja
    • Esto es distinto de minja, la biblioteca mínima de Jinja que llama.cpp usó en algún momento
  • Existen diferencias de rendimiento considerables entre implementaciones de Jinja
    • Como el procesamiento de plantillas de chat no suele ser el cuello de botella en aplicaciones locales con LLM, no es un tema especialmente polémico

Tokens especiales

  • Como los modelos de lenguaje pueden seguir emitiendo el siguiente token para una secuencia de entrada, hace falta una forma de detener la generación
  • La solución habitual es usar un token de fin: cuando el modelo lo emite, el motor de inferencia deja de generar
  • El token de fin es un ejemplo de token especial
    • Los tokens especiales suelen tener un significado que va más allá de los caracteres tokenizados
    • Por lo general no deberían mostrarse al usuario, aunque muchas veces tienen una representación textual visible
  • Algunos ejemplos de tokens especiales de Gemma4 son los siguientes
    • 1 / <eos>: fin de secuencia; el modelo lo emite para detener la generación
    • 2 / <bos>: inicio de secuencia; se antepone a la entrada
    • 46 / <|tool_call>: marca el inicio de una llamada a herramienta
    • 47 / <tool_call|>: marca el final de una llamada a herramienta
    • 105 / <|turn>: marca el inicio de un turno de conversación
    • 106 / <turn|>: marca el final de un turno de conversación

Configuración y orden del sampler

  • Un modelo de lenguaje emite una distribución de probabilidades sobre el siguiente token, y al proceso de elegir un token de esa distribución se le llama sampling
  • La forma más simple es elegir aleatoriamente a partir de la distribución ponderada
  • En la práctica, se obtienen mejores resultados si se aplican transformaciones a la distribución de probabilidades antes de elegir un token concreto
  • Cuando un laboratorio publica un modelo nuevo, muchas veces también comparte una configuración recomendada del sampler
  • También es común que los usuarios copien y peguen valores desde archivos Markdown u otras fuentes para conseguir mejores respuestas
  • Para reducir esa copia manual, NobodyWho publicó modelos seleccionados en su página de Hugging Face y empaquetó configuraciones recomendadas del sampler en un formato propio
    • Funcionaba, pero para que el modelo fuera realmente útil hacía falta una conversión adicional hecha por NobodyWho
  • Una función añadida recientemente al formato GGUF permite especificar directamente dentro del archivo del modelo la cadena de samplers
    • Gracias a eso, el formato propio de NobodyWho dejó de ser necesario, que era justamente el objetivo
  • En la webapp llm-sampling se puede ver rápidamente el papel de las distintas etapas del sampler
  • Si arrastras y sueltas pasos individuales, se nota que el orden de las etapas de sampling puede cambiar mucho la distribución final
  • Muchos formatos de configuración de sampler, como los archivos JSON de imágenes de Ollama o generation_config.json de Hugging Face, no tienen forma de indicar el orden de las etapas de sampling
  • El estándar GGUF puede especificar el orden de sampling con el campo general.sampling.sequence
  • Aun así, muchos modelos GGUF siguen omitiendo este campo y dependen de un orden implícito que corresponde al comportamiento por defecto de llama.cpp

Lo que todavía falta

  • Un buen motor de inferencia busca ofrecer una interfaz unificada para distintos modelos de lenguaje
  • Si se analiza y aprovecha la información adicional de los metadatos de GGUF, se pueden reducir mucho los caminos de código específicos por modelo
  • Formato de llamadas a herramientas

    • Casi todos los motores de inferencia tienen rutas hardcodeadas para parsear distintos formatos de llamadas a herramientas
    • Un ejemplo del formato de llamadas a herramientas de Qwen3 es el siguiente
<tool_call>{"name": "get_weather", "arguments": {"location": "Copenhagen"}}</tool_call>
  • Un ejemplo del formato de llamadas a herramientas de Qwen3.5 es el siguiente
<tool_call>
<function=get_weather>
<parameter=city>
Copenhagen
</parameter>
</function>
</tool_call>
  • Un ejemplo del formato de llamadas a herramientas de Gemma4 es el siguiente
<|tool_call>call:get_weather{city:<|"|>Copenhagen<|"|>}<tool_call|>
  • Cuando sale un modelo nuevo, varios motores de inferencia tienen que implementar su propio parser por separado
  • Si el archivo del modelo incluyera una gramática y a partir de ella se pudiera derivar un parser, sería una excelente adición al estándar GGUF
  • NobodyWho añade además una etapa que genera una gramática restringida adaptada a la herramienta concreta que se pasa
    • Esto permite garantizar seguridad de tipos en las llamadas a herramientas
    • Es especialmente útil porque los modelos pequeños, sobre todo de 1B o menos, pueden equivocarse y pasar un float donde se necesita un entero
  • Incluso si existiera una gramática capaz de crear un parser general de llamadas a herramientas, NobodyWho seguiría necesitando implementar una función que genere la gramática según las herramientas concretas recibidas
  • Un formato de metagramática capaz de producir una gramática específica para una herramienta concreta y derivar de ahí un parser sigue siendo un problema interesante
  • Token think

    • Es probablemente lo más fácil de añadir entre las piezas faltantes
    • El repositorio upstream de Hugging Face ya empezó a incluir el campo think_token
    • think_token es muy útil para separar los bloques de razonamiento de la salida generada
      • Normalmente esos bloques deben eliminarse o renderizarse de forma distinta al contenido principal
    • Las conversiones GGUF downstream normalmente no incluyen este campo
    • Como resultado, los motores de inferencia basados en GGUF no pueden separar el stream de razonamiento de la salida principal sin escribir código específico para ciertas familias de modelos
    • Agregar think_token al pipeline estándar de conversión a GGUF resolvería este problema
  • Modelos de proyección

    • Las interacciones con LLM multimodales, donde el modelo puede ver de forma nativa imágenes y audio en lugar de tratarlos como texto, requieren un modelo adicional que procese entradas no textuales
    • A ese modelo adicional se le llama modelo de proyección
    • La convención actual es pasar dos archivos GGUF
      • Uno para el modelo principal de lenguaje
      • Otro para un modelo más pequeño que procesa imágenes y audio
    • Esto rompe la comodidad de archivo único que ofrece GGUF
    • Sería una gran mejora que un solo archivo GGUF pudiera empaquetar dentro del archivo principal los pesos y la configuración del modelo de proyección
    • Los modelos de proyección suelen pesar alrededor de 1 GB
      • Son lo bastante grandes como para querer evitar esa sobrecarga cuando no se usan
    • Una opción razonable sería ofrecer dos variantes de GGUF: una con pesos de proyección incluidos y otra sin ellos
    • Así se volvería a un escenario con una sola URL para descargar y un solo archivo para gestionar en caché en disco
  • Lista de capacidades compatibles

    • Cada modelo soporta capacidades distintas, y no es fácil detectar qué funciones soporta realmente mirando solo el archivo GGUF
    • Algunos modelos aceptan entrada de imágenes y otros no
      • Hoy, la mejor aproximación es asumir que hay soporte de imágenes si se entrega un modelo de proyección
    • Algunos modelos soportan llamadas a herramientas nativas y otros no
      • Hoy, la mejor aproximación es buscar por coincidencia parcial de cadenas en la plantilla de chat si hay una parte que intente renderizar una lista de esquemas JSON de herramientas
      • Eso es claramente un parche temporal
    • Algunos modelos emiten bloques de razonamiento y otros no
      • Como las etiquetas de razonamiento normalmente no están en los metadatos de GGUF, no queda claro cuál es una buena manera de confirmar si se puede esperar ese tipo de bloques del modelo
    • Si la comunidad de GGUF añadiera banderas de capacidades al archivo del modelo, las bibliotecas de inferencia independientes del modelo podrían ofrecer mensajes de error y advertencias más consistentes
      • Por ejemplo, se podría dar una guía más adecuada al intentar usar llamadas a herramientas con un modelo que no las soporta de forma nativa

Conclusión

  • GGUF reúne en un solo archivo la información adicional necesaria para ejecutar correctamente un modelo, lo que evita tener que añadir demasiados caminos de código específicos por modelo
  • GGUF es un formato abierto y extensible, con una comunidad fuerte
  • Si el estándar sigue fortaleciéndose en conjunto, será posible mantener una buena experiencia para desarrolladores y a la vez facilitar el reemplazo de modelos dentro de las aplicaciones
  • Los metadatos de GGUF ya son útiles en muchos aspectos, pero todavía hay margen de mejora en áreas como las gramáticas para llamadas a herramientas, think_token, el empaquetado de modelos de proyección y las banderas de capacidades

1 comentarios

 
GN⁺ 3 시간 전
Comentarios de Hacker News
  • Es una pena que el modelo de proyección haya quedado separado en un archivo aparte; yo también habría preferido que estuviera dentro de un archivo único
    No sé exactamente por qué pasó eso, pero se desvía bastante de la filosofía de archivo único que se tenía en mente al diseñar GGUF
    Ojalá alguien impulse la integración de ambos; esta vez siento que yo ya estoy demasiado fuera del flujo :-)

    • Viendo que el soporte para MTP está en desarrollo, parece que durante esa discusión surgió la idea de separar los modelos MTP del GGUF principal, como Mmproj, pero fue rechazada
      Me gusta esa decisión. Entonces no me parece descabellado pensar que también podría haber apertura para incluir el archivo Mmproj dentro de GGUF
      El único problema que se me ocurre es qué formato meter ahí. Hay opciones como BF16, F16, etc.
  • GGML y GGUF han sido muy importantes para el ecosistema open source de machine learning/AI
    Proyectos como llama.cpp, whisper.cpp y stable-diffusion.cpp suelen funcionar bien casi de inmediato en distintas plataformas y backends de hardware

    • llama.cpp viene del lado de Meta, y realmente detesto a Meta, pero admito que es el más fácil en comparación con otros
      Lo compilas, le pones el modelo y lo ejecutas. Y con eso ya tienes web UI y API
  • > <|turn>user Hi there!<|turn>model Hi there, how can I help you today
    Dios mío, lograron crear un formato todavía menos legible que XML

    • No es un formato hecho para que lo lean humanos. De hecho, casi nunca hace falta mirarlo directamente
      Está diseñado para que no se confunda con el contenido real, y ese contenido puede ser cualquier texto sacado de internet
      Para eso, necesitas usar un formato que no se use en ninguna otra parte
    • Correcto. En términos de eficiencia de uso de memoria, no parece ser un formato óptimo
  • Creo que lo que más falta hoy es una forma de definir la arquitectura del modelo sin hardcodearla en el build actual
    No hace falta que tenga equivalencia de rendimiento 1:1 con los modelos totalmente soportados
    Tener soporte correcto y validado por el proveedor desde el día uno es lo que determina si un modelo se siente excelente o terrible. Los lanzamientos recientes de Gemma y Qwen son ejemplos de eso
    No sé cuál sea la solución, pero podría existir una forma de escribir un DSL que describa el grafo del modelo y meterlo en GGUF
    Otra alternativa sería leer el módulo PyTorch del lanzamiento oficial del modelo y convertirlo de alguna manera a operaciones de GGML

    • La especificación de GGUF dejó a propósito espacio para incluir un grafo computacional, con la esperanza de que alguien siguiera por ahí
      Quería incluirlo en la primera versión, pero en ese momento la prioridad era sacar una especificación mínima funcional y lograr que se implementara
      Todavía me gustaría verlo, pero hace falta alguien que impulse eso y que conozca muy bien el estado actual del IR de GGML
    • Parece que podría embederse un grafo computacional dentro del archivo de pesos, al estilo de ONNX
      Luego se podría exponer una interfaz común que reciba parámetros comunes, y dejar los parámetros personalizados adicionales como extensiones, al estilo de Wayland
      Así se podrían soportar no solo transformadores como LLaMa, sino también redes neuronales recurrentes como RWKV, modelos multimodales, etc.
      No sé bien cómo sería la implementación real, pero suena como una gran idea. Eso sí, me preocupa que si el grafo computacional queda incrustado en el archivo del modelo, entonces mejoras de arquitectura u optimizaciones que no requieran cambiar los pesos no podrían aplicarse a archivos existentes sin convertirlos
  • > Lo realmente elegante de GGUF es que es un solo archivo. Comparado con un repositorio típico de safetensors en Hugging Face, tienes archivos JSON necesarios esparcidos por todas partes [...]
    Curiosamente, para mí los modelos de AI “siempre” fueron un solo archivo, porque en la generación local de imágenes eso era lo estándar
    Los archivos safetensors también pueden contener todo tipo de cosas internamente, así que para eso no necesariamente hace falta GGUF
    Pero los codificadores de texto de los modelos modernos son en sí mismos modelos de lenguaje de varios gigabytes, así que nadie mete una copia duplicada en cada checkpoint

    • La distribución en archivo único fue un objetivo de diseño que fijé de manera deliberada
      La mayoría de los modelos de imagen eran o siguen siendo de un solo archivo, pero los safetensors de LLM al menos en ese momento no lo eran, y quería imponer eso a nivel estructural
      Además, no quería que un runner, por ejemplo llama.cpp, tuviera que depender de un lector de JSON, y con el enfoque de ST eso habría sido necesario
      El problema más grande, si mal no recuerdo, era que en ese momento ST no podía soportar los nuevos formatos de cuantización de GGML, y tener un formato de archivo propio permitía una flexibilidad difícil de conseguir con ST
    • Decir que “en la generación local de imágenes los modelos de AI siempre fueron de un solo archivo” tampoco tiene sentido en ese ámbito
      Para ejecutar realmente una arquitectura con sus pesos no usas solo un archivo de pesos único, sino también varios encoders y decoders, entre otras cosas
      La herramienta que uses puede ocultarlo, pero debajo de la superficie todo eso sigue existiendo
  • Sobre llama_chat_apply_template, esa función algo rara expuesta en la API de libllama que hardcodea directamente en C++ algunos formatos de chat, como alguien que está jugueteando con una app de inferencia de escritorio basada en FLTK[0], me gustaría que usara el parser real de plantillas Jinja2 que usa llama.cpp
    O al menos que hubiera otra función en C que hiciera eso. Para parsear correctamente, parece que tendrías que poder pasar varios datos, por ejemplo para que la plantilla sepa si hay tool calling o no
    Por ahora uso esta función improvisada, pero al final probablemente termine usando directamente un intérprete de Jinja2 o copiando y pegando el código desde llama.cpp
    Aun así, el enfoque todo-en-uno de GGUF es muy conveniente. Coincido en que se siente extraño que el modelo de proyección venga en un archivo separado
    La primera vez que descargué un modelo con soporte de visión, bajé el GGUF que parecía adecuado, pero llama.cpp no pudo procesar el modelo, y mucho después me di cuenta de que hacía falta un archivo adicional
    En ese momento pensé literalmente: “¿GGUF no era un formato que lo incluía todo?” :-P
    [0] https://i.imgur.com/GiTBE1j.png

  • Yo siempre he usado un formato tipo safetensors + archivos de metadatos parecido al de los repositorios de Hugging Face
    No es una gran incomodidad para nada, pero está bien que GGUF tenga un formato más compacto y buen soporte

  • Al ver las cosas que todavía no tiene GGUF, en realidad terminé aprendiendo más sobre GGUF
    El formato de tool calling se siente muy natural, y parece que podría ser un hito en el paso de los LLM hacia agentes

  • Hace poco descargué el 7B Mistral de TheBloke para probarlo, y tengo una 4070

    • Me gusta Mistral, pero ese modelo no es lo mejor
      Te convendría probar Gemma 4 e4b. Tiene un tamaño parecido al de Mistral 7B y debería correr bien en una 4070
      El nombre “E4B” puede prestarse un poco a confusión
    • Mistral 7B ya tiene bastante tiempo
      En una 4070 de 12GB puedes correr Qwen 3.5 9B q4km o Qwen 3.6 35B. El segundo es mucho más inteligente, pero bastante más lento por el offloading de memoria
      Si pruebas ambos en LM Studio, de verdad sorprende muchísimo lo capaces que son
    • Confirmo que incluso en una 2070 funciona muy bien y bastante rápido
      Me gusta TheBloke, así que ojalá siguiera armando modelos