Fine-tuning continuo en producción: del tráfico real al adapter desplegado

TL;DR

Fine-tuning continuo no es “entrenar el modelo cada cierto tiempo”. Es un ciclo cerrado donde el tráfico real de producción genera los datasets, un pipeline corto entrena un adapter LoRA, una batería de evaluaciones decide si promociona, y vLLM lo carga sin reiniciar. El estado del arte en mayo de 2026 ha fragmentado el stack: ya no es DPO contra todo, sino una elección entre SFT, DPO, KTO, ORPO y SimPO según el tipo de señal que captura tu producto. Lo que ha consolidado el patrón es la combinación PostgreSQL 18 + pgvector 0.8 como sistema nervioso del pipeline —captura de tráfico, dataset versioning, eval results, registry de adapters—, junto a vLLM multi-LoRA hot-swap que convierte el despliegue en una llamada HTTP. Este artículo desmonta el ciclo con esquemas concretos, queries reales, y los números que cuestan en una RTX 4090 frente a un cluster 4×H100.

La analogía: el restaurante que afina su carta

Imagina un restaurante de barrio con un plato estrella que funciona, pero el chef sabe que se puede afinar. Cada noche pasan cosas:

  • Algunos comensales dejan parte del plato: señal débil de que algo no acabó de encajar.
  • Otros piden otra versión ("¿podrías ponerle menos sal?"): señal explícita y direccional.
  • Otros terminan el plato y vuelven la semana siguiente: la única señal que de verdad importa, pero llega tarde.
  • Y un grupo selecto opina sin que se les pregunte, normalmente para mal.

El chef no rehace su carta cada noche. Hace algo más interesante: anota en una libreta los platos servidos, las devoluciones, los cambios pedidos, las propinas. Cada cierto tiempo, lee la libreta entera, decide ajustes mínimos en una receta, prueba la nueva versión en mesa privada con su personal, y solo si la prueban favorablemente la incorpora a la carta del día siguiente. A veces incluso sirve dos versiones distintas del plato a distintas mesas durante una semana, mide qué pasa, y elige.

Eso es fine-tuning continuo. La libreta es Postgres. El plato es el modelo base. Las anotaciones son señales de feedback —explícitas y implícitas—. El “ajuste mínimo” es un LoRA adapter de 30 MB. La mesa privada es la batería de evaluaciones automatizadas. La carta del día siguiente es vLLM con multi-LoRA hot-swap, que carga el nuevo adapter sin reiniciar el servicio. El servir dos versiones a distintas mesas es A/B testing con tráfico real.

La analogía es exacta en un punto crítico: el chef no tira la receta original. Mantiene la receta base y guarda una libreta separada con las “modificaciones que dan buen resultado para los habituales del barrio”. Esa libreta es el adapter LoRA: encima del modelo base, no en su lugar.

El ciclo, desmontado

Antes de entrar en componentes, conviene fijar el flujo completo. Estos siete pasos son lo que cualquier equipo serio replica con variaciones:

El ciclo cerrado de fine-tuning continuo1 · vLLM servingbase + adapters activos2 · Captura tráficoprompts, respuestas, feedback3 · Curacióndedup, PII, balanceo, snapshot4 · Training LoRASFT / DPO / KTO / ORPO / SimPO5 · Eval gates3 etapas: PR, full, canary6 · Adapter registrystatus: canary | prod | retired7 · Hot-swapPOST /v1/load_lora_adapterPostgreSQL 18+ pgvector 0.8single source of truth

El ciclo dura entre 1 y 4 semanas en producción real. Lo que cambia entre equipos es el ritmo (más rápido en chat asistente, más lento en banca regulada) y los detalles de cada paso. La estructura es la misma.

Por qué fine-tuning continuo (y por qué no es RAG)

Antes de profundizar, una distinción que se sigue confundiendo. Fine-tuning sirve para forma, no para hechos. Si tu problema es que el modelo no conoce las tarifas del cliente o el catálogo actualizado, no fine-tunees: usa RAG. Si tu problema es que el modelo responde con un tono que no encaja, no respeta tu formato JSON, rechaza casos legítimos o se inventa estructura, ahí sí es fine-tuning.

En 2026 el límite ya está bien establecido por la práctica de la comunidad:

