Inicio / Artículos / Ejecutando Qwen3.8-27B en una RTX 3090 con vLLM actualizado y DFlash2

Ejecutando Qwen3.8-27B en una RTX 3090 con vLLM actualizado y DFlash2

Cómo un fork fijado de vLLM 0.28.0 con embeddings requantizados y especulación DFlash2 permite ejecutar un modelo híbrido de 27 mil millones en una tarjeta de 24 GB, y dónde el contexto largo lo ralentiza.

3193 palabras

Un modelo de 27 mil millones de parámetros en una sola GPU de consumo suele implicar una difícil elección entre la longitud del contexto, la concurrencia y la velocidad. Una versión modificada de vLLM adaptada para Qwen3.8-27B cambia esa situación en una RTX 3090 de 24 GB: en un trabajo de carga con agentes, alcanza aproximadamente 177 tokens por segundo de decodificación en una tarjeta común (no Ti) con un límite de consumo de 300 W. Esta guía explica cómo realizar una instalación nativa sin contenedores, detalla qué optimizaciones permiten esa velocidad y muestra exactamente en qué punto este enfoque deja de ser eficaz para que pueda decidir si se adapta a su carga de trabajo.

Los dos perfiles de inicio que se describen a continuación equilibran la longitud del contexto con el paralelismo y la velocidad. Las cifras provienen de un trabajo de carga con agentes en una sola máquina, por lo que deben considerarse como una indicación y no como una garantía.

| Config     | Context   | Parallel requests | My measured decode speed |
|------------|-----------|-------------------|--------------------------|
| CTX=fast   | 65,536    | 8                 | ~177 tok/s               |
| CTX=long   | 131,072   | 4                 | ~122 tok/s               |

Dado que el servidor expone una API compatible con OpenAI, conectarlo a un entorno de agentes como DeepSeek Harness consiste simplemente en dirigir al cliente una URL base del formato http://<host>:18020/v1 y proporcionar una clave de autenticación. Los subagentes pueden entonces enviar solicitudes en paralelo a un modelo local que responde a velocidades que anteriormente requerían hardware de centro de datos.

Qué es realmente el proyecto qwen38-27b-rtx3090

El proyecto se encuentra en el repositorio syv-ai/qwen38-27b-rtx3090. No se trata ni de un modelo nuevo ni de un motor de inferencia nuevo. Es una versión derivada basada en vLLM 0.28.0, combinada con un conjunto de parches, scripts de recuantización y perfiles de inicio cuyo único propósito es adaptar este modelo híbrido específico a una tarjeta de 24 GB y hacer que funcione rápidamente.

Se proporciona una imagen de Docker, y docker compose --profile single up -d es toda la instalación si está satisfecho con los contenedores. El repositorio también permite ejecutar los mismos pasos manualmente en un entorno virtual de Python, que es el método utilizado aquí. Esto mantiene los registros visibles, evita tener otra imagen grande en el disco y facilita ver qué cambia en cada paso.

Por qué una cifra de tok/s única significa poco

El rendimiento de esta solución depende en gran medida de la tarea. La generación de código es rápida, la prosa abierta es más lenta, y reproducir texto que ya está en el prompt es drásticamente más rápido (la sección de búsqueda y redacción a continuación explica por qué). La documentación del proyecto enfatiza el mismo punto: un número de rendimiento para esta solución no significa nada a menos que también se sepa qué prompt lo generó. La cifra de 177 tok/s se midió con cargas de trabajo del agente; sus propios resultados dependerán mucho más de la estructura de sus prompts que del azar.

Instalación nativa, sin Docker

