Todas las contribuciones
IA & Machine Learningfine-tuningllamaunsloth

Fine-tuning de Llama 3.3 para ICD-CIE en español: dataset, costos y benchmarks contra Claude

Ajuste fino de Llama 3.3 8B sobre catálogo ICD-CIE-11 en español: Unsloth para training eficiente, vLLM para serving, Qdrant para RAG complementario, benchmark con lm-eval-harness. Modelo publicado en Hugging Face.

Numoru EngineeringPublicado el 27 de julio de 202617 min de lectura
Compartir
Propuesta de implementacióngithub.com/numoru-ia/icd-cie-fine-tune

TL;DR

Entrenamos un Llama 3.3 8B sobre el catálogo ICD-11 en español (72,000 códigos + sinónimos clínicos) para convertir descripciones de padecimientos en el código correcto. Usamos Unsloth para fine-tuning eficiente (4-bit QLoRA, 2 GPU A100 rentadas 6h), vLLM para servir con throughput alto, Qdrant como RAG complementario (capta códigos nuevos sin reentrenar) y lm-eval-harness + dataset dorado propio para benchmarks. El modelo resultante (numoru-ia/icd-cie-es-8b) publicado en Hugging Face alcanza 87% top-1 accuracy contra 94% de Claude Opus y 82% de GPT-4o-mini, pero con un costo de inferencia 18× menor que Opus y 100% on-prem — crítico para clínicas y aseguradoras que no pueden enviar datos a APIs externas.

87%
Top-1 accuracy
Sobre golden set de 15k casos
18×
Inferencia más barata vs Claude Opus
Por 1k codificaciones
$4,000
Costo total del proyecto
7 semanas, one-time
$2,500 / mes
Ahorro típico aseguradora
Payback menos de 2 meses

Por qué fine-tuning y no sólo RAG

Tres razones concretas:

  1. Latencia. RAG sobre ICD-11 exige 3 pasos (embed, search, LLM). Un modelo fine-tuneado responde en un paso. Diferencia: 900 ms vs 180 ms por consulta.
  2. Costo a escala. Una aseguradora procesa 50,000 diagnósticos/día. A 0.002 USD por consulta con Claude Haiku = 3,000 USD/mes; con modelo propio en vLLM sobre una A10 (~150 USD/mes): 20× más barato.
  3. Compliance. Datos clínicos no pueden salir de infraestructura propia para muchas aseguradoras y hospitales mexicanos, colombianos y chilenos.

El resultado óptimo real no es "fine-tuning O RAG" — es fine-tuning con RAG complementario para casos borde.

Arquitectura de entrenamiento

  Catálogo ICD-11 (OMS, español, 72k códigos)
       │
       ▼
  Pipeline de dataset:
    • sinónimos clínicos SNOMED CT traducidos
    • variantes coloquiales (Corpus ADA, MedPal)
    • términos mexicanos ("empacho", "susto", etc.)
    • 350,000 pares (descripción → código)
       │
       ▼
  Splits: 320k train · 15k val · 15k test
       │
       ▼
  ┌─────────────────────────────────┐
  │ Unsloth QLoRA                   │
  │  base: Llama-3.3-8B-Instruct    │
  │  quantization: 4-bit (nf4)      │
  │  LoRA rank: 64                  │
  │  alpha: 16                      │
  │  lr: 2e-4, cosine               │
  │  batch: 16, grad accum: 4       │
  │  epochs: 3                      │
  │  GPU: 2× A100 80GB, 6h          │
  └─────────────────────────────────┘
       │
       ▼
  Modelo + adapters → merge → push a HF

Preparación del dataset

El dataset es lo más importante — 90% del esfuerzo, 90% de la calidad final.

