17°
Portada del artículo: ExLlamaV3 contra llama.cpp con Qwen3.8-27B en 16 GB: decodifica más rápido, pero no siempre gana
ExLlamaV3llama.cppCuantizaciónGPUModelos de lenguaje

ExLlamaV3 contra llama.cpp con Qwen3.8-27B en 16 GB: decodifica más rápido, pero no siempre gana

Les di a los dos motores exactamente los mismos tokens de entrada dentro de un contenedor con límites de pod. ExLlamaV3 decodifica más rápido y procesó 131 mil tokens de contexto; en archivos de 13 GB pierde menos calidad, en los de 15 GB pierde contra el GGUF. En un turno agéntico, el prefill se come casi toda la ventaja.

Efrain Garay 14 de septiembre de 2026

Reproduciendo el resumen

Hace un mes medí si Qwen3.8-27B entraba en mi RTX 4070 Ti SUPER de 16 GB, y después cuanticé el modelo a la medida de la tarjeta con llama.cpp. Siempre con el mismo motor. El 13 de septiembre salió ExLlamaV3 1.5.0 y su tabla de rendimiento empieza en tarjetas de 24 GB. La mía no aparece.

Así que les di a los dos motores exactamente los mismos tokens de entrada, dentro del mismo contenedor. ExLlamaV3 decodificó más rápido en todo lo que medí y procesó 131 mil tokens de contexto, donde llama.cpp llegó a 64 mil, el máximo que probé. En calidad gana a 3 bits y pierde a 15 GB. Y en un turno agéntico real, el prefill se come casi toda la ventaja de velocidad.

En 65 segundos y narrado: el mismo Qwen3.8-27B en la misma GPU de 16 GB, con ExLlamaV3 y llama.cpp recibiendo los mismos tokens. Con MTP en código, 103,1 tokens por segundo contra 79,5; en un turno agéntico de 11 mil tokens la ventaja se encoge a 8,62 contra 9,29 segundos; y en calidad ExLlamaV3 gana con archivos de 13 GB y pierde con los de 15 GB. Sin sonido por defecto: actívalo en los controles.Verlo en el visualizador de reels →

Qué comparé, y por qué es justo

ExLlamaV3 usa su propio formato de cuantización, EXL3, y no carga GGUF. No se puede medir “el mismo archivo en dos motores”: lo más cercano es elegir archivos del mismo tamaño y ver qué hace cada motor con su formato.

  • ExLlamaV3 1.5.0 con el EXL3 de 3.00 bits por peso que publica turboderp: 12,9 GiB en disco, con la cabeza MTP incluida. Para la comparación de calidad a mayor tamaño, también el de 3.50 bits (14,3 GiB).
  • llama.cpp con un Q3_K_M de 12,6 GiB, cuantizado con la misma matriz de importancia del post anterior, y el GGUF de 15,0 GiB que ese post cuantizó a medida. Para el MTP usa un borrador aparte de 1,3 GB.

Los dos reciben los mismos ids de tokens. Tomé la batería de prompts que trae ExLlamaV3 para medir decodificación especulativa (código, agentes con herramientas, traducción, texto creativo), la tokenicé una vez y le mandé esos mismos números a llama-server. Con 16 mil tokens de contexto entran 15 de los 22 prompts; los 7 agénticos más largos quedan fuera en los dos motores.

El banco: un pod, dos motores, los mismos tokensLos dos motores reciben ids de tokens idénticos dentro de un contenedor con límites de pod. La calidad se mide aparte, contra 8 bits.
El banco: un pod, dos motores, los mismos tokenspod · fedora:43 · 8 CPU · 24 GB · GPU por CDImismos tokens15 prompts · contexto 16kllama.cppQ3_K_M · 12,6 GiB+ borrador MTP 1,3 GBExLlamaV3 1.5.03.00 bpw · 12,9 GiB+ cabeza MTP incluidaRTX 4070 Ti SUPER16 GB de VRAMreferencia Q8_0qbench · KLDEl banco: un pod, dos motores, los mismos tokensmismos tokens15 prompts · contexto 16kpod · 8 CPU · 24 GB · GPU por CDIllama.cppQ3_K_M12,6 GiB+ borrador MTPExLlamaV33.00 bpw12,9 GiB+ cabeza MTPRTX 4070 Ti SUPER16 GB de VRAMreferencia Q8_0qbench · KLD