Antes de comenzar, asegúrese de que la máquina cuente con:

  • Linux o WSL2 con una RTX 3090 de cualquier variante (lo importante son los 24 GB de VRAM)
  • Un controlador NVIDIA actual, para que nvidia-smi se ejecute sin errores
  • Python 3.12 o posterior, incluidos sus headers de desarrollo
  • Un conjunto de herramientas CUDA 13, o al menos sus cabeceras (ver la trampa común en el paso 2)
  • Aproximadamente 60 GB de espacio libre en el disco para modelos y cachés
  • Una forma práctica de abordar los requisitos a nivel del sistema es dejar que un agente de programación con acceso a la shell (Claude Code, Codex, OpenCode o similar) se encargue de la instalación. Diríjalo al repositorio y pídale que haga funcionar todo el entorno en tu tarjeta 3090. Cuando algo falla, el agente lee el error y lo resuelve in situ, ya sea instalando python3.12-dev o libssl a través de apt, actualizando el controlador o cambiando los conjuntos de herramientas CUDA. Esto convierte la búsqueda de paquetes faltantes, que antes requería horas de búsquedas en foros, en un paso rutinario. Revisa lo que ejecuta el agente, tal como harías con cualquier herramienta que tenga acceso a la shell.

    Paso 1: clonar el repositorio

    Comienza por descargar el repositorio e ingresar en él.

    git clone https://github.com/syv-ai/qwen38-27b-rtx3090
    cd qwen38-27b-rtx3090
    

    Paso 2: crear un entorno virtual e instalar la versión fija de vLLM

    El fork está dirigido específicamente a vLLM 0.28.0, ya que en esa versión la decodificación especulativa DFlash2 pasó a formar parte de vLLM original (fusionada como PR #52816). Los comandos a continuación crean un entorno virtual nuevo e instalan esa versión exacta junto con FlashInfer, las herramientas de descarga de Hugging Face, ninja para la compilación del kernel y pandas.

    python3.12 -m venv venv
    venv/bin/pip install -U pip
    venv/bin/pip install vllm==0.28.0 huggingface_hub hf_transfer ninja \
      flashinfer-python flashinfer-cubin==0.6.13 pandas
    

    Hay dos detalles en los que es fácil cometer errores:

    • No permita que pip mueva flashinfer-python a otra versión. El lanzador establece FLASHINFER_DISABLE_VERSION_CHECK=1, y los parches fueron validados contra exactamente esta pareja de versiones fijas.
  • La ruta de muestreo DFlash2 compila un núcleo FlashInfer en tiempo de ejecución, y ese núcleo incluye curand.h. Si su instalación de CUDA carece de los encabezados curand, la compilación falla al iniciar por primera vez y el servidor cambia silenciosamente a un método alternativo más lento. La salida sigue siendo correcta, el rendimiento disminuye en aproximadamente un 5%, y nada en los registros lo indica. En Ubuntu con CUDA 13, instalar libcurand-dev-13-0 a través de apt soluciona el problema.
  • El segundo punto es un buen ejemplo de un fallo que ninguna verificación de estado detectará. Si sus valores parecen unos pocos por ciento más bajos, primero verifique la presencia de esos encabezados.

    Paso 3: descargar el punto de control base

    El punto de partida es el punto de control dbirks/Qwen3.8-27B-W4A16-AutoRound, de aproximadamente 19,5 GB. Habilitar el modo de transferencia Xet de alto rendimiento acelera considerablemente la descarga.

    HF_XET_HIGH_PERFORMANCE=1 venv/bin/hf download \
      dbirks/Qwen3.8-27B-W4A16-AutoRound \
      --local-dir models/Qwen3.8-27B-W4A16-AutoRound
    

    Paso 4: preparar el modelo

    Los scripts en prepare/ modifican el punto de control. Requantizan las matrices de incrustación de entrada y salida, que los puntos de control cuantizados públicos dejan con precisión total; reconstruyen la cabeza del borrador MTP con un vocabulario más adecuado, y luego descargan la variante rápida junto con el borrador DFlash2 de 1,2 GB. Todo se ejecuta en la CPU y cada script finaliza en pocos minutos.

    M=models/Qwen3.8-27B-W4A16-AutoRound
    venv/bin/python prepare/quant_lm_head.py     $M
    venv/bin/python prepare/quant_embed.py       $M
    venv/bin/python prepare/quant_mtp.py         $M
    venv/bin/python prepare/build_draft_vocab.py $M --ids prepare/draft_vocab_ids.json
    venv/bin/python prepare/fetch_fast_variant.py
    venv/bin/python prepare/fetch_dflash2.py
    

    Paso 5: aplicar la pila de parches

    El directorio patches/ contiene aproximadamente quince archivos .patch que abarcan la configuración de cuantización para incrustaciones, atención split-KV para verificación de borradores, correcciones en los kernels int8 de Marlin, generación de consultas con DFlash2 y más. Su orden está definido en patches/series. El bucle a continuación elimina los comentarios y las líneas en blanco de ese archivo y aplica cada parche al paquete vLLM dentro del entorno virtual. Omite dflash2-backport.patch, que solo existe para versiones de vLLM anteriores a 0.28.0, cuando DFlash2 aún no era nativo.

    sed -e 's/#.*//' -e 's/^[[:space:]]*//;s/[[:space:]]*$//' -e '/^$/d' patches/series |
    while IFS= read -r name; do
      [ "$name" = "dflash2-backport.patch" ] && continue
      patch -p1 -d venv/lib/python3.12/site-packages/vllm < "patches/$name"
    done
    

    Un parche que no se aplica casi siempre indica una incompatibilidad de versiones. Ejecute venv/bin/pip show vllm y confirme que indique exactamente 0.28.0.

    Paso 6: verificar la instalación

    Antes de lanzar cualquier cosa, genere una clave API y ejecute el script de verificación en modo sin conexión. Este verifica que cada parche se haya aplicado y que el modelo tenga la forma esperada.

    openssl rand -hex 24 > api_key.txt
    bash verify.sh --no-server    # checks all patches applied + model shape correct
    

    Un resultado limpio significa que su instalación coincide, byte a byte, con la configuración de referencia utilizada por los mantenedores. Para inferencias autohospedadas, donde una ligera desviación en las versiones es una fuente constante de confusión, esa reproducibilidad resulta excepcionalmente valiosa.

    Paso 7: iniciar el servidor

    Toda la configuración se realiza a través de variables de entorno. La orden por defecto habilita la especulación DFlash2 y el caché de prefijos en la GPU seleccionada; el comentario muestra cómo cambiar al perfil de contexto largo o a un perfil de 245k después de ejecutar kvarn/install.sh.

    CUDA_VISIBLE_DEVICES=0 SPEC=dflash2 PREFIX_CACHE=1 bash single-user/start_qwen.sh
    # add CTX=long for ~131k context (4 slots), or CTX=huge after kvarn/install.sh for 245k
    

    Siempre establezca CUDA_VISIBLE_DEVICES de forma explícita. De lo contrario, vLLM tiende a elegir la GPU con más memoria libre, que en una máquina con múltiples GPUs podría no ser la tarjeta que usted pretendía utilizar. La clave de autenticación se lee desde api_key.txt o desde VLLM_API_KEY; úsela antes de exponer el puerto a cualquier dispositivo que no sea localhost, ya que sin ella el servidor aceptará solicitudes sin autenticación. Los demás parámetros provienen de .env. Unos minutos después del inicio, tendrá un endpoint compatible con OpenAI que sirve un modelo de 27B desde una sola tarjeta cuyo costo es inferior al de una bicicleta usada.

    De dónde proviene la velocidad

    Ejecutar vLLM con puntos de verificación cuantizados disponibles en el mercado desperdicia rendimiento en varios aspectos no obvios. El documento de optimizaciones del proyecto (docs/optimizations.md en el repositorio) describe nueve cambios. Cuatro de ellos explican la mayor parte de las mejoras.

    Cuantización de los embeddings que todos pasan por alto

    Qwen3.8-27B utiliza embeddings desvinculados, lo que significa tablas separadas de entrada y salida de aproximadamente 2.5 GB cada una. Los puntos de verificación “cuantizados” públicos mantienen ambas en formato bf16, principalmente porque cuantizarlos resulta complicado. Los scripts de preparación convierten ambos a int8, recuperando 2.6 GB de VRAM sin pérdida apreciable en calidad. En una tarjeta de 24 GB, eso equivale aproximadamente a la memoria necesaria para el contexto adicional de un usuario más.

    Aprovechamiento de la arquitectura híbrida

    Solo 16 de las 64 capas del modelo son capas de atención convencionales cuya memoria crece con el contexto. Las 48 restantes son capas Gated DeltaNet, un diseño de atención lineal cuyo estado por conversación tiene un tamaño fijo, independientemente de que la conversación tenga 100 o 100,000 tokens. Stock vLLM asignó ese estado en fp32, lo que supone unos 150 MB por solicitud, y no pudo manejar más de 37 solicitudes simultáneas a pesar de tener un límite configurado de 64. La versión modificada lo almacena en fp16, la perplejidad se mantiene idéntica hasta tres decimales, y todos los espacios configurados se vuelven utilizables.

    DFlash2: redactar siete tokens en una sola pasada

    Este cambio es especialmente importante para la latencia en solicitudes individuales. El decodificación especulativa combina un modelo más pequeño con el modelo objetivo de mayor tamaño. El modelo pequeño propone varios tokens futuros, y el modelo objetivo los verifica a todos en una sola pasada forward, manteniendo el prefijo correcto y regenerándose a partir del primer error. La verificación es mucho más económica que la generación en una GPU con limitaciones de memoria, ya que los pesos se leen una sola vez para varios tokens, lo que permite que cada paso genere más de un token.

    El cabezal MTP integrado en Qwen conecta cuatro predicciones una tras otra. El módulo fork lo reemplaza por DFlash2, un generador de cinco capas que predice un bloque completo de siete tokens en una sola pasada no autoregresiva, utilizando estados ocultos extraídos de cinco profundidades diferentes del modelo objetivo. El propio generador fue cuantizado desde 3,85 GB a 1,19 GB con GPTQ, de modo que comparte la tarjeta sin competir intensamente por el ancho de banda de memoria. El resultado es aproximadamente tres tokens por paso en lugar de uno. Dado que la decodificación especulativa estándar acepta o rechaza los tokens preliminares para que la distribución de salida final coincida únicamente con el modelo objetivo, la aceleración es sin pérdidas; la precisión de GSM8K se mantiene entre el 96 y el 96,5% en todas las configuraciones reportadas por el repositorio.

    Un vocabulario preliminar creado a partir de la propia salida del modelo

    El redactor solo puede proponer tokens que existan en su vocabulario de salida reducido; todo lo que esté fuera de él es rechazado por definición. El vocabulario original se derivó de texto web y cubría el 92% de los tokens que este modelo genera realmente. Los mantenedores recopilaron 5,4 millones de tokens de la propia salida del modelo y reconstruyeron el vocabulario a partir de esas cifras, logrando una cobertura del 97,5%. Ese cambio por sí solo aumentó la eficiencia en aproximadamente un 10%, sin necesidad de nuevo hardware ni modificaciones en el modelo.

    Elaboración mediante búsqueda para el texto que cita el modelo

    Otra técnica explica las cifras más extremas. Cuando la salida repite material ya presente en el prompt, como código fuente en proceso de edición o un documento pegado, el fork omite al redactor y propone tokens directamente del contexto. Al reproducir un documento de 25k tokens se alcanzan 381 tok/s en el hardware de referencia. Los agentes de programación pasan gran parte de su tiempo reproduciendo código que apareció momentos antes en el prompt, el caso ideal para esta técnica, y esa es una de las razones por las cuales las mediciones relacionadas con el uso de agentes arrojan valores elevados.

    Por qué duplicar el contexto no agota la VRAM

    El lanzador para un solo usuario utiliza por defecto un contexto de 65k. Al cambiar a CTX=long este valor se duplica aproximadamente, llegando a 131k, y parecería razonable esperar un error de memoria insuficiente en una tarjeta de 24 GB. Sin embargo, el servidor arranca normalmente y solo funciona ligeramente más lentamente. Varias decisiones de diseño explican esto.

    • Los pesos tienen un tamaño fijo. Con la cuantización W4A16, el cuerpo del modelo ocupa unos 15 GB, independientemente de la longitud del contexto.
    • El pool KV se reserva una vez, en bytes. Antes de atender cualquier tráfico, el fork reserva un presupuesto fijo, de unos 5.2 GiB en este caso, y vLLM registra el resultado, por ejemplo “Tamaño del caché KV de la GPU: 136,429 tokens”. Dado que el pool se asigna al iniciar el sistema, o cabe entonces o el servidor se niega a arrancar. No existe una asignación por solicitud que pueda fallar a mitad de una tarea del agente.
  • El perfil largo utiliza tokens más económicos, no más memoria. El perfil predeterminado mantiene los cachés de atención en formato bf16, mientras que CTX=long los almacena en int8, reduciendo aproximadamente a la mitad el costo por token. Así, los mismos 5.2 GiB pueden contener 136,429 tokens en lugar de 68,605, doblando el contexto máximo a 131 mil. La perplejidad forzada por el instructor en un pasaje idéntico difiere solo un 0.2% entre los dos perfiles.
  • La mayoría de las capas no aumentan con el contexto. Solo las 16 capas de atención escalan según la longitud de la secuencia; las 48 capas de DeltaNet mantienen su estado fijo. Por lo tanto, el contexto largo es mucho más económico aquí que en un modelo puramente basado en transformadores, lo cual es una parte importante de la razón por la que el modelo puede caber en una sola tarjeta.
  • Por qué el límite es 131,072 y no 136,429

    No toda la memoria del pool puede destinarse al caché de atención de una sola solicitud. Cada solicitud residente también necesita su estado DeltaNet de tamaño fijo, y DFlash2 agrega ocho espacios para estados especulativos por solicitud además de eso. Con cuatro plazas disponibles en el perfil largo, el iniciador establece el contexto máximo en 131,072, aproximadamente un 4% por debajo de la capacidad del pool, reservando el resto para esas páginas de estado y para el alineamiento de bloques. Este margen es lo que respalda la promesa de no tener falta de memoria: al iniciar se verifica el escenario más exigente, una sola solicitud de longitud máxima mientras todas las plazas restantes también están en uso.

    Qué se sacrifica en su lugar

    Un contexto más extenso se paga en velocidad, no en fallos. Los tokens se almacenan en formato int8, y cada solicitud residente reserva páginas de estado de un pool ahora dividido entre menos espacios. Los slots paralelos pasan de 8 a 4 y la tasa de decodificación disminuye de aproximadamente 177 a unos 122 tok/s. La tarjeta logra más con los mismos bytes y cobra por ello en términos de rendimiento, no en excepciones.

    Dónde falla: contexto profundo para una sola solicitud

    La pila funciona mejor con contextos cortos y medios, que es lo que envían principalmente los agentes. Una sola solicitud que se acerca a 100 mil tokens se comporta de manera diferente. Medido en una tarjeta 3090, una sola solicitud se decodifica a aproximadamente 107 tok/s con un prompt corto, 78 tok/s alrededor de 10 mil tokens y 38 tok/s alrededor de 43 mil, disminuyendo a unos 31 tok/s cerca de los 100 mil. La causa es la tasa de aceptación del modelo, que disminuye con la profundidad del contexto. El repositorio observa el mismo efecto, con la tasa de aceptación estableciéndose alrededor del 0.29 independientemente del tipo de caché, por lo que se trata de una propiedad del modelo y de la profundidad del contexto, y no de un error sin solución.

    Para comparación, una versión derivada basada en llama.cpp de la misma clase de tarjeta mantiene un rendimiento constante de 65 a 80 tok/s con 150k de contexto. No cuenta con un mecanismo de adivinación cuyas predicciones empeoren ni con un bloque de verificación que impida el pago, por lo que nunca es especialmente rápido pero tampoco lento. La tabla a continuación muestra ambos sistemas uno al lado del otro; tenga en cuenta que el rango de contexto corto de vLLM combina una cifra correspondiente a una sola solicitud con la medición del agente de múltiples slots.

    | Context depth   | vLLM fork (DFlash2) | llama.cpp ATX fork |
    |-----------------|---------------------|--------------------|
    | short (<4k)     | 107–177 tok/s       | 65–80 tok/s        |
    | ~10k            | ~78 tok/s           | 65–80 tok/s        |
    | ~43k            | ~38 tok/s           | 65–80 tok/s        |
    | ~100k           | ~31 tok/s           | 65–80 tok/s        |
    

    La regla práctica es directa: si su trabajo se basa en la lectura lenta de un documento muy largo, llama.cpp es la opción más estable; consulte nuestra guía sobre servir un modelo Qwen3.8 MoE en RTX 3090s con descarga de tensores mediante llama.cpp para conocer los detalles de este equilibrio. Si su trabajo consiste en muchas conversaciones de programación de longitud media, la versión derivada vLLM está muy por delante.

    Por qué esto es adecuado para los harnesses de agentes

    Harnesses como Pi, Hermes o DeepSeek Harness no gestionan una sola conversación. Con subagentes generan muchos hilos en paralelo al mismo tiempo, generalmente de cinco a doce, cada uno de longitud media, siendo la mayoría de ellos reutilizadores de un prompt del sistema idéntico y del mismo contexto de códigobase. Esa es precisamente la carga para la cual se ajustó esta versión:

    • El rendimiento total escala con la concurrencia. Cuatro flujos concurrentes generaron más de 300 tok/s en total en una sola tarjeta 3090. Las pruebas de referencia del repositorio muestran entre 279 y 335 tok/s con cuatro solicitudes concurrentes, y hasta aproximadamente 400 tok/s en total con ocho solicitudes y MTP.
  • El caché de prefijos hace que el contexto compartido sea prácticamente gratuito. Con PREFIX_CACHE=1, 64 solicitudes que comparten un prompt del sistema de 5.8k tokens se completaron en 17 segundos en lugar de 222 según las pruebas del repositorio, y la latencia media disminuyó de 95 s a 8 s. Después de la primera solicitud, los subagentes pagan casi nada por las instrucciones compartidas. En este modelo híbrido, el caché también almacena el estado recurrente de DeltaNet, no solo los valores KV de atención, por lo que una respuesta adicional en un documento de 24k tokens tarda aproximadamente 1 segundo en lugar de 23.
  • La redacción por búsqueda coincide con la salida del agente. Los procesos de refactorización, reescritura y edición citan repetidamente su entrada, lo que hace que la velocidad alcance picos de más de 380 tok/s.
  • Existe un límite máximo. Más allá de unos cuatro flujos residuales de contexto largo, la limitación proviene de cómo está dividido el conjunto de recursos, y no de la capacidad de cálculo. El repositorio indica claramente que ocho solicitudes concurrentes de contexto largo rinden significativamente peor que cuatro, describiéndolo como “no un compromiso, sino una pérdida”. Diseñar el sistema alrededor de aproximadamente cuatro procesadores en paralelo a profundidad media permite aprovechar al máximo la tarjeta gráfica. Para ver un ejemplo de lo que puede crear en la práctica una configuración local con Qwen3.8-27B, consulte cómo crear una copia local de un juego con Qwen3.8-27B y Pi.

    Puntos clave

    • La velocidad proviene del software: embeddings requantizados, estado DeltaNet en fp16, un generador DFlash2 de siete tokens y un vocabulario adaptado a las salidas reales del modelo. La GPU no cambia.
  • Marque con un pin cada versión que el repositorio haya marcado, instale los encabezados adecuados y ejecute verify.sh antes de confiar en cualquier prueba de rendimiento que realice.
  • Un grupo KV reservado al inicio convierte los errores de falta de memoria en una decisión que se toma durante el arranque, y el perfil avanzado obtiene más contexto al pasar a usar cachés de tipo int8, aunque a costa de espacios y velocidad.
  • La decodificación especulativa empeora con la profundidad del contexto, por lo que para solicitudes individuales que superen con creces los 40 mil tokens, una configuración de llama.cpp podría ser más adecuada.
  • Establezca la concurrencia del agente de tamaño en unos cuatro trabajadores de profundidad media con caché por prefijo habilitado, y siempre informe el rendimiento junto con la carga de trabajo que lo generó.
  • Para más detalles, la carpeta docs/reproductions del repositorio incluye notas sobre cómo reproducir paso a paso el proceso sin Docker en una tarjeta 3090.

    Lecturas relacionadas

  • Evaluación de la deuda en el diseño del esquema GraphQL con LLMs y un ratchet CI — Cómo utilizar un revisor basado en LLMs y una escala de calificación del 1 al 5 para detectar problemas subjetivos en el diseño de GraphQL en nuevas solicitudes de integración y mapear la deuda ya presente en su esquema.