11°
Portada del artículo: Más bits donde duele: cuanticé un Qwen3.8-27B a la medida de mi 4070 Ti
Cuantizaciónllama.cppGGUFGPUModelos de lenguaje

Más bits donde duele: cuanticé un Qwen3.8-27B a la medida de mi 4070 Ti

El Q4 se pasa de 16 GB y el Q3 pierde calidad. Un parche de llama.cpp reparte los bits capa por capa: medí un Qwen3.8-27B al 94 % de una referencia de 8 bits que sí entra en una 4070 Ti.

Efrain Garay 2 de septiembre de 2026

Quería correr el mejor Qwen3.8-27B posible en mi RTX 4070 Ti, que tiene 16 GB. El problema es de talla de zapato: el escalón Q4_K_M ocupa 15,66 GB de archivo y al cargarlo con contexto se pasa de los 16 GB, da error de memoria. El de abajo, Q3_K_M, entra con holgura pero regala calidad. No había un punto medio.

Un parche de llama.cpp lo da: en vez de un escalón fijo, un objetivo continuo. Medí que a 15 GB el modelo queda con 94,3 % de acuerdo con la referencia de 8 bits y carga donde el Q4 fijo ni arranca.

En 45 segundos: por qué el modelo que cabe es más tonto, cómo el dial reparte los bits capa por capa, y el 94 % de fidelidad que entra en 16 GB donde el Q4 no.Verlo en el visualizador de reels →

Qué es cuantizar, en simple

Un modelo son millones de números, sus pesos. Guardarlos en 16 bits es fiel pero pesado. Cuantizar es guardarlos con menos bits (8, 5, 4, 3) para que el archivo quepa en la GPU. Es la misma idea que pasar una foto de RAW a JPG: ocupa mucho menos y a simple vista se ve casi igual, pero si comprimes de más aparecen los cuadros.

Hasta ahora esa compresión venía en tallas fijas. Q4_K_M significa, más o menos, “cuatro bits y medio por peso, para todo el modelo”. Eliges una talla y rezas para que calce en tu GPU.

cuantización de una señal · esquema
16 bits100%el original, fiel
4 bits29%liviano, casi igual
3 bits23%más liviano, ya se nota

Cada peldaño es un valor que se puede guardar. El eje es el mismo; lo que baja es cuántos peldaños caben. Peldaños ilustrativos, no a escala.

El dial: pedir un tamaño, no una talla

El parche agrega dos opciones a llama-quantize: --target-bpw (bits por peso) y --target-size (tamaño de archivo). Le dices “quiero que pese 15 GB” y un algoritmo decide, tensor por tensor, qué tipo de cuantización usar para cumplir ese presupuesto perdiendo la menor calidad posible.

La diferencia con el escalón fijo se ve mejor mirando lo que decidió de verdad. Le pedí 15 GB al Qwen3.8-27B y revisé el reparto que hizo sobre sus 503 tensores:

llama-quantize --target-size 15g · 503 tensores
141/ 503tensores con más bits que el escalón fijo (4,98)
ffn_down5,50
attn_v5,50
attn_qkv5,14
attn_k5,09
attn_q5,09
attn_output4,35
ssm_alpha4,31
ssm_beta4,31
ffn_gate4,25
ffn_up4,25
attn_gate4,25
ssm_out4,25
bloque 064

Con menos presupuesto medio que el escalón fijo comparable (4,77 contra 4,98 bits) y mejor repartido: las capas de arriba se llevan más que las de abajo. Las celdas apagadas son bloques donde ese tensor no existe (el modelo alterna atención y ssm).

El escalón fijo pondría la misma altura en todas las barras. El dial levantó ffn_down y la proyección de valores de la atención a 5,5 bits —son las que más duelen al comprimir—, dejó las consultas y las claves cerca de 5,1, y hundió el resto (ffn_gate, ffn_up, ssm) a 4,25. Y lo hace con menos presupuesto medio que el escalón fijo comparable: 4,77 bits contra 4,98, mejor repartidos.

Para que el algoritmo sepa qué tensor “duele” más necesita una matriz de importancia (imatrix): una medición de qué activaciones pesan más, calculada pasando texto por el modelo. La calibré con un corpus diverso, no solo con el texto con el que después mido, para no hacer trampa conmigo mismo.

Instalación, con los tropiezos

El parche es el PR #15550 de EAddario, todavía sin integrar a la rama principal. Lo compilé del commit 325319b. Que no esté integrado importa: nadie reproduce esto desde el main de llama.cpp, y encontré al menos un fallo de escritura en el camino.