Misma entrada, mismos límites, misma tarjeta. Lo que cambia es el motor, su formato de cuantización y la forma de su MTP.

Todo lo de velocidad corrió dentro de un contenedor con límites de pod: 8 CPU, 24 GB de RAM y la GPU entregada por CDI. Repetí llama.cpp también directo en el host y la diferencia no pasó de 0,6 %: la carga vive en la GPU. La batería de prompts corrió tres veces por motor y ninguna categoría varió más de 0,3 % entre repeticiones, salvo una que explico más abajo. El turno agéntico, cinco veces por configuración. El barrido de contexto es una corrida por tamaño en ExLlamaV3 y dos repeticiones internas de llama-bench en llama.cpp. La calidad y la VRAM pico de ExLlamaV3 con MTP las medí en el host: qbench es determinista y la memoria de la GPU no depende de los límites del contenedor.

Instalación, con los tropiezos

La instalación costó más que la medición, y casi nada fue culpa de ExLlamaV3.

  • Al instalar torch desde el índice de PyTorch, uv cortó dos veces con operation timed out al traer paquetes de NVIDIA, tanto para CUDA 13.2 como para 12.8. No encontré la causa: más tarde, el mismo paquete de 218 MB bajó completo a 11 MiB/s con curl. Terminé instalando torch 2.11.0 desde PyPI, con un tiempo de espera de red más largo.
  • La release trae wheels por combinación de CUDA y torch. Mi script armaba el nombre con variables que quedaron vacías y generó exllamav3-1.5.0+.torch.0, un nombre que uv rechaza. Con el nombre completo, +cu132.torch2.11.0, instaló sin problema.
  • exllamav3 no expone __version__. Mi verificación falló aunque todo estaba bien instalado.
  • Para medir calidad con qbench, la herramienta de ExLlamaV3, hizo falta compilar llama-cpp-python con CUDA (352 s, no hay wheels recientes), instalar transformers y gguf, y corregir el nombre del dataset de wikitext, que la versión nueva de huggingface_hub ya no acepta sin el prefijo Salesforce/.
  • Dentro del contenedor aparecieron tres más. llama-server no encontraba libcudart.so.13, que el host resuelve por ldconfig. torch se caía buscando el nombre de un usuario que el contenedor no tiene. Y ExLlamaV3 no arrancaba en la imagen pelada de Fedora 43 porque Triton necesita un compilador de C para armar sus kernels: le agregué gcc.

La herramienta de medición también se mide

Dentro del contenedor, eval/spec_decode.py dio en dos categorías velocidades que no se sostenían, igual en tres procesos seguidos: 51 tokens por segundo con MTP en un prompt agéntico que el host resolvía a 89 con los mismos tokens, y 69,8 en repetición trivial, que el runner de abajo midió en 115,6. Corriendo ese prompt solo, el contenedor bajó aún más: 18,5, y en otras corridas hasta 14,4. No lo cambiaron los límites de CPU o memoria, ni /dev/shm, ni la librería de cuBLAS, ni la salida de texto, ni cambiar la caché de Triton. No encontré la causa. Lo que sí vi: dentro de un mismo proceso, la primera pasada de ese prompt es lenta y las siguientes no. Por eso la batería de ExLlamaV3 de abajo sale de un runner mío que repite cada prompt tres veces en el mismo proceso: la primera repetición dio 51, las otras dos 90,9.

Velocidad: los mismos tokens, dos motores

Sin decodificación especulativa, ExLlamaV3 generó entre 40,7 y 43,1 tokens por segundo según la categoría, y llama.cpp entre 36,0 y 38,0. En un prefill de 4 mil tokens quedaron casi iguales: unos 1 490 contra 1 530 tokens por segundo.

Con MTP, los dos con ventana de 3 tokens:

Tokens por segundo con MTP, ventana de 3 tokenstokens por segundo, decodificación · más es mejor
  1. ExLlamaV3 · código103,1
  2. llama.cpp · código79,5
  3. ExLlamaV3 · texto creativo75,2
  4. llama.cpp · texto creativo56,2
  5. ExLlamaV3 · traducción75,2
  6. llama.cpp · traducción60,2
  7. ExLlamaV3 · repetición trivial115,6
  8. llama.cpp · repetición trivial89,8