Problema observadoSolución
El modelo no sabe X (X cambia semanalmente)RAG
El modelo conoce X pero responde mal de tono o formatoFine-tuning SFT
Hay dos formas de responder y prefiero una sobre otraFine-tuning con preferencias (DPO/KTO/ORPO/SimPO)
El modelo razona mal en un dominio verificable (código, mates)RL con recompensa verificable (GRPO/DAPO)
El modelo es competente, solo necesita memoria de hechosRAG, no fine-tuning

Fine-tuning continuo es la versión disciplinada del segundo y tercer caso. La palabra clave es continuo: no es un evento puntual de “alineamos el modelo”, es un proceso que toca cada vez que la distribución del tráfico se desvía lo suficiente, o que aparecen nuevos casos de uso.

Las cuatro técnicas según la señal que captures

El cambio más importante de los últimos 12 meses ha sido el fin del monopolio de DPO. En 2024 todo equipo que hacía alineamiento usaba DPO con pares (chosen, rejected). En 2026 la elección es más fina y depende de cómo es la señal que recoges en tu producto:

Señal real en productoTécnica recomendadaPor qué
Ejemplos correctos etiquetados (input → output esperado)SFT + LoRASigue siendo la base. 500-5.000 ejemplos bastan para estilo.
Pares explícitos (chosen, rejected)DPO o SimPOSimPO elimina el modelo de referencia → 50 % menos VRAM en entrenamiento.
👍 / 👎 sueltos sobre respuestasKTOEl método que más naturalmente encaja con la telemetría real.
SFT y preferencias en una sola pasadaORPOUn solo modelo en memoria, evita el drift entre fases.
Recompensa verificable (tests, soluciones)GRPO / DAPORazonamiento, no chat. Otro mundo.

La regla práctica: diseña la captura de feedback en producto pensando en qué método podrás usar después. Si tu UI sólo tiene 👍/👎, fuerzas el camino a KTO. Si añades un botón “regenerar respuesta”, desbloqueas DPO desde el regenerate-as-rejected (lo veremos abajo). Si añades un botón “editar respuesta”, la respuesta editada se convierte en SFT directo de alta calidad.

Hay un detalle de coste que se publicita poco. DPO necesita mantener en memoria dos modelos: el que entrenas y el de referencia. SimPO elimina ese segundo modelo. ORPO también. Para un Llama 3 8B en BF16 esto es la diferencia entre necesitar ~32 GB de VRAM activos durante entrenamiento (DPO) o ~16 GB (SimPO/ORPO). Es la diferencia entre que el entrenamiento quepa en una RTX 4090 con QLoRA agresivo, o no quepa sin offload.

Postgres como sistema nervioso del pipeline

Aquí está la opinión técnica fuerte de este artículo, y es la que conviene defender con datos: Postgres 18 + pgvector 0.8 + un bucket S3/MinIO para los pesos es suficiente para todo el pipeline. No hace falta MLflow, no hace falta lakeFS, no hace falta DVC.

No se trata de minimalismo ideológico. Se trata de tres ventajas concretas que ningún stack alternativo iguala en el escenario on-premise con compliance:

  1. Una sola fuente de verdad, un solo modelo de autorización. Las ACL que ya tienes para Postgres cubren los datos de entrenamiento, los resultados de eval, el registry de adapters y el log de auditoría. No multiplicas planos de control.
  2. SQL como lenguaje universal del pipeline. El query que genera el dataset, el predicado del eval gate, la asignación de tráfico A/B, la decisión de promoción: todo es SQL. Tu equipo ya sabe SQL.
  3. Audit y reproducibilidad criptográfica gratis. Las extensiones pg_audit y pgcrypto, combinadas con set_hash sobre el dataset, te dan trazabilidad criptográfica sin código adicional. Es un terreno que da para artículo propio.

Esquema concreto

Empezamos por la tabla de tráfico, particionada por semanas para que el DROP PARTITION sea barato:

