- colibrì es un motor en C puro creado para ejecutar GLM-5.2 744B MoE en unos 25 GB de RAM en una máquina de consumo; funciona sin GPU, haciendo streaming desde disco de los expertos ruteados
- La arquitectura clave mantiene residente en RAM la parte dense, de unos 17B parámetros, en int4 usando 9.9 GB, mientras que los 21,504 expertos ruteados quedan en unos 370 GB de disco y se leen cuando hacen falta
- La implementación incluye el forward de GLM-5.2, atención MLA y KV-cache comprimida, router estilo DeepSeek-V3, decodificación especulativa MTP, kernels de cuantización int8/int4/int2 y un tokenizer BPE a nivel de bytes
- En un entorno WSL2 con 12 núcleos, 25 GB de RAM y NVMe VHDX, el tiempo de carga es de unos 30 segundos, el RSS pico durante chat ronda los 20 GB, y el decode en frío lee unos 11 GB de disco por token, con una velocidad de 0.05~0.1 tok/s
- Con NVMe más rápidos y más RAM, el caché y el pinning son importantes; en mediciones reales de la comunidad, un Apple M5 Max con 128 GB registró 1.06 tok/s con MTP desactivado
El problema que colibrì busca resolver
- colibrì se presenta como “Tiny engine, immense model” y es un motor para ejecutar el modelo MoE GLM-5.2 de 744B parámetros en máquinas de consumo con unos 25 GB de RAM
- El runtime es C puro, sin dependencias de Python, BLAS ni GPU
- El motor está compuesto por un único archivo C
c/glm.c de unas 1,300 líneas y pequeños headers
- Una ejecución de ejemplo es
./coli chat, y la salida de ejemplo muestra colibrì v1.0 — GLM-5.2 · 744B MoE · int4 · streaming CPU, un tiempo de preparación de unos 32 segundos y 9.9 GB de memoria residente
Ejecución MoE basada en streaming desde disco
- En el modelo Mixture-of-Experts GLM-5.2 de 744B, solo se activan unos 40B parámetros por token
- Los expertos ruteados que cambian en cada token equivalen a unos 11 GB
- La parte dense reside en RAM
- Incluye attention, shared experts y embeddings
- Unos 17B parámetros
- Usa unos 9.9 GB de RAM en int4
- Los expertos ruteados se almacenan en disco
- Incluye 75 capas MoE × 256 expertos y la MTP head
- En total, 21,504 routed experts
- Unos 19 MB por experto en int4
- El uso total de disco es de unos 370 GB
- La carga de expertos usa caché LRU por capa, un hot-store pineado opcional y el page cache del sistema operativo
Funciones principales implementadas
- GLM-5.2 forward se implementó de acuerdo con la estructura
glm_moe_dsa y fue validado token-exact contra el oracle de transformers
- Teacher-forcing 32/32 en el modelo tiny-random
- Generación greedy 20/20
- MLA attention incluye q/kv-LoRA y RoPE parcial intercalado
- La KV-cache comprimida usa 576 floats por token
- Es 57 veces más pequeña que los 32,768 floats/token anteriores
- GLM-5.2 tiene 64 heads y no usa GQA
- Se implementó un router sigmoid estilo DeepSeek-V3
noaux_tc
routed_scaling_factor
- shared expert
- primeras 3 capas dense
- Native MTP speculative decoding usa la multi-token-prediction head de GLM-5.2 (capa 78) para crear tokens borrador, que el modelo principal verifica con un forward en batch
- La MTP head debe ser int8
- En int4, la aceptación de borradores cae a 0~4%, por lo que la especulación no funciona
- En int8, se midió una aceptación de 39~59% y 2.2~2.8 tokens/forward
- Mediante rejection sampling, se mantiene lossless incluso durante el sampling
- Con caché fría, un borrador verificado puede rutear expertos adicionales y aumentar los expert-loads/token de unos 660 a unos 1100
- En ese caso, puede tardar más antes de que la caché y el pin se calienten, por lo que existen un adaptive guard y la opción
DRAFT=0
- True sampling soporta temperature y nucleus
- Valores por defecto: 0.7 / 0.90
- La configuración oficial 1.0 / 0.95 se describe como muestreo de ruido de cuantización en la cola en entornos int4
- El kernel integer-dot usa activaciones int8 estilo Q8_0 y
maddubs de AVX2
- int8 matmul es 1.4~2.5 veces más rápido
- Rendimiento medido: 119 GFLOP/s
- int4 es 1.8 veces más rápido en batch
- int4 single-row resultó más lento en las mediciones, por lo que se mantiene en f32
- MLA weight absorption evita la reconstrucción k/v por token durante decode
- La query absorbe
kv_b y el contexto se proyecta después de attention
- También fue validado en entornos con absorption forzado con TF 32/32 y generation 20/20
- El readahead de expertos lee el siguiente bloque con
WILLNEED mientras se multiplica un bloque de experto
- Los kernels de cuantización soportan int8, int4 empaquetado, int2 empaquetado, escalas por fila, AVX2 y dequant-on-use
- DSA sparse attention está en progreso
- Los pesos de lightning-indexer se extraen desde el repo FP8, unos 108 GB
- El forward del indexer está planeado como siguiente paso
- Hasta entonces, la attention es dense y exacta con context ≤ 2048 tokens
- En prefill y verificación MTP, Batch-union MoE lee solo una vez los expertos únicos del batch y los aplica a todas las posiciones ruteadas a ese experto
- El tokenizer es un byte-level BPE tokenizer implementado en C, con regex de propiedades Unicode estilo GPT-2 y 320k merges
- El mecanismo de seguridad de RAM ajusta automáticamente el tamaño de la caché de expertos al inicio según
MemAvailable
- Refleja la proyección pico del working set, KV, fila MTP y reconstruction buffer
- Está diseñado para evitar que se active el OOM-killer del kernel
- El convertidor offline se entrega como
c/convert_fp8_to_int4.py
- Descarga los shards FP8 de GLM-5.2 de a uno
- Hace dequant con block scales de 128×128 y luego requantize al container del motor
- Va borrando los shards durante el proceso, por lo que no hace falta tener simultáneamente en disco todo el checkpoint FP8 de 756 GB
- La conversión se puede reanudar
Rendimiento base medido y limitaciones
- El entorno de desarrollo es WSL2, 12 núcleos, 25 GB de RAM, NVMe vía VHDX
- Los valores medidos son los siguientes
- Tamaño del modelo container int4: unos 370 GB
- RAM residente: 9.9 GB
- Tiempo de carga: unos 30 segundos
- RSS pico durante chat: unos 20 GB, con auto-cap
- Costo de cold decode: unos 11 GB de lecturas de disco por token
- Techo de disco aleatorio en VHDX: alrededor de 1 GB/s
- Velocidad de cold decode: unos 0.05~0.1 tok/s
- Especulación MTP: 2.2~2.8 tok/forward con head int8
- Se aclara que no es un sistema rápido, y se explica que warm cache, hot experts pineados y MTP reducen la latencia de useful-response
- Hay dos advertencias sobre SSD
- Como el streaming de colibrì es read-only, la lectura en sí no tiene un impacto significativo en el desgaste del SSD
- Si falta RAM y se genera tráfico de swap, habrá escrituras que pueden desgastar la unidad
- Un ciclo prolongado de lectura plena puede calentar unidades económicas, por lo que se requiere monitorear temperatura y health
Descarga y ejecución del modelo
- El modelo GLM-5.2 int4 preconvertido está disponible en Hugging Face
- Si se descarga el modelo preconvertido, se puede omitir el paso de conversión FP8 → int4
- Para ejecutarlo, se define
COLI_MODEL apuntando al directorio del modelo
COLI_MODEL=/path/to/GLM-5.2-colibri-int4 ./coli chat
- El quick start ejecuta
./setup.sh desde el directorio c para verificar gcc/OpenMP, compilar y correr el self-test
- Para convertirlo directamente, se usa
./coli convert --model /nvme/glm52_i4
- Se requiere una ruta ext4/NVMe con unos 400 GB libres
- La conversión requiere Python y
torch, safetensors, huggingface_hub, numpy
- El motor de runtime en sí es C puro; Python solo se usa para el convertidor de una sola vez
- Algunas opciones útiles son
--temp T: sampling temperature, por defecto 0.7 y nucleus 0.90; 0 es greedy
--topp 0.7: adaptive expert top-p, reduce el uso de disco 30~40%
--ngen N: número máximo de tokens por respuesta
AUTOPIN=0: desactiva el auto-pin de learning cache
THINK=1: activa el bloque de razonamiento de GLM-5.2
DRAFT=n: profundidad de borrador MTP
TF=1: validación teacher-forcing
Learning cache y expectativas según hardware
- Learning cache registra los expertos ruteados durante el uso real en
.coli_usage, junto al modelo
- Al inicio, pinea automáticamente los expertos más hot en la RAM libre
- La descripción del proyecto afirma que colibrì se vuelve más rápido a medida que se acumula uso
- El entorno requerido es Linux o WSL2, gcc con soporte OpenMP, AVX2, al menos 16 GB de RAM y un NVMe local con el modelo int4 de unos 370 GB
- Se recomienda una ruta ext4
- Se indica no usar montajes network/9p
- La secuencia de pruebas es: compilar y self-test, medir disco con
iobench, verificar tok/s, expert hit-rate y RSS en chat, registrar el uso de expertos y luego pinear, y ejecutar benchmarks de calidad
- Las expectativas por hardware son estimaciones, no mediciones
- Máquina de desarrollo WSL2 VHDX, alrededor de 1 GB/s, 25 GB de RAM: 0.05~0.1 tok/s en frío
- Linux nativo, PCIe4 NVMe 3~5 GB/s random, 32 GB: 0.5~1 tok/s
- PCIe5 NVMe o 2×NVMe RAID0 8~12 GB/s, 64 GB, PIN de unos 40 GB: 2~4 tok/s
- 128~256 GB de RAM, 12 núcleos, hot experts cacheados: 2~4 tok/s
- Misma RAM con 24~32 núcleos o kernels AVX-512/VNNI: 5~15 tok/s
Benchmarks de la comunidad
- En un Intel Core Ultra 7 270K Plus, 24 threads, WSL2, 24 GB de RAM y NVMe VHDX, la configuración por defecto registró 0.07 tok/s
- disk iobench: 1.96 GB/s buffered, 2.74 GB/s O_DIRECT
- expert hit: 3~4%
- RSS: 14.1 GB
- En el mismo entorno,
--topp 0.7 registró 0.11 tok/s
- expert hit: 11%
- RSS: 14.7 GB
- La velocidad end-to-end aumentó 1.6 veces
- En un Apple M5 Max, 18 cores, macOS, 128 GB de memoria unificada y SSD interno, se registró 1.06 tok/s
- disk iobench: 14.2 GB/s O_DIRECT
- default, MTP off
- expert hit: 23%
- RSS: 21.8 GB
- En máquinas con 24 GB de RAM, la caché de expertos se limita automáticamente a 2 slots por capa, por lo que el decode permanece en estado frío aunque el disco sea 2~2.7 veces más rápido que el de la máquina de desarrollo
- En máquinas con poca RAM, el cuello de botella pasa a ser el RAM cap más que el disco
- La medición del M5 Max es un caso donde un modelo 744B alcanza alrededor de 1 tok/s desde el SSD de una laptop; un disco de 14 GB/s desplaza el cuello de botella nuevamente al presupuesto de RAM y al kernel
Benchmarks de calidad y mediciones pendientes
- Todavía no se midió el costo de la cuantización int4 sobre la precisión
- El benchmark harness está implementado, pero con el disco de alrededor de 1 GB/s de la máquina de desarrollo, una ejecución completa toma una parte considerable del día
- Los comandos de benchmark provistos ejecutan
hellaswag, arc_challenge y mmlu
cd c
./coli bench
./coli bench hellaswag --limit 200
./coli bench mmlu arc_challenge --ram 100
- La salida muestra accuracy por task, y usa log-likelihood scoring con estilo EleutherAI-harness
- Se indica que los puntajes públicos de GLM-5.2 en full precision rondan 85~95% en esas tasks
- Si el container int4 queda dentro de unos pocos puntos, la cuantización queda validada; si no, sería una razón para invertir en cuantización mixta o grouped-scale
Estructura del repositorio y licencia
- La estructura de archivos principales es la siguiente
c/glm.c: motor con GLM-5.2 forward, streaming MoE, MTP y modo serve
c/st.h: lector safetensors basado en pread y fadvise, sin usar mmap
c/tok.h: tokenizer BPE a nivel de bytes en C
c/coli: CLI para chat, run, bench, convert e info
c/iobench.c: microbenchmark de disco paralelo según lo percibe el motor
c/convert_fp8_to_int4.py: convertidor FP8 → int4 seguro para disco
c/make_glm_oracle.py: generador de oracle tiny-random para validación
c/olmoe.c: motor Stage-A y primer target de validación
- El nombre colibrì proviene del colibrí, que vuela suspendido con poco peso y visita muchas flores al día
- La licencia del proyecto es Apache 2.0, y los weights de GLM-5.2 fueron publicados por Z.ai bajo MIT
1 comentarios
Opiniones de Hacker News
Lo que más me intriga es si, en uso real, se mide en tokens/segundo, o si está más cerca de 1 token por minuto.
Aunque un LLM local sea tan lento como 1 token/segundo, me resultó bastante útil dejarle un proyecto durante la noche y revisar el resultado 6 a 8 horas después.
En cambio, los 0.05~0.1 tokens/segundo del hardware de especificaciones mínimas del enlace parecen tener muy pocos usos prácticos.
Aun así, el concepto general es excelente, y espero ver más intentos de permitir correr modelos de clase 350B~900B localmente en hardware que una persona común pueda costear, aunque sea a 1 token/segundo.
Me gusta la dirección de: “si tienes un SSD NVMe con lectura rápida y un modelo enorme en el disco local, no lo cargues completo; lee 11 GB/token cuando haga falta”.
Creo que para 2028 a más tardar ya lo estaré corriendo localmente.
Quiero mantener el presupuesto por debajo de 10 mil dólares y correr un modelo comparable a los mejores modelos actuales.
Antes de gastar dinero en una Mac Studio o en una GPU, ya decidí cómo validar esta idea con el hardware que tengo ahora, y creo que esta semana puedo armar al menos una prueba de concepto.
No es una idea nueva; solo necesito tiempo para hacer que funcione con mi forma de trabajar.
Sigo intentando mejorarlo más.
1 token por minuto es una cifra demasiado pequeña incluso comparada con los límites de uso gratuito.
Estoy construyendo algo parecido para macOS en Apple Silicon.
La idea es usar Unsloth split GGUF, partes comprimidas residentes en memoria unificada, kernels nativos de Metal y KV comprimido nativo solo en RAM.
Creo que tendría más sentido en 128 GB que en mis 64 GB, y pienso subirlo a GitHub cuando esté listo.
También probé una estrategia similar para generación de imágenes/video con https://github.com/cretz/thinfer.
Hay bastante trabajo en la rama video.
Como seguía necesitando un motor de inferencia que pudiera traer y sacar pesos en el momento necesario con una estrategia LRU, terminé vibe codeando una herramienta que acepta
--vram-budgety se mantiene dentro de ese límite.Resultó que el costo de mover bytes mmap hacia dentro y fuera de la VRAM es bastante barato comparado con el cómputo, y si mezclas pipelining y doble buffering, casi siempre el cuello de botella termina siendo el cómputo, no la memoria.
Claro que estoy usando modelos mucho más pequeños.
Me da curiosidad si hiciste un harness común capaz de ejecutarlos todos.
También me interesa saber qué modelos se mantienen de forma más consistente dentro del presupuesto de VRAM.
llama.cpp admite varias cuantizaciones de 4 bits o menos y, por defecto, hace mmap del modelo, así que no hace falta tener todos los pesos en memoria.
El sistema operativo los trae del almacenamiento cuando los necesita.
Es genial que lo hayan implementado con muy poco código y sin dependencias, pero me da curiosidad si en la práctica hay una ventaja de rendimiento.
Bastante genial.
Esta semana estuve usando GLM 5.2 y también me impresionó.
En la empresa estamos probando ejecución local en hardware carísimo antes de iniciar el próximo proyecto, así que me gusta ver que haya gente corriendo este enorme modelo open source en máquinas promedio.
Aunque todavía no sea muy práctico, es un buen trabajo.
No conozco bien la estructura interna de los LLM, pero me pregunto si se podría usar una estructura parecida con otros modelos que tienen “capas”.
¿Sería posible que, cuando una capa termina su trabajo, se la quite de la RAM, se cargue la siguiente capa desde el disco y luego se active sobre el resultado de la primera capa?
Tengo una máquina con 64 GB de RAM y 24 GB de VRAM, así que me pregunto si se podría ajustar para que use más RAM.
O quizá se podría correr Gemma/Qwen en la GPU y hacer que GLM-5.2 le delegue tareas más pequeñas.
Tal vez habría que reentrenar GLM-5.2 en cierta medida.
También me pregunto si se podría aumentar la velocidad usando varios discos en paralelo para incrementar el ancho de banda.
La advertencia de desgaste del SSD dice que el arranque en frío es pesado, con unas lecturas aleatorias de alrededor de 11 GB/token; que las lecturas en sí son seguras, pero que la caché de páginas del sistema operativo puede generar escrituras, y que el uso excesivo puede acelerar el desgaste de SSD baratos.
Quizá una forma segura sería crear una partición aparte para los pesos del modelo y configurarla como solo lectura.
No sé si la caché de páginas funciona por partición o por disco, pero si fuera por disco, quizá también se podría formatear un
data.isode solo lectura como si fuera una partición y montarlo como disco.Si tuviera más discos, sí podría hacer pruebas reales.
Cuando tengas benchmarks, súbelos como pull request o issue y podemos trabajarlo juntos.
¿Qué me estoy perdiendo? ¿Por qué una lectura masiva generaría escrituras?
La página tiene una advertencia de desgaste del SSD: https://github.com/JustVugg/colibri#ssd-wear-warning
Si usas una PC de escritorio ensamblada por ti, puedes reemplazar el SSD, pero ¿qué deberían hacer los usuarios con SSD soldados?
¿Evitar este tipo de aplicaciones, o seguir adelante asumiendo la posibilidad de que el almacenamiento falle antes de tiempo?
Supongo que habría que usar almacenamiento externo como un SSD consumible.
En una laptop con SSD soldado hay que monitorear bien el uso y tener cuidado.
Este proyecto parece más un experimento que algo que todos deberían correr, pero aun así está bastante bueno.
No hace falta ponerse supersticioso aquí.
La actividad del disco, especialmente las escrituras, se puede medir.
Por ejemplo, en Linux puedes usar
iostatovmstat.La aplicación no realiza escrituras, así que en realidad la aplicación en sí no desgasta el SSD.
El resto se parece más a recomendaciones generales de administración, no relacionadas con la aplicación.
Técnicamente es impresionante, pero me pregunto si en realidad se puede usar de forma práctica.
Yo también estaba construyendo básicamente lo mismo, pero opté por mmapear el modelo completo en memoria para evitar usar RAM adicional.
También hice que Claude implementara Medusahttps://arxiv.org/abs/2401.10774 en el modelo, para intentar obtener los beneficios de la predicción de múltiples tokens sin cargar un modelo adicional en memoria.
Acabo de llegar a casa y, para agregar más contexto, todas las modificaciones las estoy haciendo en llama.cpp, y el objetivo final es llevarlo a algo como una computadora de placa única que procese el archivo mmap con NVMe.
Según pruebas anteriores, el límite teórico de la configuración actual parece ser de unos 1.8 tokens/segundo, pero los heads adicionales de Medusa no están completamente entrenados y, sinceramente, no estoy seguro de si cuentan como tokens generados.
Al final, la idea parece similar, pero como todavía no sé escribir desde cero un parser/runner de LLM, en vez de especificar qué dejar en memoria se lo dejé al kernel de Linux.
Por último, limité el uso de llama.cpp a 16 GB de mis 32 GB, así que tal vez sea posible bajarlo aún más.