RTX 4070 Ti SUPER en contenedor de 8 CPU y 24 GB. Prompts de eval/spec_decode.py de ExLlamaV3 1.5.0, mismos ids de tokens en los dos motores, greedy, hasta 1024 tokens nuevos. Tokens generados sobre tiempo de generación, mediana de tres repeticiones.

ExLlamaV3 · prompt agéntico · ventana 3
sin MTP0,20 s
LainferencialocalcorreentupropiaGPU
41 tok/s · uno por paso
con MTP0,09 s
LainferencialocalcorreentupropiaGPU
90.9 tok/s · tres por paso, aprobados

La cabeza MTP propone, el modelo verifica. Poco más del doble de rápido (41 → 90.9 tok/s) y el texto que sale es idéntico.

ExLlamaV3 ganó las ocho categorías. Y no es porque su borrador acierte más: llama.cpp aceptó el 91 % de lo que propuso en el prompt agéntico de curl y ExLlamaV3 el 78 %; en traducción con razonamiento, 71 % contra 57 %. ExLlamaV3 va más rápido aun acertando menos. Las cuentas lo explican: en curl, llama.cpp saca 3,7 tokens por ronda de verificación y tarda unos 45 ms en cada una; ExLlamaV3 saca 3,3 tokens, pero cada ronda le toma unos 37 ms. Las rondas las derivo suponiendo tres tokens propuestos por ronda, que es la ventana configurada.

Medio segundo de rondas de verificación, doce veces más lentoCada bloque es una ronda: el borrador propone tres tokens y el modelo los revisa de una pasada. Prompt agéntico de curl, ventana 3.
ExLlamaV336,8 ms por ronda · 3,34 tokens por ronda
13 rondas · 43 tokens · 90,9 tok/s
llama.cpp45,4 ms por ronda · 3,68 tokens por ronda
11 rondas · 40 tokens · 80,2 tok/s

llama.cpp acepta más por ronda, pero cada ronda le toma casi 9 ms más. En el mismo medio segundo ExLlamaV3 alcanza a meter dos rondas más, y de ahí sale su ventaja. Las rondas y los milisegundos por ronda se derivan de los contadores, suponiendo tres tokens propuestos por ronda.

Una caché de Triton vacía arruina la medición

ExLlamaV3 compila kernels de Triton cuando no los tiene en caché, y esa compilación cae dentro del tiempo medido. En el host, con la caché de Triton vacía, el prompt de curl con MTP dio 38 tokens por segundo; con la caché llena, 89.

Cuánto tarda en escribir la misma respuesta, según la caché y la herramientaLos 97 tokens de la respuesta al prompt agéntico de curl con MTP, sin contar el prefill. Cada carril se llena en el tiempo calculado con esos 97 tokens y la velocidad medida.
Caché de Triton llena1,09 s
Caché de Triton vacía2,55 s

A velocidad real. Misma respuesta, misma aceptación del borrador.

eval/spec_decode.py1,9 s
Runner propio, 2.ª y 3.ª pasada1,07 s

A velocidad real. Contenedor de 8 CPU y 24 GB.

Compilar kernels o una primera pasada lenta pueden duplicar el tiempo de la misma respuesta. Por eso las cifras de la batería son la mediana de tres repeticiones.

Otro detalle raro: con cachés de Triton distintas cambió el texto generado, lo justo para mover la aceptación del borrador de 2,34 a 2,46 tokens por ronda. Mi hipótesis es que el autoajuste eligió kernels distintos, pero no lo comprobé. Por eso las cifras de la batería son la mediana de tres repeticiones: con una primera pasada lenta, como la de 51 en curl, la mediana cae en una de las dos rápidas. Con tres muestras no es una medida de variabilidad, solo evita que una pasada anómala defina el número.

El turno agéntico: donde la ventaja se encoge

Los prompts agénticos traen 11 a 14 mil tokens de historial y herramientas, y la respuesta es una llamada de 60 a 97 tokens. Ahí manda el prefill, no la decodificación.

Un turno agéntico completo, de la petición a la última palabraCada carril se llena en la mediana de cinco repeticiones. Prompt de código con 11 021 tokens de contexto.
llama.cpp9,86 s
ExLlamaV39,36 s

Al doble de velocidad. Contenedor de 8 CPU y 24 GB.