CREATE TABLE obs.inference_log (
  id            BIGSERIAL,
  request_id    UUID NOT NULL,
  tenant_id     INT NOT NULL,
  user_hash     BYTEA,                    -- pseudonimización GDPR
  adapter_id    TEXT NOT NULL,            -- ej. "support-es-v4.1"
  experiment    TEXT,                     -- ej. "rerank-v2-canary"
  variant       CHAR(1),                  -- 'A' | 'B' | NULL
  messages      JSONB NOT NULL,
  completion    TEXT,
  ttft_ms       INT,
  tokens_in     INT,
  tokens_out    INT,
  -- señales de feedback
  fb_explicit   SMALLINT,                 -- -1/0/+1 (KTO-ready)
  fb_regen      BOOLEAN DEFAULT false,    -- usuario regeneró → DPO-rejected
  fb_edited     BOOLEAN DEFAULT false,    -- usuario editó → SFT golden
  parent_id     BIGINT,                   -- autoreferencia regenerate
  -- vector y meta
  embedding     HALFVEC(1024),            -- pgvector 0.8, mitad de RAM
  pii_flags     SMALLINT DEFAULT 0,       -- bitmask
  created_at    TIMESTAMPTZ DEFAULT now()
) PARTITION BY RANGE (created_at);

CREATE TABLE obs.inference_log_2026w21 PARTITION OF obs.inference_log
  FOR VALUES FROM ('2026-05-18') TO ('2026-05-25');

CREATE INDEX ON obs.inference_log_2026w21
  USING hnsw (embedding halfvec_cosine_ops);
CREATE INDEX ON obs.inference_log_2026w21
  (tenant_id, adapter_id, created_at);

Tres decisiones merecen una nota:

HALFVEC(1024). Vectores en FP16 nativos de pgvector 0.8. La mitad de RAM y disco con pérdida de precisión irrelevante para deduplicación semántica. Esto solo, a escala de millones de filas, ahorra entre 4 y 8 GB.

Particionado semanal por rango temporal. A los 90 días, DROP TABLE obs.inference_log_2026wXX libera espacio en milisegundos sin bloqueo prolongado. Autovacuum nunca vuelve a tocar particiones congeladas.

parent_id autoreferenciado. El usuario regenera la respuesta → se inserta una nueva fila con parent_id apuntando a la anterior. Eso nos dará un dataset DPO sin tocar la UX.

El registry de adapters

CREATE TABLE serve.adapter (
  id                  TEXT PRIMARY KEY,
  base_model          TEXT NOT NULL,
  rank                INT, alpha INT,
  target_modules      JSONB,
  method              TEXT,              -- 'sft'|'dpo'|'kto'|'orpo'|'simpo'
  training_run_id     UUID,
  dataset_snapshot_id UUID,
  weights_uri         TEXT,              -- s3://.../v4.2.safetensors
  eval_summary        JSONB,
  status              TEXT NOT NULL,     -- 'training'|'canary'|'prod'|'retired'
  traffic_pct         NUMERIC(5,2) DEFAULT 0,
  promoted_at         TIMESTAMPTZ
);

El router de vLLM lee esta tabla con TTL de pocos segundos. Un UPDATE serve.adapter SET status='prod', traffic_pct=100 WHERE id='v4.2' es una promoción. Un UPDATE ... SET status='retired' es un rollback. La auditoría de quién hizo qué y cuándo la da pg_audit sin escribir una línea de código adicional.

Generar datasets DPO y KTO desde tráfico real

Aquí es donde la elegancia del esquema paga. El dataset no es un fichero estático: es una vista materializada que se construye con SQL sobre obs.inference_log.

Dataset KTO desde 👍/👎

KTO es el método que mejor encaja con la señal que captura cualquier producto de chat decente. La query:

CREATE MATERIALIZED VIEW train.kto_v3_candidate AS
SELECT
  messages                                            AS prompt,
  completion                                          AS response,
  CASE WHEN fb_explicit > 0 THEN true ELSE false END AS label,
  adapter_id, created_at
FROM obs.inference_log
WHERE fb_explicit != 0
  AND created_at > now() - interval '60 days'
  AND pii_flags = 0
  AND tenant_id IN (SELECT id FROM tenant WHERE consent_training);

Simple. Cada fila con feedback explícito se convierte en un ejemplo (prompt, response, deseable_sí_no). KTO entrena directamente sobre esta señal, sin necesidad de construir pares.

Dataset DPO desde “regenerar”

El truco que vale por sí solo este artículo. Cuando el usuario pulsa “regenerar respuesta”, está dando una señal extraordinariamente fuerte: la primera respuesta no le valió. Si la segunda no se regenera ni se valora negativamente, asumimos que sí. Eso es un par DPO sin un solo clic adicional en la UI:

