<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Oci on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/oci/</link><description>Recent content in Oci on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Sun, 26 Jul 2026 19:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/oci/index.xml" rel="self" type="application/rss+xml"/><item><title>Cadena de confianza del modelo (2/4): dónde viven los bytes — registro, artefactos OCI y distribución al nodo GPU</title><link>https://blog.lo0.es/posts/registro-distribucion-modelos-oci-oras/</link><pubDate>Sun, 26 Jul 2026 19:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/registro-distribucion-modelos-oci-oras/</guid><description>&lt;p>En el &lt;a href="https://blog.lo0.es/posts/kserve-open-inference-protocol-plano-control/">primer artículo de la serie&lt;/a> describimos el plano de control: quién declara que existe un endpoint de inferencia, quién lo reconcilia y qué contrato hablan los clientes. Pero un &lt;code>InferenceService&lt;/code> no es más que una promesa hasta que alguien pone 140 GB de pesos en el sistema de ficheros que ve el contenedor. Este segundo artículo va exactamente de eso: &lt;strong>de dónde salen esos bytes y por qué camino llegan a la memoria de la GPU&lt;/strong>.&lt;/p>
&lt;p>Es un tema que el blog ha tocado por los bordes —en la &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">serie de cold start&lt;/a>, en el &lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">versionado de datos con DVC y lakeFS&lt;/a>, en el &lt;a href="https://blog.lo0.es/posts/catalogo-herramientas-oss-llmops/">catálogo de herramientas OSS de LLMOps&lt;/a>— pero nunca de frente. Y conviene hacerlo, porque casi todas las plataformas de inferencia on-premise que se auditan comparten el mismo defecto de origen, que no es de rendimiento sino conceptual: confunden tres cosas distintas, que viven en sitios distintos y fallan de formas distintas.&lt;/p>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Tres planos, no uno.&lt;/strong> &lt;em>Model registry&lt;/em> (metadatos, versiones, linaje, promoción) ≠ &lt;em>repositorio de artefactos&lt;/em> (los bytes, direccionados por digest) ≠ &lt;em>almacenamiento del despliegue&lt;/em> (lo que el pod monta en &lt;code>/mnt/models&lt;/code>). Mezclarlos es lo que rompe la reproducibilidad.&lt;/li>
&lt;li>&lt;strong>El anti-patrón&lt;/strong>: token de Hugging Face en el pod y descarga desde internet en cada arranque. Sin inmutabilidad, sin caché, sin auditoría, sin air-gap y con cold starts de minutos.&lt;/li>
&lt;li>&lt;strong>El artefacto OCI es hoy la respuesta pragmática&lt;/strong>: OCI Image Spec 1.1 (marzo de 2024) trajo &lt;code>artifactType&lt;/code>, &lt;code>subject&lt;/code> y la Referrers API; ORAS v1.3.0 (octubre de 2025) añadió &lt;code>backup&lt;/code>/&lt;code>restore&lt;/code> para air-gap; ModelPack (sandbox CNCF desde junio de 2025) estandariza los mediaTypes de pesos.&lt;/li>
&lt;li>&lt;strong>Kubernetes ya sabe montar artefactos OCI&lt;/strong>: el &lt;em>image volume source&lt;/em> (KEP-4639) llegó a GA en &lt;strong>v1.36&lt;/strong> (22 de abril de 2026). Es el cambio operativo más relevante del año para distribución de modelos.&lt;/li>
&lt;li>&lt;strong>El cuello de botella real casi nunca es la GPU.&lt;/strong> 140 GB por un enlace de 10 Gb/s saturado son unos 112 s; por NVMe Gen4 unos 20 s; por PCIe Gen5 unos 2-3 s. Optimizar el &lt;em>loader&lt;/em> sin arreglar la red es optimizar el eslabón equivocado.&lt;/li>
&lt;li>Un &lt;em>model registry&lt;/em> (MLflow, Kubeflow Hub) &lt;strong>no guarda los bytes&lt;/strong>: guarda la ficha y un puntero. Si esperabas que resolviera tu distribución, has comprado la herramienta equivocada.&lt;/li>
&lt;/ul>
&lt;h2 id="la-analogía">La analogía&lt;/h2>
&lt;p>Piensa en un &lt;strong>archivo histórico&lt;/strong> serio. Tiene tres cosas que un visitante despistado confunde constantemente.&lt;/p>
&lt;p>El &lt;strong>catálogo&lt;/strong>: fichas con signatura, autor, fecha, procedencia, estado de conservación y restricciones de consulta. La ficha pesa gramos y describe algo que pesa kilos. El &lt;strong>depósito&lt;/strong>: las estanterías compactas del sótano donde están las cajas de verdad, con control de acceso, temperatura y un inventario que cuadra al gramo; no sabe nada de &lt;em>por qué&lt;/em> un documento importa, pero garantiza que la caja 4711 sigue conteniendo exactamente lo que contenía. Y la &lt;strong>mesa de la sala de consulta&lt;/strong>: donde el investigador tiene el documento abierto delante. Sitio temporal, caro y del que el documento vuelve al depósito al acabar la sesión.&lt;/p>
&lt;p>El &lt;em>model registry&lt;/em> es el catálogo. El repositorio de artefactos es el depósito. El nodo GPU con el modelo en HBM es la mesa de consulta. Quien dice &amp;ldquo;hemos montado un registro de modelos&amp;rdquo; y lo que ha montado es MLflow, ha montado el catálogo y ha dejado el depósito sin construir: las cajas siguen en el maletero del coche de alguien. Quien mete el modelo dentro de la imagen del contenedor ha metido el documento dentro de la mesa: cada vez que cambia la mesa, mueve el documento. Y quien descarga de internet en cada arranque no tiene depósito: pide el documento por mensajería a otra ciudad cada vez que un investigador se sienta.&lt;/p>
&lt;p>Volveremos al archivo: el &lt;strong>expurgo&lt;/strong> que borra cajas todavía referenciadas, el &lt;strong>carro de transporte&lt;/strong> entre el sótano y la sala, y la &lt;strong>reproducción certificada&lt;/strong> que se envía a la sede aislada.&lt;/p>
&lt;h2 id="tres-planos-que-no-son-el-mismo">Tres planos que no son el mismo&lt;/h2>
&lt;p>Conviene fijar el vocabulario antes de discutir herramientas, porque buena parte de las discusiones de arquitectura en esta capa son en realidad discusiones de nomenclatura.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Plano&lt;/th>
&lt;th>Qué guarda&lt;/th>
&lt;th>Unidad&lt;/th>
&lt;th>Pregunta que responde&lt;/th>
&lt;th>Ejemplos OSS&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Model registry&lt;/strong>&lt;/td>
&lt;td>Metadatos, versiones lógicas, linaje, estado de promoción, métricas de evaluación&lt;/td>
&lt;td>La &lt;em>ficha&lt;/em> del modelo (KB)&lt;/td>
&lt;td>¿Qué versión está aprobada para producción y de dónde salió?&lt;/td>
&lt;td>MLflow, Kubeflow Hub, ClearML&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Repositorio de artefactos&lt;/strong>&lt;/td>
&lt;td>Los bytes, direccionados por digest, inmutables&lt;/td>
&lt;td>El &lt;em>blob&lt;/em> (GB-TB)&lt;/td>
&lt;td>¿Cuáles son exactamente los bytes de esa versión?&lt;/td>
&lt;td>Harbor, &lt;code>distribution&lt;/code>, Zot, MinIO, lakeFS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Almacenamiento del despliegue&lt;/strong>&lt;/td>
&lt;td>La copia que el pod ve montada&lt;/td>
&lt;td>El &lt;em>volumen&lt;/em> (&lt;code>/mnt/models&lt;/code>)&lt;/td>
&lt;td>¿Qué está leyendo el proceso de inferencia ahora mismo?&lt;/td>
&lt;td>PVC, image volume, emptyDir + caché NVMe&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Las tres capas tienen ciclos de vida distintos. Una ficha vive para siempre (auditoría); un blob, mientras alguna versión lo referencie (retención); un volumen, lo que vive el pod. Implementadas como una sola cosa, hereda lo peor de las tres: si el registro es también el almacenamiento del despliegue, borrar una versión antigua tumba un pod en producción; si el almacenamiento del despliegue es también la fuente de verdad, no hay forma de responder qué se sirvió el martes pasado.&lt;/p>
&lt;p>De ahí la regla operativa que vale la pena escribir en la pared: &lt;strong>la fuente de verdad de los bytes es un digest, no un tag y no una ruta&lt;/strong>. Todo lo demás —tags, aliases, rutas de PVC— es un puntero mutable que resuelve a ese digest en un momento dado. El artículo 3/4 construye toda la verificación de procedencia sobre esa premisa.&lt;/p>
&lt;h2 id="el-anti-patrón-de-partida">El anti-patrón de partida&lt;/h2>
&lt;p>Este es, con diferencia, el patrón más extendido en despliegues on-premise que empezaron como un piloto y se quedaron:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-anti-patron&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm/vllm-openai:v0.11.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;--model&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;meta-llama/Llama-3.3-70B-Instruct&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">HF_TOKEN&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">valueFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">secretKeyRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">hf-token&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">token&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nvidia.com/gpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;4&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Funciona. Arranca. Sirve tokens. Y es inaceptable en una plataforma soberana por cinco motivos independientes, cualquiera de los cuales bastaría:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Dependencia externa en el camino crítico de arranque.&lt;/strong> Un &lt;code>Deployment&lt;/code> que no puede recuperarse de un fallo sin acceso saliente a internet no es alta disponibilidad: es una dependencia de terceros no declarada en el SLA.&lt;/li>
&lt;li>&lt;strong>Cero inmutabilidad.&lt;/strong> &lt;code>meta-llama/Llama-3.3-70B-Instruct&lt;/code> es una referencia mutable, y la rama por defecto de un repositorio cambia (correcciones de tokenizer, plantillas de chat). Dos réplicas del mismo &lt;code>Deployment&lt;/code> arrancadas con una semana de diferencia pueden servir bytes distintos, y nada en el clúster lo dice.&lt;/li>
&lt;li>&lt;strong>Cero caché coordinada ni auditoría.&lt;/strong> Cada pod descarga su propia copia: ocho réplicas de un modelo de 140 GB son 1,1 TB de tráfico saliente por cada rolling update. Y no queda registro interno de qué se descargó, con qué digest ni quién lo autorizó; ante un auditor de ENS o de ISO 42001, la respuesta es &amp;ldquo;lo bajamos de internet&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Incompatible con air-gap.&lt;/strong> Todo el diseño se cae el día que hay que replicar la plataforma en un entorno aislado, que es el caso de defensa y sanidad tratado en &lt;a href="https://blog.lo0.es/posts/infra-cumplimiento-normativo-defensa-ia/">infraestructura de cumplimiento normativo&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Cold start de minutos.&lt;/strong> Descargar 140 GB por un enlace de salida de 1 Gb/s (125 MB/s efectivos, siendo generosos) son &lt;strong>unos 19 minutos&lt;/strong> solo de transferencia, antes de tocar el disco o la GPU; con 10 Gb/s dedicados y saturados, unos &lt;strong>112 segundos&lt;/strong>. En un evento de escalado por carga, eso es latencia de servicio.&lt;/li>
&lt;/ol>
&lt;p>La &lt;a href="https://blog.lo0.es/posts/acelerar-cold-start-carga-modelos-tensorizer/">serie de cold start del blog&lt;/a> explora cómo recortar el tramo disco-HBM. Lo que este artículo añade es que &lt;strong>si el primer tramo es internet, el resto de la optimización es irrelevante&lt;/strong>.&lt;/p>
&lt;h2 id="el-modelo-como-artefacto-oci">El modelo como artefacto OCI&lt;/h2>
&lt;p>La respuesta pragmática es aburrida, y por eso funciona: &lt;strong>guarda el modelo en el mismo registro donde ya guardas los contenedores&lt;/strong>. Ahí ya tienes control de acceso, replicación entre sedes, escaneo, cuotas, auditoría y un equipo que sabe operarlo.&lt;/p>
&lt;h3 id="qué-hizo-posible-oci-11">Qué hizo posible OCI 1.1&lt;/h3>
&lt;p>Durante años, meter cosas que no fueran imágenes de contenedor en un registro OCI era un apaño. Las especificaciones &lt;strong>OCI Image Spec 1.1 y Distribution Spec 1.1&lt;/strong>, publicadas el 13 de marzo de 2024, lo convirtieron en un caso de uso de primera clase con tres piezas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>artifactType&lt;/code>&lt;/strong> en el manifest: declara explícitamente &amp;ldquo;esto no es una imagen ejecutable, es un artefacto de tipo X&amp;rdquo;. Antes se codificaba abusando del &lt;code>mediaType&lt;/code> del config, que es por lo que todavía hoy hay registros que muestran un modelo como una imagen rota.&lt;/li>
&lt;li>&lt;strong>&lt;code>subject&lt;/code>&lt;/strong>: un manifest puede declarar que &lt;em>se refiere a&lt;/em> otro manifest. Es lo que permite colgar una firma, un SBOM o una atestación de un modelo sin modificarlo y sin cambiar su digest, que es el punto.&lt;/li>
&lt;li>&lt;strong>Referrers API&lt;/strong> (&lt;code>GET /v2/&amp;lt;repo&amp;gt;/referrers/&amp;lt;digest&amp;gt;&lt;/code>): la consulta inversa, &amp;ldquo;dame todo lo que apunta a este digest&amp;rdquo;, con &lt;em>fallback&lt;/em> por tag para registros que aún no la implementan.&lt;/li>
&lt;/ul>
&lt;p>Sobre estas tres piezas construye el artículo 3/4 la &lt;a href="https://blog.lo0.es/posts/firma-procedencia-aibom-cadena-suministro-modelo/">firma, procedencia y AIBOM&lt;/a>. Sin ellas, verificar un modelo obliga a inventarse una convención propia.&lt;/p>
&lt;h3 id="oras-el-cliente-genérico">ORAS: el cliente genérico&lt;/h3>
&lt;p>&lt;strong>ORAS&lt;/strong> (&lt;em>OCI Registry As Storage&lt;/em>) es la navaja suiza para meter y sacar artefactos arbitrarios de un registro OCI. La versión &lt;strong>v1.3.0&lt;/strong> se publicó el 6 de octubre de 2025, conforme a distribution-spec v1.1.1, y añadió tres cosas relevantes para modelos: &lt;code>backup&lt;/code>/&lt;code>restore&lt;/code> a directorio o tarball (clave para air-gap), gestión de índices multi-plataforma y &lt;code>--format&lt;/code> para salida estructurada.&lt;/p>
&lt;p>Un dato para calibrar madurez: &lt;strong>ORAS entró en el sandbox de CNCF el 13 de julio de 2021 y a fecha de este artículo sigue en sandbox&lt;/strong>. Cinco años en el nivel de entrada no invalida la herramienta —es estable y la referencia de facto— pero dice algo sobre la gobernanza del proyecto que conviene sopesar antes de convertirla en dependencia crítica sin plan B.&lt;/p>
&lt;p>Empujar un modelo con ORAS, con tipos de contenido explícitos:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">oras push registro.interno/modelos/llama-3.3-70b:2026.07.1 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --artifact-type application/vnd.cncf.model.manifest.v1+json &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --annotation &lt;span class="s2">&amp;#34;org.opencontainers.image.created=2026-07-20T09:00:00Z&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --annotation &lt;span class="s2">&amp;#34;es.ejemplo.modelo.precision=bf16&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --annotation &lt;span class="s2">&amp;#34;es.ejemplo.modelo.origen=hf:meta-llama/Llama-3.3-70B-Instruct&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> config.json:application/vnd.cncf.model.weight.config.v1.raw &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> tokenizer.json:application/vnd.cncf.model.weight.config.v1.raw &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> model-00001-of-00030.safetensors:application/vnd.cncf.model.weight.v1.raw &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> model-00002-of-00030.safetensors:application/vnd.cncf.model.weight.v1.raw
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Recuperarlo, con cache local de blobs para no re-descargar lo ya presente&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">ORAS_CACHE&lt;/span>&lt;span class="o">=&lt;/span>/var/cache/oras
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">oras pull registro.interno/modelos/llama-3.3-70b:2026.07.1 --output /srv/modelos/llama-70b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">oras manifest fetch registro.interno/modelos/llama-3.3-70b:2026.07.1 --descriptor
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="modelpack-la-convención-que-faltaba">ModelPack: la convención que faltaba&lt;/h3>
&lt;p>ORAS te deja inventarte los mediaTypes, y ese es exactamente el problema: cada organización se inventa los suyos y nada interopera. &lt;strong>ModelPack&lt;/strong> es el intento de estandarizarlos, aceptado en el &lt;strong>sandbox de CNCF en junio de 2025&lt;/strong>. Define, sobre OCI Image Manifest 1.1:&lt;/p>
&lt;ul>
&lt;li>&lt;code>artifactType&lt;/code>: &lt;code>application/vnd.cncf.model.manifest.v1+json&lt;/code>&lt;/li>
&lt;li>config: &lt;code>application/vnd.cncf.model.config.v1+json&lt;/code>&lt;/li>
&lt;li>capas de pesos: &lt;code>application/vnd.cncf.model.weight.v1.raw&lt;/code> (y variantes &lt;code>.tar&lt;/code>, &lt;code>.tar+gzip&lt;/code>, &lt;code>.tar+zstd&lt;/code>)&lt;/li>
&lt;li>capas de configuración de pesos, documentación, código y dataset con el mismo patrón&lt;/li>
&lt;/ul>
&lt;p>Su CLI de referencia es &lt;strong>modctl&lt;/strong>, que trabaja con un &lt;code>Modelfile&lt;/code> declarativo:&lt;/p>
&lt;pre tabindex="0">&lt;code>NAME llama-3.3-70b-instruct
ARCH transformer
FAMILY llama
FORMAT safetensors
PARAMSIZE 70
PRECISION bf16
CONFIG config.json
CONFIG tokenizer.json
MODEL *.safetensors
DOC *.md
&lt;/code>&lt;/pre>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">modctl build -t registro.interno/modelos/llama-3.3-70b:2026.07.1 -f Modelfile .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">modctl push registro.interno/modelos/llama-3.3-70b:2026.07.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">modctl pull registro.interno/modelos/llama-3.3-70b:2026.07.1 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --extract-dir /srv/modelos/llama-70b --extract-from-remote
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>La alternativa con más recorrido en usuarios es &lt;strong>KitOps&lt;/strong> (&lt;em>ModelKit&lt;/em>), con ergonomía tipo Docker e integración nativa con Hugging Face. A fecha de este artículo &lt;strong>no hay convergencia&lt;/strong>: ModelPack apunta al caso empresarial y a la integración con runtimes y registros; KitOps y Docker Model Runner, a la ergonomía de desarrollador. Elegir uno hoy es apostar; lo que no es apuesta es usar &lt;code>artifactType&lt;/code> y anotaciones explícitas en vez de convenciones caseras.&lt;/p>
&lt;h3 id="cómo-se-trocea-un-modelo-de-140-gb">Cómo se trocea un modelo de 140 GB&lt;/h3>
&lt;p>Un modelo grande no es &amp;ldquo;un fichero&amp;rdquo;: son 20-40 shards de safetensors más media docena de ficheros pequeños de configuración. La estrategia de capas importa:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Una capa por shard, sin comprimir.&lt;/strong> Los pesos en &lt;code>bf16&lt;/code> o &lt;code>fp8&lt;/code> son incompresibles en la práctica: gzip sobre safetensors gasta CPU para ahorrar un 2-3 %, y obliga a descomprimir en el nodo, añadiendo una pasada completa por CPU y disco en el camino crítico. Usa &lt;code>.raw&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Los ficheros pequeños, en su propia capa.&lt;/strong> &lt;code>config.json&lt;/code>, &lt;code>tokenizer.json&lt;/code> y las plantillas cambian mucho más a menudo que los pesos. Si van en la misma capa que un shard de 5 GB, cada corrección de tokenizer invalida 5 GB de caché en todos los nodos.&lt;/li>
&lt;li>&lt;strong>Tamaño de capa entre 2 y 8 GB.&lt;/strong> Suficientemente grande para no pagar overhead por blob, suficientemente pequeño para paralelizar y para que un fallo de red no obligue a reintentar 40 GB.&lt;/li>
&lt;/ul>
&lt;p>Y lo que duele: la subida usa &lt;em>chunked upload&lt;/em> del distribution-spec, con interoperabilidad históricamente irregular entre implementaciones, así que empujar 140 GB es una operación de decenas de minutos que exige enlace decente y reintentos; la deduplicación es por digest de capa, de modo que dos versiones que difieren en un shard comparten el resto &lt;strong>solo si esos blobs son byte a byte idénticos&lt;/strong> (cualquier reempaquetado que altere metadatos la rompe); y los valores por defecto de los proxies inversos —timeouts, tamaño máximo de cuerpo, vida de la sesión de subida— están calibrados para imágenes de 1 GB, no para blobs de 8 GB.&lt;/p>
&lt;h2 id="harbor-como-depósito-on-premise">Harbor como depósito on-premise&lt;/h2>
&lt;p>&lt;strong>Harbor&lt;/strong> es el registro OSS de referencia para on-premise: proyecto &lt;strong>graduado en CNCF desde junio de 2020&lt;/strong>, con un ciclo de releases sano —la serie &lt;strong>2.15 salió el 20 de marzo de 2026&lt;/strong> y el parche &lt;strong>2.15.2 el 2 de julio de 2026&lt;/strong>—, y soporte de las tres últimas minor durante unos nueve meses cada una. Lo relevante para modelos:&lt;/p>
&lt;p>&lt;strong>Replicación entre sedes.&lt;/strong> Reglas push o pull, filtradas por repositorio, tag y tipo de recurso, con disparo manual, programado o por evento. Para modelos, programada y en ventana, con &lt;code>label&lt;/code> que marque qué versiones se replican (ver trampas).&lt;/p>
&lt;p>&lt;strong>Proxy cache.&lt;/strong> Aquí hay que ser preciso, porque circula mucha desinformación: el proxy cache de Harbor funciona &lt;strong>contra registros OCI&lt;/strong> —Harbor, Docker Hub, registro &lt;code>distribution&lt;/code>, ECR, ACR, GCR, Quay y GHCR—, y &lt;strong>no contra Hugging Face&lt;/strong>, que no expone una API de distribución OCI. Si la idea era &amp;ldquo;pongo Harbor delante de Hugging Face y ya&amp;rdquo;, no funciona así. Las opciones reales son (a) una tarea de ingesta que baja de Hugging Face en un entorno de staging y empuja a Harbor como artefacto OCI —el patrón recomendable, porque introduce un punto de control explícito— o (b) un acelerador P2P con soporte nativo de repositorios de modelos, como Dragonfly. Lo que sí resuelve el proxy cache es el espejo de las imágenes base de runtime (vLLM, TGI, TEI), que no es poco: crea por defecto una retención de 7 días y el proyecto es de solo lectura.&lt;/p>
&lt;p>&lt;strong>Cuotas por proyecto.&lt;/strong> En bytes, por UI o por API:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -u admin:REDACTED -X PUT &lt;span class="s2">&amp;#34;https://registro.interno/api/v2.0/quotas/7&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -H &lt;span class="s2">&amp;#34;Content-Type: application/json&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="s1">&amp;#39;{&amp;#34;hard&amp;#34;: {&amp;#34;storage&amp;#34;: 21990232555520}}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esos 21990232555520 bytes son 20 TiB, dimensionamiento &lt;em>ajustado&lt;/em> —no generoso— para una docena de modelos con tres versiones vivas cada uno. Dos avisos: la cuota se comprueba al llegar el manifest, es decir &lt;strong>después&lt;/strong> de subir los blobs, así que un push que la excede ya ha consumido disco y red; y la contabilidad no siempre refleja la deduplicación de blobs compartidos entre proyectos.&lt;/p>
&lt;p>&lt;strong>Tags inmutables.&lt;/strong> Reglas por proyecto que impiden sobrescribir un tag que cumpla un patrón. Para modelos no es opcional: una regla que haga inmutable todo &lt;code>modelos/**&lt;/code> con patrón de tag &lt;code>2*&lt;/code> (versiones con fecha) elimina de raíz la clase entera de incidentes de &amp;ldquo;el tag cambió debajo&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>Retención y GC.&lt;/strong> Reglas de retención por proyecto (últimas N versiones, o las de los últimos N días) que &lt;em>marcan&lt;/em> artefactos para borrado, y un &lt;em>garbage collector&lt;/em> separado que libera los blobs no referenciados. Son dos cosas distintas, y &lt;strong>borrar en la UI no libera disco&lt;/strong>: el expurgo se ejecuta aparte. Ejecutar GC con un push concurrente en marcha es la receta clásica para borrar blobs que estaban a punto de ser referenciados.&lt;/p>
&lt;p>&lt;strong>Escaneo.&lt;/strong> Trivy integrado, con una honestidad necesaria: escanear un artefacto de pesos no detecta nada relevante sobre el modelo. Sirve para las imágenes de runtime, que sí ejecutan código. Detectar formatos peligrosos —un &lt;code>pickle&lt;/code> de PyTorch con código arbitrario en lugar de safetensors— requiere herramientas específicas, y es materia del artículo 3/4.&lt;/p>
&lt;h2 id="model-registries-de-verdad">Model registries de verdad&lt;/h2>
&lt;p>Ahora el catálogo, con la afirmación incómoda por delante: &lt;strong>un model registry no resuelve tu distribución&lt;/strong>.&lt;/p>
&lt;h3 id="mlflow">MLflow&lt;/h3>
&lt;p>&lt;strong>MLflow&lt;/strong> (LF AI &amp;amp; Data) es el estándar de facto del catálogo, con la serie 3.x madura —la &lt;strong>3.13.0 se publicó en junio de 2026&lt;/strong>, con foco en RBAC, UI de administración y retención de trazas—. Su modelo de datos: &lt;em>registered model&lt;/em> (nombre lógico) → &lt;em>model version&lt;/em> (entero incremental) → &lt;em>aliases&lt;/em> y &lt;em>tags&lt;/em>.&lt;/p>
&lt;p>El cambio conceptual importante: &lt;strong>las &lt;em>stages&lt;/em> (&lt;code>Staging&lt;/code>, &lt;code>Production&lt;/code>, &lt;code>Archived&lt;/code>) están deprecadas desde MLflow 2.9.0&lt;/strong> y se sustituyen por &lt;em>aliases&lt;/em> y &lt;em>tags&lt;/em>, por la inflexibilidad de un enumerado fijo para expresar flujos reales de MLOps. Un alias es un puntero mutable con nombre:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">mlflow&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MlflowClient&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MlflowClient&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_registered_model_alias&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;llama-3.3-70b-soberano&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;champion&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_model_version_tag&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;llama-3.3-70b-soberano&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;7&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;estado_validacion&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;aprobado_ens_medio&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_model_version_tag&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;llama-3.3-70b-soberano&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;7&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;oci_digest&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;sha256:9f2c...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El consumo se hace por URI de alias: &lt;code>models:/llama-3.3-70b-soberano@champion&lt;/code>.&lt;/p>
&lt;p>Lo que MLflow aporta: linaje al &lt;em>run&lt;/em> que produjo el modelo, métricas de evaluación asociadas a la versión, un lenguaje de promoción compartido y una API sobre la que colgar aprobaciones. Lo que &lt;strong>no&lt;/strong> aporta: no es un CDN, no gestiona la distribución a nodos, y su &lt;em>artifact store&lt;/em> (S3/MinIO/NFS) no da inmutabilidad por digest, ni Referrers, ni replicación entre sedes. El patrón sano es el del snippet: &lt;strong>el catálogo guarda el digest OCI como tag de la versión&lt;/strong> y apunta al depósito, en lugar de intentar ser el depósito.&lt;/p>
&lt;h3 id="kubeflow-hub-antes-model-registry">Kubeflow Hub (antes Model Registry)&lt;/h3>
&lt;p>El &lt;strong>Kubeflow Model Registry&lt;/strong> se renombró a &lt;strong>Kubeflow Hub&lt;/strong> y unificó dos funciones: el registro propio y un &lt;em>Model Catalog&lt;/em> federado que descubre modelos externos (YAML, Hugging Face), con linaje entre datos, código y modelos, metadatos y promoción por estado.&lt;/p>
&lt;p>La valoración honesta: a fecha de este artículo el componente &lt;strong>sigue en la serie 0.3.x y con estado Alpha&lt;/strong> en la política de versionado de Kubeflow, con soporte limitado. Interesante si ya explotas Kubeflow completo y quieres una sola consola; &lt;strong>no&lt;/strong> es todavía la pieza sobre la que montar la gobernanza de modelos de una plataforma soberana en producción. El renombrado en pleno 0.3.x es, además, señal de que la API sigue moviéndose.&lt;/p>
&lt;h3 id="tabla-comparativa-por-función">Tabla comparativa por función&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Función&lt;/th>
&lt;th>Registro OCI (Harbor + ORAS/ModelPack)&lt;/th>
&lt;th>MLflow Registry&lt;/th>
&lt;th>Kubeflow Hub&lt;/th>
&lt;th>lakeFS / DVC&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Guarda los bytes&lt;/strong>&lt;/td>
&lt;td>Sí, por digest, inmutable&lt;/td>
&lt;td>No (puntero a artifact store)&lt;/td>
&lt;td>No (puntero)&lt;/td>
&lt;td>Sí (datos y versiones)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Linaje a datos/código&lt;/strong>&lt;/td>
&lt;td>Solo por anotaciones&lt;/td>
&lt;td>Sí, al &lt;em>run&lt;/em>&lt;/td>
&lt;td>Sí, explícito&lt;/td>
&lt;td>Sí, para datasets&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Metadatos ricos&lt;/strong>&lt;/td>
&lt;td>Anotaciones y config JSON&lt;/td>
&lt;td>Sí, tipado&lt;/td>
&lt;td>Sí, tipado&lt;/td>
&lt;td>Parcial&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Promoción / aprobación&lt;/strong>&lt;/td>
&lt;td>Por convención (tags, labels)&lt;/td>
&lt;td>Aliases y tags&lt;/td>
&lt;td>Estados&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Firma y atestaciones&lt;/strong>&lt;/td>
&lt;td>Sí, nativo (&lt;code>subject&lt;/code> + Referrers)&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Replicación entre sedes&lt;/strong>&lt;/td>
&lt;td>Sí, nativa&lt;/td>
&lt;td>No&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Parcial&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>API estándar&lt;/strong>&lt;/td>
&lt;td>OCI Distribution 1.1&lt;/td>
&lt;td>REST propia&lt;/td>
&lt;td>REST propia&lt;/td>
&lt;td>S3/Git-like&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Air-gap&lt;/strong>&lt;/td>
&lt;td>Sí (&lt;code>oras backup&lt;/code>/&lt;code>restore&lt;/code>, &lt;code>skopeo&lt;/code>)&lt;/td>
&lt;td>Manual&lt;/td>
&lt;td>Manual&lt;/td>
&lt;td>Manual&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Mantenedor&lt;/strong>&lt;/td>
&lt;td>CNCF (Harbor graduado; ORAS y ModelPack sandbox)&lt;/td>
&lt;td>LF AI &amp;amp; Data&lt;/td>
&lt;td>Kubeflow (CNCF)&lt;/td>
&lt;td>OSS comercial&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Madurez real&lt;/strong>&lt;/td>
&lt;td>Alta (registro), media (convención de modelos)&lt;/td>
&lt;td>Alta&lt;/td>
&lt;td>Alpha&lt;/td>
&lt;td>Alta&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La tesis de la tabla: &lt;strong>las columnas son complementarias, no alternativas&lt;/strong>. El registro OCI es el depósito; MLflow, el catálogo; lakeFS o DVC versionan el dataset que da origen a todo, como se detalla en &lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">versionado de datos con DVC y lakeFS&lt;/a>.&lt;/p>
&lt;h2 id="la-ruta-caliente-del-registro-a-la-hbm">La ruta caliente: del registro a la HBM&lt;/h2>
&lt;div class="diagram" style="max-width:860px;margin:1rem auto;">
&lt;svg viewBox="0 0 860 340" role="img" aria-label="Los tres planos de la cadena de distribución de un modelo y la ruta caliente desde el registro OCI hasta la memoria HBM de la GPU" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.4}.dsh{fill:none;stroke:currentColor;stroke-width:1.4;stroke-dasharray:5 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.4;marker-end:url(#am2)}&lt;/style>
&lt;defs>&lt;marker id="am2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;text x="430" y="20" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">Tres planos y la ruta caliente hasta la GPU&lt;/text>
&lt;rect class="dsh" x="18" y="38" width="200" height="86" rx="5"/>
&lt;text x="118" y="58" text-anchor="middle" class="tl">Plano de catálogo&lt;/text>
&lt;text x="118" y="76" text-anchor="middle" class="ts">MLflow / Kubeflow Hub&lt;/text>
&lt;text x="118" y="92" text-anchor="middle" class="ts">version, alias, linaje&lt;/text>
&lt;text x="118" y="112" text-anchor="middle" class="ts">guarda el DIGEST, no los bytes&lt;/text>
&lt;path class="ar" d="M218,81 L268,81"/>
&lt;rect class="bx" x="268" y="38" width="210" height="86" rx="5"/>
&lt;text x="373" y="58" text-anchor="middle" class="tl">Plano de depósito&lt;/text>
&lt;text x="373" y="76" text-anchor="middle" class="ts">Harbor + ORAS / ModelPack&lt;/text>
&lt;text x="373" y="92" text-anchor="middle" class="ts">blobs por sha256, inmutables&lt;/text>
&lt;text x="373" y="112" text-anchor="middle" class="ts">cuotas, retencion, replicacion&lt;/text>
&lt;path class="ar" d="M478,81 L528,81"/>
&lt;rect class="bx" x="528" y="38" width="200" height="86" rx="5"/>
&lt;text x="628" y="58" text-anchor="middle" class="tl">Plano de despliegue&lt;/text>
&lt;text x="628" y="76" text-anchor="middle" class="ts">image volume / modelcar&lt;/text>
&lt;text x="628" y="92" text-anchor="middle" class="ts">montado en /mnt/models&lt;/text>
&lt;text x="628" y="112" text-anchor="middle" class="ts">efimero o cache NVMe&lt;/text>
&lt;text x="40" y="162" class="tl">Ruta caliente y ancho de banda por tramo (modelo de 140 GB)&lt;/text>
&lt;rect class="bx" x="40" y="178" width="150" height="52" rx="5"/>
&lt;text x="115" y="198" text-anchor="middle" class="tl">Registro (red)&lt;/text>
&lt;text x="115" y="216" text-anchor="middle" class="ts">10 Gb/s → aprox. 112 s&lt;/text>
&lt;path class="ar" d="M190,204 L240,204"/>
&lt;rect class="bx" x="240" y="178" width="150" height="52" rx="5"/>
&lt;text x="315" y="198" text-anchor="middle" class="tl">Cache NVMe local&lt;/text>
&lt;text x="315" y="216" text-anchor="middle" class="ts">7 GB/s → aprox. 20 s&lt;/text>
&lt;path class="ar" d="M390,204 L440,204"/>
&lt;rect class="bx" x="440" y="178" width="150" height="52" rx="5"/>
&lt;text x="515" y="198" text-anchor="middle" class="tl">PCIe Gen5 x16&lt;/text>
&lt;text x="515" y="216" text-anchor="middle" class="ts">aprox. 50 GB/s → 3 s&lt;/text>
&lt;path class="ar" d="M590,204 L640,204"/>
&lt;rect class="bx" x="640" y="178" width="180" height="52" rx="5"/>
&lt;text x="730" y="198" text-anchor="middle" class="tl">HBM (H100 SXM)&lt;/text>
&lt;text x="730" y="216" text-anchor="middle" class="ts">TB/s: nunca es el cuello&lt;/text>
&lt;text x="40" y="262" class="ts">El primer tramo domina por uno o dos ordenes de magnitud: sin cache local, todo lo demas es ruido.&lt;/text>
&lt;text x="40" y="280" class="ts">Aceleradores de carga (streamer, tensorizer) atacan el tramo 2-3; P2P y lazy loading atacan el tramo 1.&lt;/text>
&lt;text x="40" y="298" class="ts">Un tag mutable en el plano de deposito invalida las garantias de los otros dos planos.&lt;/text>
&lt;text x="40" y="316" class="ts">Firma y atestaciones cuelgan del digest via subject + Referrers API (articulo 3/4).&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="image-volumes-el-cambio-del-año">Image volumes: el cambio del año&lt;/h3>
&lt;p>El &lt;strong>image volume source&lt;/strong> (KEP-4639) monta una imagen o artefacto OCI directamente como volumen de solo lectura en un pod: sin &lt;code>initContainer&lt;/code>, sin copiar, sin PVC. Su recorrido: alfa en &lt;strong>v1.31&lt;/strong> (agosto de 2024), beta en &lt;strong>v1.33&lt;/strong> (abril de 2025) con soporte de &lt;code>subPath&lt;/code>, beta por defecto en &lt;strong>v1.35&lt;/strong> y &lt;strong>GA en v1.36&lt;/strong>, publicada el 22 de abril de 2026.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Pod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-modelo-oci&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">registro.interno/runtime/vllm:v0.11.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;--model&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/mnt/models&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--served-model-name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;llama-3.3-70b&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pesos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/mnt/models&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readOnly&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nvidia.com/gpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;4&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pesos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">reference&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">registro.interno/modelos/llama-3.3-70b@sha256:9f2c...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pullPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">IfNotPresent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Dos detalles que cambian la operativa: la referencia es &lt;strong>por digest&lt;/strong>, con lo que la inmutabilidad deja de depender de la disciplina de nadie; y &lt;code>pullPolicy: IfNotPresent&lt;/code> hace que el runtime del nodo cachee los blobs en su almacén local, de modo que la segunda réplica en ese nodo arranca sin tocar la red. Es, literalmente, la caché coordinada que faltaba en el anti-patrón.&lt;/p>
&lt;h3 id="kserve-storageuri-y-modelcars">KServe: &lt;code>storageUri&lt;/code> y modelcars&lt;/h3>
&lt;p>En KServe hay dos caminos: el clásico, &lt;code>storageUri&lt;/code> con un &lt;em>storage initializer&lt;/em> que copia desde S3, PVC, HTTP o GCS a un &lt;code>emptyDir&lt;/code>; y el &lt;strong>modelcar&lt;/strong>, con esquema &lt;code>oci://&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">serving.kserve.io/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">InferenceService&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama-70b-soberano&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">predictor&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">modelFormat&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">huggingface&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storageUri&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">oci://registro.interno/modelos/llama-3.3-70b:2026.07.1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nvidia.com/gpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;4&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El mecanismo interno del modelcar conviene entenderlo antes de depender de él: KServe pone &lt;code>shareProcessNamespace: true&lt;/code> en el pod, arranca un contenedor lateral con la imagen del modelo y crea un enlace simbólico desde &lt;code>/mnt/models&lt;/code> al &lt;em>root filesystem&lt;/em> de ese proceso a través de &lt;code>/proc&lt;/code>. El runtime lee los pesos &lt;strong>sin copiarlos&lt;/strong>, que es el ahorro que se busca. Dos advertencias: &lt;strong>no está activado por defecto&lt;/strong> (hay que habilitar &lt;code>enableModelcar&lt;/code> en el ConfigMap &lt;code>inferenceservice-config&lt;/code>) y el tag &lt;code>latest&lt;/code> fuerza &lt;code>pullPolicy: Always&lt;/code>, anulando la caché. Con image volumes ya en GA, el modelcar queda como el camino para clústeres que aún no están en 1.36.&lt;/p>
&lt;h3 id="cargar-rápido-dónde-ataca-cada-técnica">Cargar rápido: dónde ataca cada técnica&lt;/h3>
&lt;p>Conviene separar dos problemas que se confunden todo el rato: &lt;strong>traer los bytes al nodo&lt;/strong> (red) y &lt;strong>meterlos en la HBM&lt;/strong> (disco → CPU → PCIe → GPU).&lt;/p>
&lt;p>Para el primero, &lt;strong>Dragonfly&lt;/strong> es la pieza madura: &lt;strong>graduó en CNCF el 14 de enero de 2026&lt;/strong>, distribuye pesos a escala de cientos de terabytes a cientos de nodos &amp;ldquo;en minutos&amp;rdquo;, reduce el ancho de banda consumido en el origen hasta un 90 % y aprovecha entre el 70 % y el 80 % del de cada nodo. Su &lt;strong>v2.5.0 (30 de junio de 2026)&lt;/strong> añade descarga directa de repositorios de modelos (&lt;code>dfget hf://...&lt;/code>), aceleración P2P de Git LFS y un &lt;code>dragonfly-injector&lt;/code> que inyecta capacidad P2P vía webhook sin reconstruir imágenes. Su subproyecto &lt;strong>Nydus&lt;/strong> aporta el formato de imagen con carga perezosa. Ojo con una expectativa mal puesta muy común: para pesos de modelo la carga perezosa ayuda poco, porque un motor de inferencia lee &lt;em>todos&lt;/em> los pesos casi de inmediato; el beneficio real es para runtimes gordos y para modelos de los que solo se lee una parte.&lt;/p>
&lt;p>Para el segundo, los números publicados por el &lt;strong>NVIDIA Run:ai Model Streamer&lt;/strong> sobre Llama-3-8B (15 GB, safetensors, nodo con una A10G) son la referencia más limpia disponible:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Origen&lt;/th>
&lt;th>Safetensors loader&lt;/th>
&lt;th>Run:ai Model Streamer&lt;/th>
&lt;th>Tensorizer&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>SSD gp3&lt;/td>
&lt;td>47,99 s&lt;/td>
&lt;td>14,34 s (concurrencia 16)&lt;/td>
&lt;td>16,11 s (16 workers)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>SSD io2&lt;/td>
&lt;td>47,0 s&lt;/td>
&lt;td>7,53 s (concurrencia 8)&lt;/td>
&lt;td>10,36 s (8 workers)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Object storage S3&lt;/td>
&lt;td>no soportado&lt;/td>
&lt;td>4,88 s (concurrencia 32)&lt;/td>
&lt;td>37,36 s (16 workers)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Y el tiempo total hasta que vLLM está listo para servir: 66,13 s con el loader estándar sobre gp3 frente a 35,08 s con el streamer. Dos lecturas críticas. Primera: son benchmarks &lt;strong>del propio proyecto&lt;/strong>, no medición independiente, sobre un modelo de 15 GB en una GPU modesta; extrapolar linealmente a 140 GB en 4×H100 es un salto que nadie ha publicado. Segunda, y más importante: la ganancia viene de &lt;strong>saturar el medio de almacenamiento con concurrencia&lt;/strong>, no de magia. Si el cuello de botella es un NFS a 1 Gb/s, ningún loader lo arregla. El detalle de este tramo está en &lt;a href="https://blog.lo0.es/posts/acelerar-cold-start-carga-modelos-tensorizer/">acelerar el cold start con tensorizer&lt;/a> y en &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">del disco a la HBM&lt;/a>.&lt;/p>
&lt;p>Como muestra el diagrama, en un nodo de referencia 4×H100 SXM 80 GB con NVLink &lt;strong>cada tramo es entre 5 y 10 veces más rápido que el anterior&lt;/strong>. La conclusión es aburrida y contundente: el dinero está en tener el modelo ya en el NVMe del nodo antes de que arranque el pod.&lt;/p>
&lt;h2 id="adapters-lora-cuando-el-artefacto-pesa-megabytes">Adapters LoRA: cuando el artefacto pesa megabytes&lt;/h2>
&lt;p>Todo lo anterior asume artefactos de decenas o cientos de gigabytes. Un adapter LoRA rompe ese supuesto: para un base de 70B, un adapter de rango 16 sobre las proyecciones de atención pesa decenas o pocos cientos de megabytes, &lt;strong>tres órdenes de magnitud menos&lt;/strong>. Y eso cambia la estrategia entera.&lt;/p>
&lt;p>Con artefactos así, la caché local y el P2P dejan de importar: la descarga es instantánea a cualquier ancho de banda razonable. Lo que pasa a importar es &lt;strong>la cardinalidad y la frecuencia de cambio&lt;/strong>. Una plataforma multi-tenant como la descrita en &lt;a href="https://blog.lo0.es/posts/multi-lora-serving-fundamentos/">multi-LoRA serving&lt;/a> puede tener cientos de adapters, cada uno con su ciclo de vida, propietario y política de acceso, sobre un puñado de bases. Consecuencias prácticas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>El adapter es un artefacto OCI de pleno derecho&lt;/strong>, con repositorio propio y tag inmutable. Pero el registro pasa a tener miles de repositorios pequeños en lugar de decenas grandes, lo que estresa metadatos, listados y RBAC granular, no disco.&lt;/li>
&lt;li>&lt;strong>Debe declarar su base.&lt;/strong> Un adapter sin referencia inmutable al digest del modelo base sobre el que se entrenó es una bomba de relojería: aplicado sobre otra versión, degrada la calidad en silencio. Es el caso de uso natural de &lt;code>subject&lt;/code>: el adapter &lt;em>refiere&lt;/em> al base, y la Referrers API responde &amp;ldquo;qué adapters existen para este digest&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>La carga es dinámica, no de arranque.&lt;/strong> vLLM permite cargar adapters en caliente con &lt;code>VLLM_ALLOW_RUNTIME_LORA_UPDATING&lt;/code> y el endpoint &lt;code>/v1/load_lora_adapter&lt;/code>, o de forma declarativa con los &lt;em>LoRA resolver plugins&lt;/em> (&lt;code>VLLM_PLUGIN_LORA_RESOLVERS&lt;/code>), que resuelven un adapter por nombre desde un directorio. El patrón limpio en Kubernetes es montar un image volume con el adapter y dejar que el resolver de sistema de ficheros lo descubra.&lt;/li>
&lt;li>&lt;strong>El versionado pasa a ser el problema dominante.&lt;/strong> Con 300 adapters vivos, &amp;ldquo;¿qué adapter, sobre qué base, entrenado con qué dataset, sirve al tenant X?&amp;rdquo; solo tiene respuesta si el catálogo existe. Aquí sí gana un model registry frente a la convención sobre tags.&lt;/li>
&lt;/ul>
&lt;h2 id="air-gap-y-soberanía-promoción-sin-perder-trazabilidad">Air-gap y soberanía: promoción sin perder trazabilidad&lt;/h2>
&lt;p>El caso soberano de verdad: un entorno conectado donde se ingiere y se valida, y un entorno aislado donde se sirve, sin ruta de red entre ambos. La promoción cruza un &lt;em>diodo de datos&lt;/em> o un soporte físico, y &lt;strong>la trazabilidad tiene que sobrevivir al viaje&lt;/strong>. Es la reproducción certificada del archivo: no basta con copiar el documento, hay que copiar su ficha y el sello que acredita que la copia es fiel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. En el entorno conectado: exportar el artefacto con TODO lo que cuelga de el&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">oras backup &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --output /transfer/llama-3.3-70b-2026.07.1.tar &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --include-referrers &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> registro-dmz.interno/modelos/llama-3.3-70b:2026.07.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. Verificar el digest exacto antes de cruzar&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">oras manifest fetch --descriptor &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> registro-dmz.interno/modelos/llama-3.3-70b:2026.07.1 &amp;gt; /transfer/descriptor.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sha256sum /transfer/llama-3.3-70b-2026.07.1.tar &amp;gt; /transfer/SHA256SUMS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. Cruce fisico o por diodo de datos&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. En el entorno aislado: restaurar preservando digests y referrers&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">oras restore &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --input /transfer/llama-3.3-70b-2026.07.1.tar &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> registro-aislado.interno/modelos/llama-3.3-70b
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El flag &lt;strong>&lt;code>--include-referrers&lt;/code> es el que hace que esto sirva de algo&lt;/strong>: sin él cruzan los pesos, pero se quedan atrás la firma, el AIBOM y las atestaciones, y el entorno aislado recibe un modelo indistinguible de uno descargado a mano. &lt;code>oras backup&lt;/code> está marcado como experimental en v1.3.0, lo que hay que asumir explícitamente antes de convertirlo en procedimiento; la alternativa consolidada para la copia pura es &lt;code>skopeo copy&lt;/code> con formato &lt;code>oci-archive&lt;/code>, a costa de gestionar los referrers aparte.&lt;/p>
&lt;p>Qué debe viajar pegado al artefacto para que el artículo 3/4 pueda verificarlo: el &lt;strong>digest&lt;/strong> del artefacto y de sus blobs; la &lt;strong>procedencia del origen&lt;/strong> (repositorio y revisión concreta, no el nombre del modelo, más quién lo ingirió y cuándo); la &lt;strong>identidad del proceso que empaquetó&lt;/strong> (pipeline, commit, ejecución), materia prima de una atestación SLSA; las &lt;strong>referencias cruzadas&lt;/strong> al dataset de evaluación y al modelo base si es un derivado; y el &lt;strong>estado de promoción con su aprobador&lt;/strong>, que en un entorno aislado no se puede consultar contra el MLflow del entorno conectado. Todo ello cabe en anotaciones OCI y en artefactos referidos vía &lt;code>subject&lt;/code>. La regla: &lt;strong>el entorno aislado debe poder verificar sin llamar a nadie&lt;/strong>; si la verificación necesita una consulta al exterior, no es air-gap.&lt;/p>
&lt;h2 id="mapa-de-decisión">Mapa de decisión&lt;/h2>
&lt;p>&lt;strong>Basta con un registro OCI y una convención&lt;/strong> cuando tienes menos de una veintena de modelos, un solo equipo decide qué se promociona, el linaje al entrenamiento no es requisito regulatorio (típico si consumes modelos de terceros) y ya operas Harbor. Aquí un model registry añade una consola más que mantener y ninguna respuesta que no dieran ya los tags inmutables y las anotaciones. Es el caso mayoritario en inferencia pura.&lt;/p>
&lt;p>&lt;strong>Hace falta un model registry&lt;/strong> si entrenas o haces fine-tuning y necesitas linaje auditable de modelo a dataset y a código —el ciclo de &lt;a href="https://blog.lo0.es/posts/fine-tuning-continuo-produccion/">fine-tuning continuo&lt;/a>—; si varios equipos compiten por promocionar versiones y hace falta un flujo de aprobación explícito; si tienes que responder a un auditor de &lt;a href="https://blog.lo0.es/posts/iso-42001-aims-llm-on-premise/">ISO 42001&lt;/a> o del &lt;a href="https://blog.lo0.es/posts/eu-ai-act-mapeo-arquitectura-llm-on-premise/">EU AI Act&lt;/a> sobre qué versión estaba en producción en una fecha; o si gestionas decenas de adapters con propietarios distintos.&lt;/p>
&lt;p>&lt;strong>El registro se vuelve burocracia sin valor&lt;/strong> cuando se registra el modelo &lt;em>después&lt;/em> de desplegarlo (catálogo como acto administrativo, no como puerta); cuando el estado de promoción no está conectado a ningún control técnico y se puede desplegar una versión no aprobada; o cuando conviven un model registry y un registro OCI con dos verdades y ningún digest que las una. Este último es el fallo más común y el más caro: dos sistemas de versionado que divergen en silencio.&lt;/p>
&lt;h2 id="trampas-operativas">Trampas operativas&lt;/h2>
&lt;p>&lt;strong>Tags mutables y &lt;code>latest&lt;/code> en producción.&lt;/strong> No es un consejo de estilo: un tag mutable convierte cualquier auditoría en una conjetura y permite que dos réplicas del mismo &lt;code>Deployment&lt;/code> sirvan bytes distintos. Además, en KServe &lt;code>latest&lt;/code> fuerza &lt;code>pullPolicy: Always&lt;/code> y anula la caché del nodo, así que se paga en cold start lo que se pierde en trazabilidad. Regla: tags inmutables por política de proyecto y despliegue por digest.&lt;/p>
&lt;p>&lt;strong>GC que borra capas referenciadas.&lt;/strong> El expurgo del archivo. El &lt;em>garbage collector&lt;/em> identifica blobs que ningún manifest referencia, y la ventana entre &amp;ldquo;he subido los blobs&amp;rdquo; y &amp;ldquo;he subido el manifest&amp;rdquo; es exactamente donde un GC concurrente puede llevárselos. En Harbor esto se ha manifestado históricamente en &lt;code>_uploads&lt;/code> y en discrepancias entre el espacio que la UI declara liberado y el real. Mitigación: GC en ventana con push bloqueado, y no confiar en la primera pasada.&lt;/p>
&lt;p>&lt;strong>El disco del registro y el coste del versionado.&lt;/strong> Aritmética brutal: 140 GB por versión con tres versiones vivas son 420 GB; doce modelos así, 5 TB; con retención de seis versiones y dos sedes replicadas, 20 TB. Y es almacenamiento de la clase cara si el registro vive sobre bloque replicado. La deduplicación ayuda poco, porque los pesos cambian por completo entre versiones. La política de retención hay que diseñarla &lt;em>antes&lt;/em> de llenar el registro, y hay que separar por proyecto los modelos de las imágenes de runtime, porque sus retenciones no tienen nada que ver.&lt;/p>
&lt;p>&lt;strong>Replicación que satura el enlace intersedes.&lt;/strong> Una regla por evento sobre un repositorio de modelos mueve 140 GB cada vez que alguien empuja una versión: en un enlace de 1 Gb/s compartido, casi veinte minutos a tope en mitad de la jornada. Programar en ventana, limitar ancho de banda, filtrar por label qué versiones se replican y usar P2P dentro de cada sede.&lt;/p>
&lt;p>&lt;strong>Meter el modelo en la imagen del runtime.&lt;/strong> Tentador porque &amp;ldquo;así solo hay un artefacto&amp;rdquo;. El resultado es una imagen de 145 GB que hay que reconstruir y redistribuir cada vez que se parchea una CVE del runtime, con el ciclo de vida de seguridad acoplado al del modelo. Separados: runtime pequeño y parcheable, modelo grande y estable.&lt;/p>
&lt;h2 id="para-una-factoría-de-inferencia">Para una factoría de inferencia&lt;/h2>
&lt;p>Tres cosas accionables para quien explota una factoría de &lt;strong>inferencia&lt;/strong> on-premise, no de entrenamiento.&lt;/p>
&lt;p>&lt;strong>Primera: corta la dependencia de internet en el arranque, esta semana.&lt;/strong> No hace falta un proyecto de plataforma: basta una tarea de ingesta que baje cada modelo una vez, lo empuje a Harbor como artefacto OCI con tag inmutable y anotaciones de procedencia, y cambiar los despliegues a referencia por digest. El &lt;code>HF_TOKEN&lt;/code> desaparece del pod y pasa al pipeline de ingesta, que es donde tiene sentido y donde encaja con el &lt;a href="https://blog.lo0.es/posts/hardening-secretos-stack-llm-soberano/">hardening de secretos&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Segunda: decide conscientemente si necesitas catálogo.&lt;/strong> Si solo consumes modelos de terceros y no entrenas, probablemente no: el registro OCI con convención da el 90 % del valor con el 10 % del coste operativo. Si entrenas, haces fine-tuning o gestionas adapters por tenant, monta MLflow y —esto es lo importante— &lt;strong>guarda el digest OCI como tag de cada versión&lt;/strong>, para que catálogo y depósito no puedan divergir.&lt;/p>
&lt;p>&lt;strong>Tercera: mueve el cuello de botella al sitio correcto.&lt;/strong> Antes de invertir en aceleradores de carga, mide los tres tramos. Si el modelo llega por red en cada arranque, el trabajo es caché local en NVMe, image volumes con &lt;code>pullPolicy: IfNotPresent&lt;/code> y, con muchos nodos, P2P. Solo cuando el modelo ya está en el disco del nodo tiene sentido pelearse con la concurrencia del loader.&lt;/p>
&lt;p>Ya sabes de dónde vienen los bytes y por qué camino llegan. Queda la pregunta que lo sostiene todo y que hasta ahora hemos dado por buena: &lt;strong>¿por qué te fías de esos bytes?&lt;/strong> Un digest garantiza integridad —que nadie los ha cambiado— pero no procedencia: no dice quién los produjo, con qué datos, ni si alguien con autoridad certificó que se podían servir. El &lt;a href="https://blog.lo0.es/posts/firma-procedencia-aibom-cadena-suministro-modelo/">tercer artículo de la serie&lt;/a> construye esa respuesta con Sigstore, SLSA, in-toto y AIBOM, colgada precisamente del &lt;code>subject&lt;/code> y la Referrers API que aquí hemos dejado preparados.&lt;/p>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kserve-open-inference-protocol-plano-control/">Cadena de confianza del modelo (1/4): KServe y el Open Inference Protocol&lt;/a> — el plano de control que consume el &lt;code>storageUri&lt;/code> descrito aquí.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/firma-procedencia-aibom-cadena-suministro-modelo/">Cadena de confianza del modelo (3/4): firma, procedencia y AIBOM&lt;/a> — qué se cuelga del digest vía &lt;code>subject&lt;/code> y Referrers API.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/acelerar-cold-start-carga-modelos-tensorizer/">Acelerar el cold start: carga de modelos con tensorizer&lt;/a> — el tramo disco-HBM en detalle.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM: anatomía del cold start&lt;/a> — dónde se va el tiempo dentro del nodo.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/data-versioning-dvc-lakefs/">Versionado de datos con DVC y lakeFS&lt;/a> — el plano equivalente para datasets, que alimenta el linaje del catálogo.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/catalogo-herramientas-oss-llmops/">Catálogo de herramientas OSS de LLMOps&lt;/a> — dónde encaja cada pieza de este artículo en el stack completo.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/multi-lora-serving-fundamentos/">Multi-LoRA serving: fundamentos&lt;/a> — el caso en que el artefacto pesa megabytes y la estrategia cambia.&lt;/li>
&lt;/ul>
&lt;h2 id="fuentes">Fuentes&lt;/h2>
&lt;ul>
&lt;li>Open Container Initiative, &lt;em>OCI Image and Distribution Specs v1.1 Releases&lt;/em> (13 de marzo de 2024) — &lt;a href="https://opencontainers.org/posts/blog/2024-03-13-image-and-distribution-1-1/">https://opencontainers.org/posts/blog/2024-03-13-image-and-distribution-1-1/&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>Announcing ORAS v1.3.0: Elevating artifact and registry management workflows&lt;/em> (6 de octubre de 2025) — &lt;a href="https://www.cncf.io/blog/2025/10/06/announcing-oras-v1-3-0-elevating-artifact-and-registry-management-workflows/">https://www.cncf.io/blog/2025/10/06/announcing-oras-v1-3-0-elevating-artifact-and-registry-management-workflows/&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>ORAS project page&lt;/em> (sandbox desde el 13 de julio de 2021) — &lt;a href="https://www.cncf.io/projects/oras/">https://www.cncf.io/projects/oras/&lt;/a>&lt;/li>
&lt;li>ORAS, &lt;em>oras backup (experimental)&lt;/em> — &lt;a href="https://oras.land/docs/commands/oras_backup">https://oras.land/docs/commands/oras_backup&lt;/a>&lt;/li>
&lt;li>ModelPack, &lt;em>CNCF ModelPack Specification — model spec (artifactType y mediaTypes)&lt;/em> — &lt;a href="https://github.com/modelpack/model-spec">https://github.com/modelpack/model-spec&lt;/a>&lt;/li>
&lt;li>ModelPack, &lt;em>modctl — getting started (Modelfile y comandos)&lt;/em> — &lt;a href="https://github.com/modelpack/modctl/blob/main/docs/getting-started.md">https://github.com/modelpack/modctl/blob/main/docs/getting-started.md&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>Cloud Native Computing Foundation announces Harbor graduation&lt;/em> (23 de junio de 2020) — &lt;a href="https://www.cncf.io/announcements/2020/06/23/cloud-native-computing-foundation-announces-harbor-graduation/">https://www.cncf.io/announcements/2020/06/23/cloud-native-computing-foundation-announces-harbor-graduation/&lt;/a>&lt;/li>
&lt;li>Harbor, &lt;em>Configure proxy cache&lt;/em> (registros soportados) — &lt;a href="https://goharbor.io/docs/2.6.0/administration/configure-proxy-cache/">https://goharbor.io/docs/2.6.0/administration/configure-proxy-cache/&lt;/a>&lt;/li>
&lt;li>OneUptime, &lt;em>How to configure Harbor project quotas for storage limits&lt;/em> (9 de febrero de 2026) — &lt;a href="https://oneuptime.com/blog/post/2026-02-09-harbor-project-quotas-storage/view">https://oneuptime.com/blog/post/2026-02-09-harbor-project-quotas-storage/view&lt;/a>&lt;/li>
&lt;li>endoflife.date, &lt;em>Harbor releases&lt;/em> (2.15.2 el 2 de julio de 2026; 2.15 el 20 de marzo de 2026) — &lt;a href="https://endoflife.date/harbor">https://endoflife.date/harbor&lt;/a>&lt;/li>
&lt;li>VMware Cloud Foundation Blog, &lt;em>Using Harbor as an AI Model Registry&lt;/em> (3 de marzo de 2026) — &lt;a href="https://blogs.vmware.com/cloud-foundation/2026/03/03/using-harbor-as-an-ai-model-registry/">https://blogs.vmware.com/cloud-foundation/2026/03/03/using-harbor-as-an-ai-model-registry/&lt;/a>&lt;/li>
&lt;li>MLflow, &lt;em>Model Registry workflows — stages deprecadas desde MLflow 2.9.0&lt;/em> — &lt;a href="https://mlflow.org/docs/latest/ml/model-registry/workflow/">https://mlflow.org/docs/latest/ml/model-registry/workflow/&lt;/a>&lt;/li>
&lt;li>MLflow, &lt;em>Release 3.13.0 highlights&lt;/em> — &lt;a href="https://mlflow.org/releases/3.13.0/">https://mlflow.org/releases/3.13.0/&lt;/a>&lt;/li>
&lt;li>Kubeflow, &lt;em>Hub (antes Model Registry) — overview&lt;/em> — &lt;a href="https://www.kubeflow.org/docs/components/hub/overview/">https://www.kubeflow.org/docs/components/hub/overview/&lt;/a>&lt;/li>
&lt;li>kubernetes/enhancements, &lt;em>KEP-4639: OCI VolumeSource (alfa 1.31, beta 1.33/1.35, GA 1.36)&lt;/em> — &lt;a href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/4639-oci-volume-source/README.md">https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/4639-oci-volume-source/README.md&lt;/a>&lt;/li>
&lt;li>PerfectScale, &lt;em>Kubernetes v1.36 release: OCI artifact volume support reaches GA&lt;/em> (22 de abril de 2026) — &lt;a href="https://www.perfectscale.io/blog/kubernetes-v1-36-release">https://www.perfectscale.io/blog/kubernetes-v1-36-release&lt;/a>&lt;/li>
&lt;li>KServe, &lt;em>OCI storage (modelcar)&lt;/em> — &lt;a href="https://kserve.github.io/website/docs/model-serving/storage/providers/oci">https://kserve.github.io/website/docs/model-serving/storage/providers/oci&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>CNCF announces Dragonfly&amp;rsquo;s graduation&lt;/em> (14 de enero de 2026) — &lt;a href="https://www.cncf.io/announcements/2026/01/14/cloud-native-computing-foundation-announces-dragonflys-graduation/">https://www.cncf.io/announcements/2026/01/14/cloud-native-computing-foundation-announces-dragonflys-graduation/&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>Dragonfly v2.5.0 is released&lt;/em> (30 de junio de 2026) — &lt;a href="https://www.cncf.io/blog/2026/06/30/dragonfly-v2-5-0-is-released/">https://www.cncf.io/blog/2026/06/30/dragonfly-v2-5-0-is-released/&lt;/a>&lt;/li>
&lt;li>NVIDIA Run:ai, &lt;em>Model Streamer benchmarks&lt;/em> — &lt;a href="https://github.com/run-ai/runai-model-streamer/blob/master/docs/src/benchmarks.md">https://github.com/run-ai/runai-model-streamer/blob/master/docs/src/benchmarks.md&lt;/a>&lt;/li>
&lt;li>vLLM, &lt;em>LoRA adapters (carga dinámica y resolver plugins)&lt;/em> — &lt;a href="https://docs.vllm.ai/en/stable/features/lora/">https://docs.vllm.ai/en/stable/features/lora/&lt;/a>
&lt;/content>
&lt;/invoke>&lt;/li>
&lt;/ul></description></item></channel></rss>