llama.cpp9,29 s
ExLlamaV38,62 s

Al doble de velocidad. Contenedor de 8 CPU y 24 GB.

La decodificación se multiplica, pero el turno completo baja apenas entre 6 y 8 %: casi todo el tiempo se va en leer el contexto.

En ese prompt, llama.cpp tardó 7,51 s en llegar al primer token y ExLlamaV3 7,87 s. En el de curl, con 13 703 tokens, 9,44 contra 10,11 s. Leyendo contexto largo, llama.cpp es más rápido. ExLlamaV3 recupera terreno escribiendo: con ventana 4 decodificó a 117,9 tokens por segundo contra 85,9 de llama.cpp. En el turno completo tarda menos en el prompt de código (8,62 contra 9,29 s) y lo mismo en el de curl (11,33 contra 11,34 s). Ojo con leerlo como empate exacto: las respuestas no tienen el mismo largo. En curl, ExLlamaV3 escribió 97 tokens y llama.cpp 81, así que en el mismo tiempo produjo un 20 % más; en código escribió 60 contra 64.

Calidad: cuánto se aleja cada uno del modelo de 8 bits

Medí la divergencia KL contra un Q8_0 del mismo modelo con qbench, sobre 16 bloques de 512 tokens de wikitext. Es una muestra chica, 8 192 tokens, y la referencia es de 8 bits, no el modelo original.

En archivos de unos 13 GB:

  • ExLlamaV3 3.00 bpw (12,9 GiB): divergencia media 0,0435, mediana 0,0164.
  • llama.cpp Q3_K_M (12,6 GiB): divergencia media 0,0554, mediana 0,0238.

En archivos de unos 15 GB:

  • ExLlamaV3 3.50 bpw (14,3 GiB): divergencia media 0,0234, mediana 0,0089.
  • llama.cpp a medida de 15 GB (15,0 GiB): divergencia media 0,0159, mediana 0,0075.
Dónde pierde fidelidad cada cuantización, según cuán segura estaba la referenciaDivergencia KL media contra el Q8_0 (menos es mejor), separada por la confianza del modelo de referencia en su siguiente token. Entre corchetes, la parte de los tokens que cae en cada tramo.
  • ExLlamaV3 3.00 bpw · 12,9 GiB · media 0,0435
  • llama.cpp Q3_K_M · 12,6 GiB · media 0,0554
0–25 % [20,9 %]
0,0581 · más cerca
0,0747
25–50 % [27,9 %]
0,0537 · más cerca
0,0751
50–75 % [19,0 %]
0,0449 · más cerca
0,0558
75–95 % [15,7 %]
0,0351 · más cerca
0,0421
95–100 % [16,4 %]
0,0140
0,0098 · más cerca

confianza de la referencia

  • ExLlamaV3 3.50 bpw · 14,3 GiB · media 0,0234
  • llama.cpp a medida 15 GB · 15,0 GiB · media 0,0159
0–25 % [20,9 %]
0,0305
0,0228 · más cerca
25–50 % [27,9 %]
0,0310
0,0208 · más cerca
50–75 % [19,0 %]
0,0248
0,0163 · más cerca
75–95 % [15,7 %]
0,0207
0,0104 · más cerca
95–100 % [16,4 %]
0,0023 · más cerca
0,0037

confianza de la referencia

A 13 GB ExLlamaV3 queda más cerca en cuatro de cinco tramos; solo en los tokens casi seguros el GGUF lo supera en la media. A 15 GB el GGUF a medida gana todos los tramos menos el último. Son 8 192 tokens de wikitext: una muestra chica.

La comparación está igualada por tamaño de archivo, no por bits por peso. En archivos de 13 GB, ExLlamaV3 queda más cerca de la referencia que el Q3_K_M en todos los tramos de confianza, salvo en la media de los tokens que el modelo de 8 bits predice con más de 95 % de confianza. En archivos de 15 GB se invierte: el GGUF cuantizado a medida gana, con 700 MiB más de disco. Con 15 GiB de modelo más el borrador de 1,3 GB, la cuenta ya no deja sitio para el MTP en 16 GB, y en mis pruebas no pudo crear un contexto de 4 mil tokens con llama-bench; el post anterior lo cargó con 8 mil desde llama-cli.