CREATE MATERIALIZED VIEW train.dpo_v3_candidate AS
SELECT
  rej.messages                                AS prompt,
  cho.completion                              AS chosen,
  rej.completion                              AS rejected,
  rej.adapter_id
FROM obs.inference_log rej
JOIN obs.inference_log cho ON cho.parent_id = rej.id
WHERE rej.fb_regen = true
  AND cho.fb_explicit >= 0
  -- mitigación de length bias en DPO clásico
  AND cho.tokens_out BETWEEN rej.tokens_out * 0.5
                          AND rej.tokens_out * 2.5;

La cláusula sobre longitudes es la cura barata al length bias documentado en DPO. Sin ella, el modelo aprende que “más largo = mejor” porque las respuestas que el usuario acepta tienden a ser ligeramente más largas. Con SimPO o ORPO este filtro es opcional; con DPO clásico es necesario.

Deduplicación semántica con pgvector

Antes de entrenar, dedup. Dos prompts casi idénticos en el dataset es ruido que sesga el modelo:

WITH ranked AS (
  SELECT id, embedding,
         row_number() OVER (
           PARTITION BY hashtext(messages::text)
           ORDER BY fb_explicit DESC, created_at DESC
         ) AS rn
  FROM obs.inference_log
  WHERE created_at > now() - interval '60 days'
)
DELETE FROM train.kto_v3_candidate kto
USING ranked r
WHERE r.rn > 1 AND kto.id = r.id;

Y para los duplicados semánticos (paráfrasis) usamos directamente pgvector 0.8 con iterative index scan:

-- Buscar near-duplicates de un ejemplo cualquiera
SELECT id, messages, embedding <=> $1 AS dist
FROM obs.inference_log
WHERE created_at > now() - interval '60 days'
  AND embedding <=> $1 < 0.05
ORDER BY embedding <=> $1
LIMIT 50;

El iterative scan es una mejora clave de pgvector 0.8: antes, el índice HNSW podía devolver menos resultados de los pedidos cuando había filtros adicionales (WHERE); ahora itera hasta cumplir el límite. Sin esa mejora, las queries de curación sobre datasets de millones de filas eran inviables sin un pre-filtro brutal.

Eval gates: tres etapas, todo SQL

El error más común al implementar fine-tuning continuo es saltarse o aligerar los eval gates. Eso convierte el ciclo en una ruleta. El patrón que funciona en 2026 son tres etapas, cada una con un trade-off latencia/cobertura distinto:

Tres etapas de eval gatesStage 1 · PR< 90 segundosschema-lint + prompt-lint+ 50 casos mini-evalStage 2 · pre-merge< 20 minutos200-500 casos golden+ LLM-as-judgeStage 3 · canary24-72 horas1-5 % tráfico realmétricas online + feedback

Y aquí es donde Postgres vuelve a brillar: el gate de promoción se expresa como un predicado SQL. Nada más:

CREATE TABLE train.eval_result (
  adapter_id  TEXT REFERENCES serve.adapter(id),
  suite_id    TEXT,                    -- 'safety-es', 'support-helpfulness'
  metric      TEXT,
  score       NUMERIC,
  judge_model TEXT,
  judged_at   TIMESTAMPTZ DEFAULT now(),
  PRIMARY KEY (adapter_id, suite_id, metric)
);

CREATE OR REPLACE FUNCTION serve.can_promote(candidate TEXT, current TEXT)
RETURNS BOOLEAN AS $$
  SELECT NOT EXISTS (
    SELECT 1
    FROM train.eval_result c
    JOIN train.eval_result p USING (suite_id, metric)
    WHERE c.adapter_id = candidate
      AND p.adapter_id = current
      AND suite_id IN ('safety-es','support-helpfulness','refusal-rate')
      AND c.score < p.score * 0.98     -- tolerancia 2 %
  );
$$ LANGUAGE sql STABLE;

Una función SQL como gate. Aplicable desde el CI con psql -c "SELECT serve.can_promote('v4.2','v4.1')" y un exit code 0/1. No hace falta un orquestador, no hace falta una UI específica. La auditoría queda en el log de Postgres.

vLLM multi-LoRA: el deploy es un POST HTTP

Hace dos años, desplegar un fine-tune nuevo era rotar pods de inferencia. Hoy es una llamada HTTP. vLLM 0.7+ soporta cargar y descargar adapters LoRA en caliente, manteniendo varios residentes en VRAM y eligiendo el correcto por petición.