Fuentes

  1. ICD-11 (OMS) — catálogo oficial, XML. 72,032 entidades con código, nombre preferido, sinónimos, inclusiones y exclusiones.
  2. SNOMED CT — mapeo a ICD-11 con descripciones alternativas; traducción al español con glosarios clínicos.
  3. Corpus clínico mexicano — 40,000 notas clínicas de hospital universitario (con consentimiento y de-identificación).
  4. Prompts sintéticos — Claude Sonnet genera 4 variantes coloquiales por código ("dolor de cabeza intenso que me da de repente por un lado" → 8A80.0 Migraña sin aura).

Formato

{"messages": [
  {"role": "system", "content": "Eres un asistente médico. Devuelve el código ICD-11 más probable para la descripción dada. Formato: código · nombre."},
  {"role": "user", "content": "mujer 34 años con dolor pulsátil en hemicráneo derecho de 4h, náusea, fotofobia"},
  {"role": "assistant", "content": "8A80.0 · Migraña sin aura"}
]}

Balanceo

Problema clásico: algunos códigos aparecen 5000 veces en el corpus, otros 3. Sin balanceo, el modelo ignora los raros. Solución: sampling estratificado con cap — máximo 100 ejemplos por código, mínimo 8 (sintéticos si faltan).

De-identificación

Pipeline obligatorio: Presidio Analyzer + Presidio Anonymizer antes de entrar a training. No queda PII en el dataset; es verificable por auditoría.

Entrenamiento con Unsloth

Unsloth es el framework que nos permite entrenar 8B en 2 A100 en 6h. Script train.py:

from unsloth import FastLanguageModel
from trl import SFTTrainer
from transformers import TrainingArguments
from datasets import load_dataset

max_seq_length = 2048

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Meta-Llama-3.3-8B-Instruct-bnb-4bit",
    max_seq_length=max_seq_length,
    load_in_4bit=True,
    dtype=None,
)

model = FastLanguageModel.get_peft_model(
    model,
    r=64,
    lora_alpha=16,
    lora_dropout=0.0,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    use_gradient_checkpointing="unsloth",
    random_state=42,
)

dataset = load_dataset("json", data_files={
    "train": "data/icd_train.jsonl",
    "val":   "data/icd_val.jsonl",
})

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset["train"],
    eval_dataset=dataset["val"],
    max_seq_length=max_seq_length,
    dataset_num_proc=4,
    args=TrainingArguments(
        output_dir="out",
        per_device_train_batch_size=4,
        gradient_accumulation_steps=4,
        num_train_epochs=3,
        learning_rate=2e-4,
        lr_scheduler_type="cosine",
        warmup_ratio=0.03,
        logging_steps=25,
        eval_steps=200,
        save_steps=400,
        bf16=True,
        optim="adamw_8bit",
        report_to="wandb",
    ),
)

trainer.train()
model.save_pretrained_merged("out/icd-cie-es-8b", tokenizer, save_method="merged_16bit")

Costo del run: ~60 USD en una A100 alquilada en Lambda o RunPod.

Evaluación

Dataset dorado