Tres cosas costaron tiempo:

  • La matriz de importancia no cabía en RAM. Generarla exige correr el modelo, y el original en 16 bits pesa 54 GB, más de lo que tengo entre RAM y VRAM. La calibré desde la versión Q8_0 (8 bits, 28 GB), que para medir importancia es casi idéntica al original. Lo mismo vale para cuantizar: todas las variantes salen del mismo Q8_0, así que la comparación entre ellas es limpia.
  • Un iostream error que no era del parche. A media cuantización, el proceso moría con “error de flujo de entrada/salida”. Perseguí un fantasma en las capas del MTP durante un rato. Era el disco: se había llenado al 100 %. El archivo de logits que genera la comparación de divergencia ocupaba cientos de GB. Acotarlo lo resolvió.
  • El tamaño del archivo no es la VRAM. Un GGUF de 15,66 GB no corre en 16 GB. Entre los pesos, la caché de contexto y los buffers de cómputo, se desborda. Hay que medir la VRAM real, no leer el tamaño del archivo.

Lo que medí

Perplexity sobre wikitext-2 y sobre código (fuera del dominio de calibración), divergencia contra el Q8_0 como referencia, VRAM real con contexto de 8k y velocidad. Todo en la misma 4070 Ti, todo con la misma imatrix.

variantearchivoPPL wikiPPL códigoacuerdo top con Q8¿entra en 16 GB?tok/s
Q3_K_M (fijo)12,57 GB7,1861,94990,4 %37,8
dial ≈ Q312,00 GB7,1311,94590,6 %42,6
dial · 15 GB15,00 GB7,0371,90894,3 %36,2
Q4_K_M (fijo)15,66 GB6,9531,906no carga
Q5_K_M (fijo)18,19 GB6,9621,897no carga

Dos lecturas. A igual tamaño que el Q3_K_M, el dial baja la perplexity (7,131 contra 7,186) y encima va más rápido (42,6 contra 37,8 tokens por segundo). El margen es chico y roza el error de medición, pero la divergencia lo confirma: el dial se queda más cerca del original.

El Q5 mide un pelo peor que el Q4 en wikitext (6,962 contra 6,953): la diferencia cae dentro del error, y de todos modos ninguno de los dos carga en 16 GB.

La lectura que importa es la otra.

El presupuesto de 16 GB

nvidia-smi · RTX 4070 Ti · contexto 8k
3/ 4entran en los 16 GB con contexto útil
dial · 12 GB12,5 GBentra
Q3_K_M (fijo)13,1 GBentra
dial · 15 GB15,3 GBentra
Q4_K_M (fijo)se pasase pasa
  • dial · 12 GB: cabe, y deja sitio para el MTP
  • dial · 15 GB: el más fiel que entra
  • Q4_K_M (fijo): out of memory: no carga

El único escalón fijo que cargaba con contexto útil era el Q3_K_M. El Q4_K_M, mejor en calidad, se pasa. El dial a 15 GB entra por 683 MiB de margen y coincide en el 94,3 % de los tokens con la referencia de 8 bits, contra el 90,4 % del Q3; su error medio es 3,63 % contra 6,63 %, casi la mitad. Es lo que un escalón fijo no puede darte: la calidad que cabe justo en tu GPU, ni una talla más ni una menos. Paga 1,6 tok/s por los bits extra (36,2 contra 37,8 del Q3): barato por casi cuatro puntos de acuerdo.

Ese Q8_0 de referencia sirve para leer la tabla: 94,3 % de acuerdo significa que, de cada 100 tokens, el modelo a 15 GB elige el mismo que la versión de 8 bits en 94, contra 90 del Q3.

El MTP no entra, y es un dato

El Qwen3.8-27B trae MTP, un borrador interno que propone varios tokens de golpe para acelerar. En otra medición me subió de 42 a 80 tokens por segundo.

generación · misma frase, dos ritmos
sin MTP0,19 s
LainferencialocalcorreentupropiaGPU
42 tok/s · uno por paso
con MTP0,10 s
LainferencialocalcorreentupropiaGPU
80 tok/s · tres por paso, aprobados

El borrador propone, el modelo grande verifica. Casi el doble de rápido (42 → 80 tok/s) y el texto que sale es idéntico.

Quise ponérselo encima al modelo de 15 GB.

Cuándo sí y cuándo no

El dial gana cuando el objetivo es llenar un presupuesto exacto: una GPU cuya talla fija de arriba se pasa por poco. Ahí aporta calidad que el escalón no alcanza y carga donde el escalón se desborda.

No lo usaría si te sobra memoria. Si el Q4 te carga con holgura, ya tienes tu modelo, y encima con margen para el MTP, que en velocidad rinde más que el par de décimas de perplexity que araña el dial. Tampoco si necesitas reproducibilidad estricta hoy: el parche no está en el main de llama.cpp.

Para mi caso (27B en 16 GB, sin margen) es la diferencia entre un modelo mediocre que cabe y uno bueno que también cabe.

Fuentes

  • PR #15550, --target-bpw en llama.cpp, de EAddario. Compilado del commit 325319b.
  • Qwen3.8-27B, licencia Apache-2.0.
  • Perplexity y divergencia con llama-perplexity sobre wikitext-2; los GGUF y la imatrix salieron de la versión Q8_0 del modelo.

Comentarios

Todavía no hay comentarios. El primero es tuyo.

Se revisa antes de publicarse. El correo no se guarda ni aparece en ninguna parte.