Configuración del servidor:

vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --enable-lora \
  --max-loras 4 \
  --max-lora-rank 64 \
  --env VLLM_ALLOW_RUNTIME_LORA_UPDATING=True

Despliegue de un adapter nuevo:

curl -X POST http://localhost:8000/v1/load_lora_adapter \
  -H "Content-Type: application/json" \
  -d '{
    "lora_name": "support-es-v4.2",
    "lora_path": "/mnt/adapters/support-es-v4.2"
  }'

A partir de ese momento, las peticiones que incluyen "model": "support-es-v4.2" se sirven con ese adapter aplicado sobre el modelo base. El switch entre adapters tiene latencia despreciable (la investigación más reciente sobre Activated LoRA lleva esto a niveles donde el coste de cambio es invisible).

Esto cambia la operación de forma sustancial. El despliegue de un fine-tune nuevo deja de ser un evento de infraestructura para convertirse en un cambio de estado en Postgres. El router consulta la tabla serve.adapter, ve que v4.2 está en canary con traffic_pct=5, y dirige el 5 % de peticiones al nuevo adapter. La ruta exacta del 5 % se decide con hashing determinístico del user_id para que un mismo usuario siempre vea la misma variante (sticky):

-- Sin tabla de asignación, sin estado adicional
-- el variant se calcula in-place en SQL o en el router:
SELECT
  CASE WHEN hashtext($user_id || $experiment) % 100
       < (SELECT traffic_pct FROM serve.adapter WHERE id = $candidate)
       THEN $candidate ELSE $current END AS adapter_id;

A/B con tráfico real: medir o vivir engañado

Los eval gates miden contra benchmarks fijos. Eso es necesario pero insuficiente. La realidad solo se mide con tráfico real. Una vez el adapter está en canary, lo que importa son las métricas online medidas sobre obs.inference_log para cada variante:

SELECT
  adapter_id,
  COUNT(*)                                  AS n,
  AVG(fb_explicit)                          AS mean_score,
  STDDEV(fb_explicit) / SQRT(COUNT(*))      AS sem,
  AVG(ttft_ms)                              AS ttft_avg,
  percentile_cont(0.5) WITHIN GROUP
    (ORDER BY ttft_ms)                      AS ttft_p50,
  percentile_cont(0.95) WITHIN GROUP
    (ORDER BY ttft_ms)                      AS ttft_p95,
  AVG(CASE WHEN fb_regen THEN 1 ELSE 0 END) AS regen_rate
FROM obs.inference_log
WHERE experiment = $1
  AND created_at > now() - interval '7 days'
GROUP BY adapter_id;

Lo que se mira: feedback explícito, latencia (TTFT, p50, p95), tasa de regeneración. Un adapter que sube el feedback medio pero también sube la tasa de regeneración es sospechoso —probablemente está respondiendo de forma más vistosa pero menos útil—. Un adapter que baja la latencia pero baja el feedback puede merecer estudio: puede que esté siendo más conciso de la cuenta.

La promoción a prod ocurre cuando, después de 24-72 horas en canary, el adapter candidato supera al actual en al menos una métrica clave sin degradar las demás. Otra vez: es un UPDATE en Postgres.

Aplicado a hardware on-premise típico

Bajemos a dos configuraciones representativas, una de iteración y otra de producción.

Caso 1 — RTX 4090 (24 GB) para iteración de desarrollo

Una RTX 4090 con QLoRA 4-bit puede entrenar adapters sobre un modelo 8B sin sobresalto. El presupuesto de VRAM combina cuatro componentes; el KV cache durante las evaluaciones intermedias no es despreciable y conviene reservarle margen explícito:

Modelo base 8B en 4-bit:    ~5 GB
Activations + gradientes:   ~8 GB (depende de batch y context)
Optimizer state (LoRA r=16): ~0.5 GB
KV cache durante eval:      ~2 GB
Margen de seguridad:        ~8 GB

Tiempos típicos (estimación basada en benchmarks comunitarios; conviene medir con el lab antes de prometer):

DatasetTécnicaAdapter rankTiempo aproximado
1.000 ejemplos SFTLoRA r=161620-40 min
5.000 ejemplos SFTLoRA r=32322-4 h
2.000 pares DPOLoRA r=16161-2 h
5.000 ejemplos KTOLoRA r=32323-5 h