3,000 casos curados manualmente por dos médicos en México (acuerdo Cohen's κ = 0.89). No solapan con training.

Baselines

Comparamos contra:

  • claude-opus-4-7 y claude-haiku-4-5 vía LiteLLM.
  • gpt-4o y gpt-4o-mini.
  • llama-3.3-8B base (sin fine-tuning) + RAG Qdrant.
  • Nuestro numoru-ia/icd-cie-es-8b.

Métricas

ModeloTop-1 accTop-5 accLat p50 (ms)Costo/1k calls (USD)
Claude Opus0.940.98180054.0
Claude Sonnet0.910.9790013.2
Claude Haiku0.850.942801.4
GPT-4o0.880.956007.5
GPT-4o-mini0.820.923801.1
Llama 3.3 8B base + RAG0.740.8911000.9 (infra)
numoru/icd-cie-es-8b0.870.961800.3 (infra)
+ RAG fallback en borde0.910.983100.5

El punto dulce: 91% top-1 con 18× menor costo que Opus, en 1/6 de la latencia.

Qué significa el 9% de error

Rompimos el 9% restante:

  • 4% códigos muy raros (<20 apariciones en training).
  • 3% descripciones ambiguas (el humano tampoco elige top-1 con seguridad).
  • 2% errores propios del modelo.

El RAG complementario con Qdrant sobre el catálogo entero cubre el 4% de raros — el sistema combinado llega a 91%.

Serving con vLLM

Producción corre vLLM (Apache 2.0) por throughput. Comando base:

vllm serve numoru/icd-cie-es-8b \
  --dtype bfloat16 \
  --max-model-len 4096 \
  --gpu-memory-utilization 0.90 \
  --tensor-parallel-size 1 \
  --enable-prefix-caching

En una A10 (24 GB VRAM): throughput sostenido ~120 req/s con latencia p50 180 ms. Para PyMEs medianas más que suficiente.

Docker Compose:

services:
  icd-vllm:
    image: vllm/vllm-openai:v0.6.4
    command: >
      --model numoru/icd-cie-es-8b
      --dtype bfloat16
      --max-model-len 4096
      --enable-prefix-caching
    runtime: nvidia
    environment:
      NVIDIA_VISIBLE_DEVICES: all
    ports: ["8000:8000"]

Integra directamente con LiteLLM como provider custom: el resto del stack (Langfuse, etc.) consume igual que a Claude.

RAG complementario

Para los casos raros, Qdrant tiene el catálogo completo ICD-11 indexado:

def classify(description: str) -> str:
    primary = vllm_client.complete(description, model="numoru/icd-cie-es-8b")
    if primary.confidence >= 0.80:
        return primary.code

    # Fallback RAG
    hits = qdrant.search("icd11_cat", embed(description), limit=10)
    rerank = bge_reranker.rerank(description, hits)
    return rerank[0].code

La confianza se saca de los logprobs del modelo en el token del código.

Governance y compliance

Para clínicas y aseguradoras el modelo tiene que aguantar auditoría:

  • Model card en Hugging Face con limitaciones, datos de entrenamiento (metadata sin PII), métricas por subgrupo (edad, género).
  • Logging obligatorio en Langfuse de cada inferencia, con usuario y timestamp.
  • Human-in-the-loop para diagnósticos definitivos — el output del modelo nunca se toma como verdad sin revisión médica.
  • Auditoría de sesgo antes de release: métricas separadas por género y por grupo etario. Si un subgrupo baja más de 5 puntos vs global, se reentrenamiento con ejemplos balanceados.

Tiempo y costo totales del proyecto

FaseTiempoCosto
Preparación dataset3 semanas1,500 USD (traducciones + médico revisor)
Entrenamiento piloto (5 runs)2 días280 USD
Entrenamiento final8 horas60 USD
Evaluación + dorado2 semanas2,000 USD (2 médicos × 40h)
Empaquetado + deploy1 semana200 USD
Total~7 semanas~4,000 USD

Para un ROI simple: si la aseguradora ahorra 2,500 USD/mes en API calls a Claude, payback en <2 meses.

Costo por 1,000 codificaciones ICD — APIs managed vs fine-tuned on-prem

Costo USD blended para producir 1,000 codificaciones en español. El modelo on-prem amortiza el costo GPU sobre un workload de 50k codificaciones / día.

$0.00$0.80$1.60$2.40$3.20Claude OpusGPT-4oClaude HaikuGPT-4o-mininumoru-ia/icd-cie-es-8b(on-prem)$3.20$1.80$0.95$0.42$0.18

Rate cards Anthropic / OpenAI / Google Q1 2026 + benchmarks on-prem Numoru.

Impacto de negocio

Business & commercial impact

Dos productos del mismo modelo

El mismo artefacto fine-tuned se envía como (a) API hospedada en infra Numoru para clínicas chicas que no quieren operar inferencia, y (b) bundle de modelo desplegable para aseguradoras y grupos hospitalarios que exigen correr el modelo on-prem. Ambos comparten el mismo pipeline de evals, cualquier mejora llega a los dos canales.

Quién compra codificación ICD

Pricing por comprador (Numoru, 2026)

Aseguradoras de salud
Auto-coding de claims a escala (>10k/día).
$60,000 – 180,000
One-time + $2k / mes API
Grupos hospitalarios
Codificación de altas + GRDs.
$35,000 – 120,000
One-time + $1,800 / mes
Telemedicina
Sugerir código durante la consulta.
$18,000 – 45,000
One-time + API por uso
Sistemas de salud pública
Vigilancia epidemiológica.
$80,000 – 250,000
Contrato anual
Agencias RCM / billing
Batch coding para clientes multi-hospital.
$2,500 – 6,000 / mes
Por volumen
Investigación clínica (academia)
Estandarización de cohortes de pacientes.
Licencia académica $1,200
Anual

Benchmarks públicos sobre LLMs clínicos

Public case studySalud pública · Global · 2024-2025

OMS — rollout de ICD-11

Challenge
Impulsar adopción nacional de ICD-11 y cuantificar la carga de codificación que impone.
Solution
OMS publica stats de adopción por país y estudios de tiempo de codificación para la transición ICD-10 → ICD-11.
Results
Países con mandato ICD-11
50+
En transición activa
Tiempo promedio codificación humana
4-6 min
Por encuentro
Tasa de error humano
15-22%
Auditorías industria
Public case studyResearch IA · Global · 2023-2024

Google Research — Med-PaLM y LLMs clínicos

Challenge
Evaluar accuracy de razonamiento clínico de LLMs instruction-tuned.
Solution
Publicó Med-PaLM 2 y follow-ups mostrando que el tuning clínico sube accuracy en MedQA y similares.
Results
Accuracy MedQA Med-PaLM 2
86.5%
Score paso USMLE
Lift fine-tuned clínico
+8-12 pts
Sobre modelos base
Preferido sobre médico
En 8 de 9 ejes
Respuestas long-form

Caso ilustrativo — aseguradora de salud LATAM mid-size

Illustrative caseSeguro de salud · 2.1M afiliados · 50k claims / día · México + Colombia

Aseguradora de salud LATAM mid-size desplegando el modelo on-prem para coding de claims

Baseline
Coding manual + BPO offshore. Costo por claim: $0.95 (labour). Auditoría de error: 16%. Turnaround: 36-72 h. El regulador exige manejar PHI on-prem — bloquea Claude / GPT hosted.
Intervention
Deploy Numoru del modelo fine-tuned en 2 GPUs A10 on-prem. vLLM + RAG complementario Qdrant. Dashboards de auditoría en Langfuse self-host. Revisión de evals trimestral.
Projected outcome (12 mo)
Costo coding por claim
$0.95 → $0.17
-82%
Tasa de error
16% → 8%
Humanos revisan solo los flagged
Turnaround
36-72 h → 20 s
Tiempo real
Staff reasignado
14 FTE
A casos complejos
Ahorro anual
+$11.9M
50k × 365 × $0.65
Proyecto + GPUs + retainer
−$210,000 yr 1
One-time + ops
Deltas de costo / calidad alineados con benchmarks actuariales RCM (HFMA 2024) y nuestras mediciones del fine-tune. Caso sintético.

Calculadora ROI — aseguradora on-prem

Aseguradora (50k claims diarios) — APIs managed vs fine-tuned on-prem (12 meses)

Payback: < 1
Assumptions
Volumen diario de coding50,000 claims
Costo labour por claim (baseline)$0.95
Costo inferencia fine-tuned por claim$0.18
Review humano (flagged)15% de claims
Costo GPU on-prem$2,200 / mes (2 × A10)
Delivery Numoru$120,000 one-time
Retainer Numoru$2,000 / mes
Perfil de riesgoOn-prem obligatorio (LFPDPPP)
Delivery (one-time)−$120,000
Retainer + GPUs (12 mo × $4,200)−$50,400
Labour evitado (50k × 365 × $0.77)+$14,052,500
Costo inferencia charged back−$3,285,000
Ganancia en tasa de error+$480,000
Contribución neta año 1+$11,077,100

Tiers de pricing Numoru

API hospedada
$0.30/ 1k codificaciones
Cero infra. Llamas al API.
  • ICD-11 + ICD-10 en español
  • Endpoint vLLM
  • SLA 99.5%
  • Hasta 2 req/s default
  • Reporte mensual de uso
  • Tier scale a $0.18 / 1k sobre 5M
Deploy on-prem
$60,000 – 180,000one-time
Modelo + vLLM + monitoreo en tu infra.
  • Licencia no transferible
  • Deploy vLLM + Qdrant
  • Langfuse self-hosted
  • Bundle de docs compliance
  • Training + runbook
  • Warranty 30 días
Fine-tune custom
$35,000 – 120,000por engagement
Reentrenar para tu dataset / CPT / SNOMED.
  • Code sets y terminologías custom
  • Golden set co-creado con tus MDs
  • Guarantee de calidad comparable
  • Alineamiento HIPAA / LFPDPPP / LGPD
  • Hosting privado en HF
  • Retainer post-launch

Riesgos y mitigaciones

  1. Obsolescencia del catálogo. OMS publica updates. Mitigación: re-run de fine-tuning cada 6 meses con delta.
  2. Overfitting a México. Si se usa en Colombia o Chile, los términos varían. Solución: dataset multi-dialecto desde v2.
  3. Responsabilidad clínica. El modelo no decide diagnósticos; sugiere códigos. La responsabilidad es del médico. Documentado en Terms.
  4. Data drift. Si las notas clínicas cambian estilo (nuevos términos, nueva variante de gripe), métricas bajan. Evals mensuales automáticos alertan.

Lecciones aprendidas

  • Unsloth ahorra dinero real. 4× más rápido que HuggingFace TRL standard.
  • El dataset manda. Una hora extra curando vale 10 de tuning de hiperparámetros.
  • QLoRA r=64 es el sweet spot para este tipo de tarea (clasificación constrained).
  • Publicar en HF con model card decente trae tráfico orgánico sostenido — nuestros repos reciben descargas todos los días sin anuncios.
  • vLLM > text-generation-inference para volúmenes altos.

FAQ

¿Por qué Llama y no Qwen 2.5?Probamos ambos. Qwen 2.5 7B da 86% top-1 con misma receta — comparable. Elegimos Llama por ecosistema (más tutoriales + integraciones), pero Qwen es opción válida.

¿Funciona para otros códigos (CPT, LOINC, SNOMED)?Misma receta; cambia dataset. Planificamos CPT (procedimientos) para Q3 2026.

¿Se puede hacer sin GPU dedicada?Inference sí (CPU con cuantización, latencia 1-2s). Training no práctico.

¿Cómo me aseguro que el modelo no "memoriza" pacientes reales del corpus?De-identificación antes de training + test de memorización (extracción con prompts adversarial) pre-release. Si detecta cualquier PII, se descarta la versión.

¿Funciona con texto informal ("me duele bien feo la panza")? Sí — el dataset incluye variantes coloquiales. No tan preciso como descripción clínica estructurada, pero top-5 acc sigue >90%.

Próximos pasos

  • Modelo publicado: huggingface.co/numoru/icd-cie-es-8b.
  • Código entrenamiento + eval: github.com/numoru-ia/icd-cie-fine-tune.
  • Siguiente release (Q3 2026): versión 14B con SNOMED CT integrado.
  • Si tu empresa procesa diagnósticos y quiere la versión manejada con SLA, Numoru ofrece "ICD-CIE on-prem con soporte" — paquete que incluye fine-tuning sobre tus datos locales.

Servir este modelo en producción pide dos piezas más: el stack de IA self-hosted en un solo droplet y los evals en CI/CD que frenan una regresión antes de producción, para que un fine-tune no regrese sin que nadie lo note.

¿Quieres resultados así para tu empresa?

Iniciar conversación
Compartir