Hay un detalle de conteo que parece error y no lo es. En capas, el EXL3 de 3 bits usa 3,02 bits por peso y el Q3_K_M 3,84, pero el archivo EXL3 pesa más. Una parte es la cabeza MTP que trae incluida; el resto no lo desglosé.

Por qué la perplexity no calza con el post anterior

qbench dio 9,86 de perplexity para el Q3_K_M. En el post de cuantización publiqué 7,19 para el mismo archivo. No se contradicen: son herramientas distintas. Volví a medirlo con llama-perplexity, la del post anterior, y dio 7,1856. qbench cuenta la ventana completa de 512 tokens sobre 16 bloques, y llama-perplexity solo la segunda mitad de cada ventana sobre todo wikitext.

El presupuesto de 16 GB

nvidia-smi · 4070 Ti SUPER · 16k · pico con MTP
2/ 2entran en los 16 GB con MTP y 16k de contexto
ExLlamaV3 + MTP · 16k13,6 GBentra
llama.cpp + MTP · 16k15,4 GBentra
  • ExLlamaV3 + MTP · 16k: cabeza MTP incluida
  • llama.cpp + MTP · 16k: borrador aparte de 1,3 GB

Con MTP, llama.cpp roza el techo: a 32 mil tokens de contexto ya no cupo. ExLlamaV3 deja unos 2 GB libres de los 15 945 MiB que la tarjeta reporta como utilizables.

Sin MTP, la pregunta es cuánto contexto cabe:

  • ExLlamaV3 con caché a 4 bits procesó un prefill de 131 072 tokens, y con 130 816 tokens de contexto todavía generaba 31,4 tokens por segundo. A 32 mil daba 39,4. Con caché fp16 cargó 32 mil (37,8 tokens por segundo), pero no 64 mil.
  • llama.cpp con caché q8_0 llegó a 64 mil tokens, el máximo que probé (28,9 tokens por segundo). Con caché f16 corrió 32 mil (34,0) y no pudo crear un contexto de 64 mil.
La decodificación a medida que crece el contexto, y dónde deja de caber cada cachéQwen3.8-27B, RTX 4070 Ti SUPER, contenedor de 8 CPU y 24 GB. Desde 8k, cada marca del eje X duplica el contexto.
La decodificación a medida que crece el contexto, y dónde deja de caber cada caché3035404508k16k33k66k131ktokens de contextotokens/s
  • ExLlamaV3 · caché Q4:43,5 → 31,4
  • ExLlamaV3 · caché fp16:43,6 → 37,8 · 66k no carga
  • llama.cpp · caché q8_0:37,9 → 28,9 · sin probar más allá 66k
  • llama.cpp · caché f16:38,3 → 34,0 · 66k no carga

Con la caché cuantizada a 4 bits, ExLlamaV3 todavía decodifica 31,4 tokens por segundo a 131k. llama.cpp con caché q8_0 llega a 64k, el máximo que probé. Las dos cachés fp16 y f16 se detienen en 32k.

Mi opinión

Para una sola GPU NVIDIA de 16 GB, ExLlamaV3 es hoy lo más rápido que medí para este modelo: decodifica más rápido en todas las categorías, con y sin MTP, y procesó 131 mil tokens de contexto. No lo diría de todo uso. Si el trabajo es un agente que relee su historial en cada turno, con 10 mil tokens por vuelta, la diferencia en tiempo real es chica, y llama.cpp lee contexto largo más rápido. Y si lo que buscas es la máxima calidad que cabe en la tarjeta sin MTP, el GGUF cuantizado a medida de 15 GB se aleja menos del modelo de 8 bits que el EXL3 de tamaño parecido.

Tampoco es una comparación de motores puros. Cambian a la vez el motor, el formato de cuantización y la forma del MTP. Lo que mido es qué obtienes al elegir uno u otro con el mismo presupuesto de disco.

Cuándo sí y cuándo no

  • Sí: GPU NVIDIA en Linux o Windows, respuestas largas (código, traducción, redacción), MTP en 16 GB con margen.
  • No: sin GPU NVIDIA, porque ExLlamaV3 necesita CUDA (su descarga a CPU es solo para los expertos de modelos MoE); agentes donde domina el prefill; cuando quieres la mejor calidad posible a 15 GB sin MTP; cuando necesitas el ecosistema de GGUF (Ollama, LM Studio, un archivo que corre en cualquier lado).

Fuentes

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.