Esto pone el ciclo de iteración —cambio en dataset, retrain, eval, ver número— en franja de una jornada de trabajo. Suficiente para validar hipótesis antes de mover nada al cluster de producción.

Con un cluster de este orden todo el escenario cambia. Se puede:

  • Entrenar LoRA sobre 70B en BF16 sin quantización con tensor parallel = 4.
  • Hacer DPO completo con modelo de referencia residente cuando se cuantiza la referencia a FP8, o pasarse a SimPO / ORPO que eliminan ese modelo intermedio y simplifican la planificación de VRAM (ver tabla de técnicas más arriba).
  • Soportar multi-tenant fine-tuning: varios adapters de clientes entrenándose en paralelo en pipelines lógicos separados, cada uno aislado en una partición distinta de Postgres con sus propias ACLs.
  • Servir multi-LoRA con --max-loras 8 sobre el modelo base sin que la concurrencia baje el throughput de forma perceptible.

La regla práctica de presupuesto: en horizonte de 12 meses, un equipo con este cluster puede ejecutar ~150-200 ciclos de fine-tuning continuo (training + eval + canary + promoción o descarte) si la disciplina del dataset y de los eval gates es estricta. Si no lo es, ejecutará el doble pero con la mitad de utilidad.

Posición dentro de la arquitectura: lo que cubre este artículo y lo que no

Para situar el alcance: el ciclo dibujado al principio tiene siete cajas, todas ellas cubiertas aquí en su mecánica. Quedan deliberadamente fuera tres capas transversales que son las que terminan separando un pipeline que funciona técnicamente de uno que sobrevive a una auditoría:

  1. Provenance criptográfico y trazabilidad. Hemos mencionado dataset_snapshot y pg_audit, pero la mecánica completa —el set_hash sobre los ejemplos, la integración con EU AI Act, el query_sql congelado como prueba de qué entrenó al modelo— da para análisis entero.
  2. Calibración del juez. Hemos asumido que LLM-as-judge funciona. Hace falta calibrarlo contra rúbrica humana en, al menos, 100 casos por suite crítica antes de fiarse. Sin esa calibración, los eval gates son teatro.
  3. El problema del olvido. ¿Qué pasa si un usuario ejerce su derecho al olvido GDPR y sus interacciones formaron parte del dataset de un adapter ya en producción? No hay solución limpia. Hay opciones —retrain incremental, machine unlearning a nivel de muestra, negative LoRA— y conviene conocerlas antes de que un cliente pregunte.

Lo que no hemos cubierto (próximos artículos)

  • Provenance criptográfico sobre Postgres: cómo set_hash y query_sql congelado componen una cadena de custodia auditable bajo EU AI Act.
  • Judge calibration honesta: por qué score > 0.85 no significa nada sin baseline humana, y cómo construir esa baseline sin que cueste un mes de trabajo.
  • El problema del olvido en adapters: machine unlearning a nivel de muestra, retrain incremental y otras técnicas para responder a GDPR sin tirar el adapter.
  • Online DPO y aprendizaje continuo on-policy: estado de la investigación 2026 (Fast-Slow Chasing, RLOO, iterative on-policy) y por qué todavía no es producción.

Ver también

Referencias

  • Hu et al., LoRA: Low-Rank Adaptation of Large Language Models (ICLR 2022).
  • Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs (NeurIPS 2023).
  • Rafailov et al., Direct Preference Optimization: Your Language Model is Secretly a Reward Model (NeurIPS 2023).
  • Meng, Xia, Chen, SimPO: Simple Preference Optimization with a Reference-Free Reward (NeurIPS 2024).
  • Hong et al., ORPO: Monolithic Preference Optimization without Reference Model (2024).
  • Ethayarajh et al., KTO: Model Alignment as Prospect Theoretic Optimization (2024).
  • Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention (SOSP 2023) — vLLM original.
  • Documentación oficial de vLLM Multi-LoRA: https://docs.vllm.ai/en/stable/features/lora/.
  • Documentación oficial de pgvector 0.8: https://github.com/pgvector/pgvector.
  • TRL (HuggingFace) docs: https://huggingface.co/docs/trl.
  • EU AI Act, texto consolidado y calendario de aplicación: https://artificialintelligenceact.eu/.