⏱️ Lectura: 16 min
Un modelo de lenguaje de decenas de miles de millones de parámetros tarda demasiado, y cuesta demasiado, solo para resolver una clasificación zero-shot como decidir a qué cola de soporte va un ticket. Jeff, un proyecto independiente publicado en GitHub, resuelve esa misma decisión con un modelo de apenas 0.8B de parámetros en 22 milisegundos sobre una RTX PRO 6000.
📑 En este artículo
- TL;DR
- ¿Qué es la clasificación zero-shot?
- Por qué un modelo tan chico compite con los grandes
- Cómo funciona Jeff por dentro
- Ejemplos prácticos con la API de Jeff
- Cómo empezar: instalar y correr Jeff en local
- Casos de uso reales
- Errores comunes y buenas prácticas
- Comparativa: Jeff frente a Jev y los modelos base
- Profundizando: qué pasa en esa única pasada
- Preguntas frecuentes
- ¿Jeff hace clasificación zero-shot con categorías que nunca vio?
- ¿Jeff está afiliado a TypeSafe o a Jev?
- ¿Necesito una GPU para correr Jeff?
- ¿Cuándo conviene hacer un fine-tune propio en lugar de usar Jeff tal cual?
- ¿Jeff reemplaza a un modelo grande como Jev?
- ¿Los datos de entrenamiento de Jeff usan salidas de modelos cerrados?
- Referencias
El modelo nunca vio esas categorías exactas durante el entrenamiento, y aun así elige entre ellas con una probabilidad calibrada, en una sola pasada hacia adelante.
TL;DR
- Jeff-Qwen3.5-0.8B responde en 22 ms sobre una RTX PRO 6000 y 28 ms en Apple M4 Max con MLX.
- Un fine-tune de navegación por voz subió la precisión de 31,7% a 95,8% en menos de media hora sobre una GPU.
- El endpoint /v1/systemone acepta preguntas tipo choice, noul y score en una sola petición HTTP.
- Todo el entrenamiento corre en hardware local: sin GPUs en la nube ni salidas de modelos cerrados en los datos sintéticos.
- Jeff-Qwen3.5-2B alcanza 83,1 de puntaje promedio, por encima del 83,0 publicado por Jev.
¿Qué es la clasificación zero-shot?
La clasificación zero-shot es la capacidad de un modelo de asignar una entrada a una categoría que nunca formó parte de su entrenamiento, describiendo las opciones en lenguaje natural al momento de la consulta. Jeff aplica esto a decisiones cerradas: devuelve una probabilidad calibrada por opción en una sola pasada del modelo.
El nombre de su endpoint, /v1/systemone, no es casual. Los tres tipos de pregunta que acepta (choice, noul y score) imitan lo que en psicología se conoce como sistema 1, la respuesta rápida e intuitiva, frente al razonamiento lento y deliberado de un modelo grande como Jev. Jeff no genera una explicación paso a paso, compara las opciones descritas y entrega un número por cada una.
Por qué un modelo tan chico compite con los grandes
Los números publicados en el repositorio son el argumento central. Sin fine-tune, Qwen3.5-0.8B saca un promedio de 45.3 puntos en cinco benchmarks públicos. Con el fine-tune de Jeff, el mismo modelo base sube a 79.1, casi el doble, y se acerca al 83.0 que reporta Jev, un modelo bastante más grande.
La ganancia no es pareja. En Financial PhraseBank, un benchmark de clasificación de sentimiento financiero, Jeff-Qwen3.5-0.8B llega a 96.4 puntos, por encima del 77.0 publicado por Jev. Pero en BBH (Big-Bench Hard), un benchmark de razonamiento, Jeff se queda en 64.0 frente a los 94.3 de Jev. El patrón se repite en JudgeBench y en la versión difícil de JevBench, ahí es donde el tamaño sí pesa.
La elección tiene un argumento de costo además del de velocidad. Cada llamada a un modelo grande vía API suma latencia de red y un cobro por token de entrada y salida. Un modelo de 0.8B corriendo en el mismo servidor que la aplicación elimina la llamada de red por completo y solo consume el cómputo local de una pasada del modelo, al precio de perder la profundidad de razonamiento de un modelo más grande.
Esa distinción importa para decidir cuándo usarlo. Un clasificador sin ejemplos previos como Jeff funciona bien cuando la tarea es discriminar entre opciones bien descritas, no cuando hace falta razonar sobre una cadena de pasos. Jeff no es un reemplazo de Jev, sino un especialista rápido para decisiones cerradas.
Cómo funciona Jeff por dentro
Todo el entrenamiento ocurre en hardware local, sin depender de GPUs en la nube. Los datos sintéticos de entrenamiento los escribe un modelo abierto, Qwen3.8-Flash-Next, corriendo en dos DGX Sparks. Un modelo cerrado se usó únicamente para revisar la calidad de una muestra de esos datos sintéticos, nunca para generar contenido que terminara en el set de entrenamiento.
El fine-tune en sí corre sobre una sola GPU de estación de trabajo, una RTX PRO 6000. La versión de 0.8B tarda unas 2 horas en entrenar, la de 2B unas 3.5 horas. Las pruebas finales se hacen en una MacBook, para confirmar que el modelo funciona igual de bien fuera del hardware de entrenamiento.
El código de entrenamiento de Jeff parte de la receta abierta AutoJev, lo que explica por qué AutoJev-27B aparece más adelante en la tabla de benchmarks, aunque solo como referencia. El resultado final es un checkpoint que se sirve con un servidor HTTP local (jeff-serve) y que acepta preguntas en el mismo formato que usa Jev, así que migrar código existente entre ambos no exige reescribir la lógica de la aplicación.
flowchart TD
A["Qwen3.8-Flash-Next en 2 DGX Sparks"] --> B["Datos sinteticos de entrenamiento"]
B --> C["Fine-tune en 1 RTX PRO 6000"]
C --> D["Checkpoint Jeff (0.8B o 2B)"]
D --> E["Pruebas en MacBook"]
E --> F["jeff-serve local"]
Ejemplos prácticos con la API de Jeff
El ejemplo más simple es una sola pregunta de tipo choice. El servidor recibe una descripción de la situación y una lista de opciones numeradas como texto.
curl -s localhost:8765/v1/systemone -H 'content-type: application/json' -d '{
"model": "jeff-latest",
"state": "El cliente escribe: mi pedido llego con la caja aplastada.",
"questions": {
"route": {"type": "choice", "instructions": "Que equipo debe atender esto?",
"criteria": {"1": "Reembolsos y pagos", "2": "Paquetes danados o perdidos", "3": "Cuenta y login"}}
}
}'
La respuesta trae una probabilidad por cada opción, la opción elegida y un nivel de confianza:
{
"route": {
"answer": "2",
"confidence": 0.94,
"probabilities": {"1": 0.03, "2": 0.94, "3": 0.03}
}
}
El segundo ejemplo combina dos preguntas independientes en una sola petición: la misma situación anterior, más una pregunta de tipo noul (sí/no, expresada como probabilidad) sobre el tono del cliente.
curl -s localhost:8765/v1/systemone -H 'content-type: application/json' -d '{
"model": "jeff-latest",
"state": "Reclamo de reembolso: el cliente dice que el paquete llego aplastado y quiere su dinero de vuelta.",
"questions": {
"route": {"type": "choice", "instructions": "Que equipo debe atender esto?",
"criteria": {"1": "Reembolsos y pagos", "2": "Paquetes danados o perdidos", "3": "Cuenta y login"}},
"angry": {"type": "noul", "instructions": "El cliente esta enojado?"}
}
}'
{
"route": {"answer": "2", "confidence": 0.91, "probabilities": {"1": 0.06, "2": 0.91, "3": 0.03}},
"angry": {"answer": true, "probability": 0.78}
}
El tercer tipo, score, devuelve un punto sobre una escala que vos describís en las instrucciones, útil para priorizar tickets por urgencia en lugar de solo enrutarlos.
curl -s localhost:8765/v1/systemone -H 'content-type: application/json' -d '{
"model": "jeff-latest",
"state": "El cliente dice que su cuenta fue hackeada y ya le cambiaron la contrasena.",
"questions": {
"urgencia": {"type": "score", "instructions": "Del 1 al 10, que tan urgente es este ticket?"}
}
}'
{
"urgencia": {"answer": 9, "confidence": 0.88}
}
sequenceDiagram
participant C as Cliente HTTP
participant J as jeff-serve
participant M as Modelo Jeff
C->>J: POST /v1/systemone (state + questions)
J->>M: una sola pasada hacia adelante
M-->>J: probabilidad por opcion
J-->>C: respuesta JSON con answer y confidence
Cómo empezar: instalar y correr Jeff en local
Antes de instalar hace falta tener uv, el gestor de paquetes y entornos de Python que usa el proyecto. En Windows y Linux, con una GPU NVIDIA o incluso solo CPU, la ruta es la misma:
uv sync
uv run hf download mstrasser/Jeff-Qwen3.5-0.8B --local-dir checkpoints/jeff-0.8b
JEFF_CHECKPOINT=checkpoints/jeff-0.8b PORT=8765 uv run jeff-serve
El primer comando instala las dependencias declaradas en el proyecto. El segundo descarga el checkpoint de 0.8B desde Hugging Face a una carpeta local. El tercero levanta el servidor en el puerto 8765, listo para recibir las peticiones de los ejemplos anteriores.
Las variables de entorno son simples pero conviene tenerlas claras: JEFF_CHECKPOINT apunta a la carpeta del modelo descargado, PORT define en qué puerto escucha el servidor HTTP, y JEFF_BACKEND elige entre pytorch (por defecto) y mlx en Mac. Ninguna requiere una API key ni una cuenta en la nube: todo el flujo, desde la descarga hasta la inferencia, corre sin salir de la máquina.
En una Mac con chip Apple Silicon, el backend MLX corre bastante más rápido que PyTorch y solo soporta los modelos Qwen: uv sync --extra mac y después JEFF_BACKEND=mlx JEFF_CHECKPOINT=checkpoints/jeff-0.8b PORT=8765 uv run jeff-serve.
💡 Tip: el backend MLX solo funciona con los checkpoints de Qwen3.5, no con Jeff-Gemma4-E2B; para ese modelo en Mac hay que usar el backend de PyTorch sobre CPU.
Casos de uso reales
El README describe el alcance en una frase: las opciones pueden ser cualquier cosa, colas de soporte, intenciones de usuario, etiquetas de moderación, comandos de voz o movimientos de un juego. La condición única es poder describir cada opción en una oración corta.
- Colas de soporte: enrutar un ticket entre reembolsos, envíos dañados o problemas de cuenta, como en el primer ejemplo de este artículo.
- Intenciones de usuario: distinguir si alguien quiere cancelar, actualizar datos o pedir un reembolso a partir de un mensaje libre.
- Etiquetas de moderación: marcar contenido como spam, acoso o contenido normal antes de que llegue a un moderador humano.
- Comandos de voz: el caso del fine-tune propio, elegir entre un conjunto cerrado de acciones de navegación a partir de audio transcripto.
- Movimientos de juego: elegir entre las jugadas legales de un turno, como en las pruebas con Frogger y Doom.
El repositorio prueba a Jeff con varios juegos, incluidos Frogger y Doom, donde el código describe la situación y los movimientos legales en palabras, y el modelo elige uno por turno. Los propios autores aclaran que un juego no es la prueba de clasificación sin entrenamiento específico ideal, porque el estado de un juego no se parece a un dato real desordenado, pero sirve para revisar el comportamiento fuera de los benchmarks estándar.
El caso más concreto sigue siendo el fine-tune propio de navegación por voz: la precisión sobre datos de validación subió de 31.7% a 95.8% en menos de media hora de entrenamiento sobre una sola GPU. Es la prueba de que, cuando el zero-shot no alcanza, unos pocos ejemplos reales del dominio cierran la brecha rápido.
Errores comunes y buenas prácticas
- Usar el modelo sin fine-tune esperando el mismo resultado. Qwen3.5-0.8B sin entrenar promedia 45.3 puntos; el mismo modelo con el fine-tune de Jeff llega a 79.1. La diferencia no es un margen de error, es el fine-tune completo.
- Mezclar el backend MLX con un checkpoint de Gemma. La documentación es explícita: MLX es más rápido en Mac, pero solo corre los modelos Qwen.
- Pedirle razonamiento en varios pasos. En BBH, un benchmark de razonamiento, Jeff-Qwen3.5-0.8B saca 64.0 frente a 94.3 de Jev. Para esa clase de tareas conviene un modelo grande, no un clasificador rápido.
- No versionar el checkpoint descargado. El comando de descarga apunta a una carpeta local; si varios servicios comparten esa carpeta sin control de versiones, un redeploy puede pisar el checkpoint que ya estaba en producción.
- Confundir zero-shot con sin fine-tune. Jeff es zero-shot respecto a las categorías que ve en cada consulta, no respecto a su entrenamiento: el modelo sí fue entrenado, y bastante, antes de poder improvisar sobre categorías nuevas.
⚠️ Ojo: los juegos de prueba y el benchmark de JevBench hard muestran el mismo límite: cuanto más se acerca la tarea a razonamiento largo, más se abre la brecha con un modelo grande como Jev.
Comparativa: Jeff frente a Jev y los modelos base
| Modelo | Parámetros | Overall (5 benchmarks) | JevBench hard | Cuándo usarlo |
|---|---|---|---|---|
| Jeff-Qwen3.5-0.8B | 0.8B | 79.1 | 47.6 | Latencia mínima, clasificación simple en local |
| Jeff-Qwen3.5-2B | 2B | 83.1 | 53.3 | Más precisión sin salir de una sola GPU |
| Jeff-Gemma4-E2B | ~2B (Gemma) | 81.6 | 48.6 | Alternativa a la familia Qwen |
| Jev (published) | modelo grande cerrado | 83.0 | 73.3 | Máxima precisión y razonamiento, vía API |
| AutoJev-27B (published) | 27B | 84.9 | 70.3 | Techo de referencia, cómputo alto |
La fila que más llama la atención es Jeff-Qwen3.5-2B contra Jev: 83.1 contra 83.0 en el promedio de los cinco benchmarks. Pero la columna de JevBench hard corrige cualquier lectura apurada, ahí Jev saca 73.3 y Jeff-Qwen3.5-2B se queda en 53.3. El promedio esconde que Jeff gana en clasificación y pierde en razonamiento. AutoJev-27B se muestra solo como referencia, la comparación real del proyecto es Jeff contra Jev, no contra AutoJev.
Profundizando: qué pasa en esa única pasada
Un modelo generativo normal produce una respuesta token por token: primero decide la primera palabra, después la siguiente, y así hasta terminar la frase o el JSON completo. Ese proceso es lento porque cada token nuevo exige otra pasada por toda la red.
Un modelo systemone como Jeff evita eso. En lugar de generar texto, calcula de una sola vez qué tan probable es cada opción descrita en la petición, y devuelve esas probabilidades directamente. No hay texto que generar ni que parsear después, la respuesta ya es la probabilidad. Es la misma lógica que permite clasificar con la salida de un solo token en otros contextos, aplicada aquí a decisiones con hasta 255 opciones por pregunta.
Los tres tipos de pregunta (choice, noul, score) son formas distintas de leer esas probabilidades: choice reparte la probabilidad entre las opciones listadas y elige la más alta, noul la colapsa a una sola probabilidad de sí, score la traduce a un punto sobre la escala que describiste en las instrucciones.
flowchart TD
Q["Pregunta enviada a Jeff"] --> T{"Tipo de pregunta"}
T -->|"choice"| A["Probabilidad por cada opcion, hasta 255"]
T -->|"noul"| B["Una sola probabilidad de si"]
T -->|"score"| C["Un punto sobre la escala descrita"]
El campo confidence que devuelve cada respuesta sirve para poner un umbral de seguridad en producción: un ticket con confianza por debajo de 0.6, por ejemplo, puede escalarse a revisión humana en lugar de enrutarse automáticamente. Es una ventaja práctica de trabajar con probabilidades calibradas en vez de con texto generado que después hay que interpretar.
Varias preguntas independientes pueden ir en la misma petición, como se vio en el segundo ejemplo de código, y el servidor las responde juntas en una sola pasada del modelo, no una por una.
📖 Resumen en Telegram: Ver resumen
Tu próximo paso: descargá el checkpoint de 0.8B con uv run hf download mstrasser/Jeff-Qwen3.5-0.8B --local-dir checkpoints/jeff-0.8b y probá los tres ejemplos de este artículo contra tus propias categorías de soporte o moderación.
Preguntas frecuentes
¿Jeff hace clasificación zero-shot con categorías que nunca vio?
Sí. Las categorías se describen en cada petición, en el campo criteria, y no necesitan haber aparecido en los datos de entrenamiento. Eso es justamente lo que separa a Jeff de un clasificador tradicional entrenado sobre una lista fija de etiquetas.
¿Jeff está afiliado a TypeSafe o a Jev?
No. El repositorio aclara que Jeff es un proyecto independiente que reutiliza el mismo formato de petición que Jev, pero no tiene relación ni respaldo de TypeSafe, la empresa detrás de Jev.
¿Necesito una GPU para correr Jeff?
No es obligatorio. El backend de PyTorch corre tanto en GPU NVIDIA como en CPU; en Mac, el backend MLX aprovecha el chip Apple Silicon y suele ser más rápido, pero solo con los checkpoints de Qwen3.5.
¿Cuándo conviene hacer un fine-tune propio en lugar de usar Jeff tal cual?
Cuando la precisión zero-shot no alcanza para tu caso específico. El ejemplo de navegación por voz del propio proyecto subió de 31.7% a 95.8% de precisión con un fine-tune de menos de media hora sobre una sola GPU.
¿Jeff reemplaza a un modelo grande como Jev?
No en tareas de razonamiento largo: en BBH y JudgeBench, Jev sigue muy por delante. Jeff compite, e incluso gana, en clasificación y en detección de alucinaciones (RAGTruth), tareas más cercanas a una decisión cerrada.
¿Los datos de entrenamiento de Jeff usan salidas de modelos cerrados?
No para generarlos. La síntesis de datos corre con Qwen3.8-Flash-Next, un modelo abierto, sobre dos DGX Sparks. Un modelo cerrado se usó solo para revisar la calidad de una muestra, nunca para producir contenido de entrenamiento.
Referencias
- GitHub: firelex/jeff: repositorio oficial con el código, los benchmarks y las instrucciones de instalación.
- Hugging Face: Jeff-Qwen3.5-0.8B: checkpoint del modelo usado en los ejemplos de este artículo.
- Documentación oficial de uv: el gestor de paquetes de Python que usa Jeff para instalar dependencias y correr el servidor.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Imagen destacada: Foto de Vishnu Mohanan en Unsplash
¿Te sirvió? ¿Te dio otro error? Contalo abajo: las preguntas se responden y le sirven al siguiente que llegue.
Dejar un comentario
0 Comentarios