<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dranet on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/dranet/</link><description>Recent content in Dranet on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Sat, 06 Jun 2026 12:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/dranet/index.xml" rel="self" type="application/rss+xml"/><item><title>La puerta de la cocina que el maître no miró: NUMA de red, Cilium eBPF y DRANET, la cuarta pata del pinning</title><link>https://blog.lo0.es/posts/cilium-ebpf-dranet-numa-de-red-inferencia/</link><pubDate>Sat, 06 Jun 2026 12:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/cilium-ebpf-dranet-numa-de-red-inferencia/</guid><description>&lt;blockquote>
&lt;p>Cuarta entrega —coda— de &amp;ldquo;por debajo del motor&amp;rdquo;. La serie cerró con tres patas de la localidad: el &lt;a href="https://blog.lo0.es/posts/nvlink-nvswitch-nccl-tensor-parallel/">cable entre GPUs&lt;/a>, el &lt;a href="https://blog.lo0.es/posts/numa-hugepages-aislamiento-cpu-inferencia/">host a mano&lt;/a> y la &lt;a href="https://blog.lo0.es/posts/kubelet-resource-managers-rke2-numa/">orquestación declarativa del kubelet&lt;/a>. Pero el maître del último post sentaba al grupo mirando CPU, memoria y GPU, y nunca preguntó &lt;strong>por qué puerta entran los platos&lt;/strong>. Esa puerta es la NIC. Aquí está la cuarta pata.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>El &lt;a href="https://blog.lo0.es/posts/kubelet-resource-managers-rke2-numa/">Topology Manager&lt;/a> admite un pod en &lt;code>single-numa-node&lt;/code> si sus CPUs, su memoria y su GPU caben en el &lt;strong>mismo&lt;/strong> NUMA node. La &lt;strong>NIC no entra en esa cuenta&lt;/strong>: el kubelet no tiene un Hint Provider para la tarjeta de red. En un nodo de inferencia con red a 200/400 Gb/s —el caso de &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">disaggregated serving&lt;/a>, donde el KV-cache viaja por RDMA entre el pool de prefill y el de decode— una NIC en el socket equivocado hace que &lt;strong>cada paquete cruce la UPI/QPI&lt;/strong>, exactamente el &amp;ldquo;NUMA remoto&amp;rdquo; que la serie combate por el lado de cómputo, pero por la puerta de la red. Y hay un segundo frente: el &lt;strong>softirq&lt;/strong> (&lt;code>NET_RX&lt;/code>) que procesa el datapath corre en la CPU que atiende la IRQ de la NIC; si esa CPU es uno de los cores que &lt;code>isolcpus&lt;/code>/&lt;code>reserved-cpus&lt;/code> dieron en exclusiva a vLLM, el softirq le roba ciclos y mete jitter en la cola de p99. &lt;strong>Cilium eBPF&lt;/strong> sustituye dos piezas de RKE2 —&lt;code>kube-proxy&lt;/code> (por load balancing eBPF/XDP) y el CNI por defecto &lt;strong>Canal&lt;/strong> (por datapath nativo)— y su propia guía de tuning te manda &lt;strong>matar &lt;code>irqbalance&lt;/code> y fijar las IRQ de la NIC&lt;/strong>: una &lt;strong>cuarta lista&lt;/strong> que alinear junto a &lt;code>isolcpus&lt;/code> y &lt;code>reserved-cpus&lt;/code>. El estado del arte 2026 cierra el hueco por arriba: &lt;strong>netkit&lt;/strong> (kernel ≥6.8, overhead de namespace a cero), &lt;strong>BIG TCP&lt;/strong> (super-paquetes de 192k para 100Gb/s+), &lt;strong>host-routing&lt;/strong> (bypass de iptables), y sobre todo &lt;strong>DRA/DRANET&lt;/strong>, el driver de red que por fin co-programa &lt;strong>GPU y NIC NUMA-locales en el mismo PCIe root&lt;/strong>, habilitando GPUDirect RDMA con &lt;strong>+59,6% de bus bandwidth en &lt;code>all_gather&lt;/code> y +58,1% en &lt;code>all_reduce&lt;/code>&lt;/strong>. Sobre un cluster genérico RKE2 con nodos 4×H100 SXM.&lt;/p>
&lt;h2 id="dónde-estás-el-plano-de-red-que-la-trilogía-no-abrió">Dónde estás: el plano de red que la trilogía no abrió&lt;/h2>
&lt;div class="diagram" style="max-width:560px;margin:1.2rem auto;">
&lt;svg viewBox="0 0 560 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="La cuarta pata: el plano de red bajo el mismo NUMA que CPU, memoria y GPU">
&lt;text x="280" y="24" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">El stack vertical · la cuarta pata de la localidad&lt;/text>
&lt;rect x="120" y="40" width="320" height="38" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.3"/>
&lt;text x="280" y="64" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">Motor · pod vLLM (TP, batching, KV-cache)&lt;/text>
&lt;rect x="120" y="84" width="320" height="38" rx="6" fill="#eef3fb" stroke="#4a6fa5" stroke-width="1.4"/>
&lt;text x="280" y="108" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">Orquestación · kubelet: CPU/Mem/Topology Mgr (post 3)&lt;/text>
&lt;rect x="120" y="128" width="320" height="58" rx="6" fill="#dceede" stroke="#3c8c54" stroke-width="3"/>
&lt;text x="280" y="152" text-anchor="middle" font-family="sans-serif" font-size="12.5" font-weight="700" fill="#1f5c34">ESTÁS AQUÍ · plano de red: Cilium eBPF + DRA/NIC&lt;/text>
&lt;text x="280" y="170" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="#2c6b42">localidad NUMA de la NIC · IRQ · GPUDirect RDMA&lt;/text>
&lt;rect x="120" y="192" width="320" height="38" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.3"/>
&lt;text x="280" y="216" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">Host · NUMA, hugepages, isolcpus (post 2)&lt;/text>
&lt;rect x="120" y="236" width="320" height="38" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.3"/>
&lt;text x="280" y="260" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">CUDA + NCCL + NVLink (post 1)&lt;/text>
&lt;rect x="120" y="280" width="320" height="38" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.3"/>
&lt;text x="280" y="304" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">Hardware · 2 sockets, 4×H100 SXM, NIC 400 Gb/s&lt;/text>
&lt;text x="280" y="332" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="#777">CPU+memoria+GPU las pinnea el kubelet; la NIC, hasta 2026, no la pinnaba nadie&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-la-puerta-por-la-que-entran-los-platos">La analogía: la puerta por la que entran los platos&lt;/h2>
&lt;p>Vuelve al restaurante del &lt;a href="https://blog.lo0.es/posts/kubelet-resource-managers-rke2-numa/">post anterior&lt;/a>. El maître —el Topology Manager— sentó al grupo de ocho en una sola mesa (un NUMA node) porque cabían los comensales (CPUs), los cubiertos (memoria) y la botella reservada (la GPU). Mesa perfecta. Pero el maître &lt;strong>nunca miró dónde está el pase de cocina&lt;/strong>: la puerta por la que entra y sale cada plato.&lt;/p>
&lt;p>Esa puerta es la &lt;strong>NIC&lt;/strong>. Por ahí entra el prompt, salen los tokens, y —en disaggregated serving— circula el KV-cache que el pool de prefill manda al de decode. Si la mesa está en la sala de la izquierda (socket 0) pero el pase de cocina está en la de la derecha (socket 1), &lt;strong>cada plato cruza el restaurante entero&lt;/strong> (la UPI/QPI), una y otra vez, por mucho que la mesa esté impecablemente puesta. El comensal no nota la mesa perfecta: nota que el plato llega tarde y frío.&lt;/p>
&lt;p>Y hay un detalle más fino: el camarero que cruza la sala con los platos (el &lt;strong>softirq&lt;/strong> que procesa los paquetes) es &lt;strong>uno de los comensales sentados&lt;/strong>. Si el maître le asignó una silla en exclusiva para comer tranquilo (un core aislado por &lt;code>isolcpus&lt;/code> para vLLM) pero el restaurante lo pone también a hacer de camarero de la puerta lejana, ese comensal no come: se pasa la cena cruzando la sala. El jitter aparece justo donde creías haber comprado calma.&lt;/p>
&lt;p>La trilogía niveló tres patas de la mesa: el cable, el host y la orquestación. La cuarta —&lt;strong>por qué puerta entran los platos y quién los lleva&lt;/strong>— no la nivela ningún manager del kubelet. Hasta 2026.&lt;/p>
&lt;h2 id="el-hueco-por-qué-el-topology-manager-no-mira-la-nic">El hueco: por qué el Topology Manager no mira la NIC&lt;/h2>
&lt;p>El mecanismo del &lt;a href="https://blog.lo0.es/posts/kubelet-resource-managers-rke2-numa/">post 3&lt;/a> es un coordinador (Topology Manager) que consulta a tres &lt;strong>Hint Providers&lt;/strong>: CPU Manager, Memory Manager y Device Manager (el plugin de GPU). Cada uno dice en qué NUMA node puede satisfacer su parte; el coordinador calcula la intersección y admite o rechaza.&lt;/p>
&lt;p>El problema es de &lt;strong>censo&lt;/strong>: la NIC clásica no es un &amp;ldquo;device&amp;rdquo; del Device Manager. Una tarjeta Ethernet/InfiniBand estándar la gestiona el CNI y el kernel, no se pide en el &lt;code>resources:&lt;/code> del pod como &lt;code>nvidia.com/gpu&lt;/code>, y por tanto &lt;strong>no emite hint NUMA&lt;/strong>. El Topology Manager alinea CPU+memoria+GPU y deja la NIC donde el hardware la puso, que puede ser el otro socket. El maître tiene tres ayudantes y le falta el cuarto: el que sabe por qué puerta entran los platos.&lt;/p>
&lt;p>Esto no importaba cuando la red de un nodo eran 10/25 Gb/s y el cuello de botella estaba en otro sitio. Importa &lt;strong>ahora&lt;/strong>, con dos cargas que saturan la red del nodo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Disaggregated serving.&lt;/strong> El &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">KV-cache que viaja entre el pool de prefill y el de decode&lt;/a> se mueve por RDMA. Son transferencias grandes, sensibles a latencia y ancho de banda, que en multinodo salen por la NIC.&lt;/li>
&lt;li>&lt;strong>Colectivos NCCL multinodo.&lt;/strong> Cuando el &lt;a href="https://blog.lo0.es/posts/nvlink-nvswitch-nccl-tensor-parallel/">tensor/pipeline parallel cruza el límite del nodo&lt;/a>, los &lt;code>all-reduce&lt;/code>/&lt;code>all-gather&lt;/code> ya no van por NVLink sino por GPUDirect RDMA sobre la NIC.&lt;/li>
&lt;/ul>
&lt;p>En ambos, &lt;strong>dónde está la NIC respecto a la GPU y a los cores del pod&lt;/strong> decide el rendimiento. Y eso el kubelet, por sí solo, no lo coordina.&lt;/p>
&lt;h2 id="el-datapath-de-red-bajo-numa-irq-softirq-y-dma">El datapath de red bajo NUMA: IRQ, softirq y DMA&lt;/h2>
&lt;p>Para ver por qué la localidad de la NIC pesa, hay que mirar el camino de un paquete que llega:&lt;/p>
&lt;div class="diagram" style="max-width:820px;margin:1.2rem auto;">
&lt;svg viewBox="0 0 820 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Camino de un paquete: NIC, IRQ, softirq y DMA cross-NUMA">
&lt;defs>&lt;marker id="nm" 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="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="410" y="22" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">Socket 0 (NIC aquí) vs Socket 1 (pod vLLM aquí): el cruce que no se ve&lt;/text>
&lt;!-- Socket 0 -->
&lt;rect x="30" y="46" width="360" height="240" rx="10" fill="none" stroke="#4a6fa5" stroke-width="1.6" stroke-dasharray="5 3"/>
&lt;text x="210" y="66" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#4a6fa5">NUMA node 0 · PCIe root con la NIC&lt;/text>
&lt;rect x="60" y="80" width="120" height="44" rx="7" fill="#eef3fb" stroke="#4a6fa5" stroke-width="1.6"/>
&lt;text x="120" y="100" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">NIC 400 Gb/s&lt;/text>
&lt;text x="120" y="115" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#444">multi-queue, RSS&lt;/text>
&lt;rect x="60" y="150" width="120" height="44" rx="7" fill="#f7efda" stroke="#c79a32" stroke-width="1.6"/>
&lt;text x="120" y="170" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">IRQ + softirq&lt;/text>
&lt;text x="120" y="185" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#444">NET_RX en core 0..&lt;/text>
&lt;rect x="60" y="220" width="120" height="44" rx="7" fill="#dceede" stroke="#3c8c54" stroke-width="1.6"/>
&lt;text x="120" y="240" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">RAM node 0&lt;/text>
&lt;text x="120" y="255" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#444">DMA del paquete&lt;/text>
&lt;path d="M120,124 L120,150" fill="none" stroke="#666" stroke-width="1.4" marker-end="url(#nm)"/>
&lt;path d="M120,194 L120,220" fill="none" stroke="#666" stroke-width="1.4" marker-end="url(#nm)"/>
&lt;!-- Socket 1 -->
&lt;rect x="430" y="46" width="360" height="240" rx="10" fill="none" stroke="#a85454" stroke-width="1.6" stroke-dasharray="5 3"/>
&lt;text x="610" y="66" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#a85454">NUMA node 1 · aquí pinneó el kubelet al pod&lt;/text>
&lt;rect x="550" y="150" width="160" height="44" rx="7" fill="#dceede" stroke="#3c8c54" stroke-width="2.2"/>
&lt;text x="630" y="170" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">pod vLLM + GPU&lt;/text>
&lt;text x="630" y="185" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#444">CPUs exclusivas node 1&lt;/text>
&lt;!-- cross-numa arrow -->
&lt;path d="M180,242 C320,242 470,172 550,172" fill="none" stroke="#a85454" stroke-width="2.4" marker-end="url(#nm)"/>
&lt;text x="370" y="225" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#a85454">cada paquete cruza la UPI/QPI&lt;/text>
&lt;text x="370" y="240" text-anchor="middle" font-family="sans-serif" font-size="9" font-style="italic" fill="#a85454">+latencia, +consumo del enlace inter-socket&lt;/text>
&lt;text x="410" y="306" text-anchor="middle" font-family="sans-serif" font-size="10" font-style="italic" fill="#777">El Topology Manager hizo su trabajo en el node 1; la NIC se quedó en el 0. Nadie alineó las dos cosas.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>Tres hechos del kernel que la analogía comprime:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>La IRQ tiene afinidad.&lt;/strong> Cada cola de la NIC dispara una interrupción que el kernel atiende en una CPU concreta (&lt;code>/proc/irq/&amp;lt;n&amp;gt;/smp_affinity&lt;/code>). El procesamiento pesado se difiere a un &lt;strong>softirq&lt;/strong> (&lt;code>NET_RX&lt;/code>/&lt;code>NET_TX&lt;/code>), que corre en &lt;strong>esa misma CPU&lt;/strong>. Si &lt;code>irqbalance&lt;/code> está suelto, las va migrando de forma no determinista —veneno para el p99.&lt;/li>
&lt;li>&lt;strong>El softirq compite con el pod.&lt;/strong> Si la IRQ cae en un core que &lt;code>isolcpus&lt;/code> reservó para vLLM, el &lt;code>NET_RX&lt;/code> de esa cola le roba ciclos al modelo. La señal en &lt;code>/proc/softirqs&lt;/code>: una columna de &lt;code>NET_RX&lt;/code> que se dispara en una sola CPU. Es el mismo jitter del &lt;a href="https://blog.lo0.es/posts/numa-hugepages-aislamiento-cpu-inferencia/">post 2&lt;/a>, entrando por la red.&lt;/li>
&lt;li>&lt;strong>El DMA tiene origen NUMA.&lt;/strong> La NIC escribe el paquete por DMA en la RAM del socket de su PCIe root. Si el consumidor (el hilo del pod) está en el otro socket, lee cruzando la UPI/QPI. RFS (Receive Flow Steering) intenta llevar el procesamiento a la CPU del consumidor, pero no puede teletransportar la NIC al otro socket.&lt;/li>
&lt;/ol>
&lt;h3 id="un-número-con-su-salvedad">Un número, con su salvedad&lt;/h3>
&lt;p>Pongamos un nodo de 2 sockets, NIC de &lt;strong>400 Gb/s = 50 GB/s&lt;/strong> en el PCIe root del socket 0, y un pod de decode pinneado al socket 1. Si la NIC satura, esos ~50 GB/s de tráfico de recepción &lt;strong>cruzan la UPI&lt;/strong> hacia el socket 1. Un enlace UPI 2.0 ronda los ~&lt;strong>20–40 GB/s&lt;/strong> por dirección y enlace según generación; aun con varios enlaces, 50 GB/s de tráfico de red &lt;strong>a contracorriente&lt;/strong> se comen una fracción nada despreciable del presupuesto inter-socket —el mismo presupuesto por el que ya compiten los accesos remotos a memoria del pod y, si hay multinodo, el &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">KV-cache de la disaggregation&lt;/a>. No doy un &amp;ldquo;X% de degradación&amp;rdquo; cerrado porque depende de generación de CPU, número de enlaces UPI, MTU y patrón de tráfico; sin esa metodología, cualquier cifra exacta es marketing.&lt;/p>
&lt;p>Lo que &lt;strong>sí&lt;/strong> está medido con metodología pública es el efecto agregado de alinear GPU y NIC: el proyecto &lt;strong>DRANET&lt;/strong> reporta &lt;strong>+59,6% de bus bandwidth en &lt;code>all_gather&lt;/code> y +58,1% en &lt;code>all_reduce&lt;/code>&lt;/strong> (colectivos NCCL) cuando la NIC asignada es &lt;strong>NUMA-local a la GPU&lt;/strong> frente a no serlo. Esa es la magnitud del hueco que el Topology Manager dejaba abierto.&lt;/p>
&lt;h2 id="qué-sustituye-cilium-ebpf-de-rke2-y-por-qué-toca-esta-historia">Qué sustituye Cilium eBPF de RKE2 (y por qué toca esta historia)&lt;/h2>
&lt;p>RKE2 trae por defecto &lt;strong>Canal&lt;/strong> (Flannel + Calico) como CNI y &lt;strong>&lt;code>kube-proxy&lt;/code>&lt;/strong> (reglas iptables/IPVS) para el balanceo de Services. Cambiar a Cilium (&lt;code>cni: cilium&lt;/code> en &lt;code>/etc/rancher/rke2/config.yaml&lt;/code>) sustituye ambas piezas por un datapath eBPF:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza de RKE2&lt;/th>
&lt;th>Qué hace&lt;/th>
&lt;th>Qué pone Cilium eBPF&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>kube-proxy&lt;/code> (iptables/IPVS)&lt;/td>
&lt;td>balanceo de Services&lt;/td>
&lt;td>LB en eBPF; con &lt;code>kubeProxyReplacement=true&lt;/code>, y aceleración en &lt;strong>XDP&lt;/strong> (capa de driver)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Canal (Flannel+Calico)&lt;/td>
&lt;td>overlay VXLAN + NetworkPolicy&lt;/td>
&lt;td>datapath nativo (&lt;code>routingMode=native&lt;/code>), NetworkPolicy L3/L4 y L7 en eBPF&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>veth por pod&lt;/td>
&lt;td>par de interfaces del namespace&lt;/td>
&lt;td>&lt;strong>netkit&lt;/strong> (kernel ≥6.8): overhead de namespace ~0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>recorrido iptables del host&lt;/td>
&lt;td>hooks netfilter&lt;/td>
&lt;td>&lt;strong>host-routing&lt;/strong> eBPF: bypass de iptables y de la parte alta del stack&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Hasta aquí es networking puro y &lt;strong>no toca&lt;/strong> los resource managers del kubelet: Cilium no asigna CPUs exclusivas ni emite hints NUMA de cómputo. Los diez knobs del &lt;a href="https://blog.lo0.es/posts/kubelet-resource-managers-rke2-numa/">post 3&lt;/a> siguen idénticos pongas Canal o Cilium.&lt;/p>
&lt;p>&lt;strong>Pero&lt;/strong> Cilium sí entra en la cuarta pata por dos puertas. La primera: su propia &lt;a href="https://docs.cilium.io/en/stable/operations/performance/tuning/">guía de tuning&lt;/a> recomienda, literalmente, &lt;em>&amp;ldquo;matar &lt;code>irqbalance&lt;/code> y fijar las IRQ de la NIC a CPUs específicas para máximo aislamiento de la carga&amp;rdquo;&lt;/em>, además del perfil &lt;code>tuned network-latency&lt;/code>, el governor &lt;code>performance&lt;/code> y &lt;code>CONFIG_PREEMPT_NONE&lt;/code>. Es decir: el datapath eBPF rinde de verdad &lt;strong>solo si coordinas la afinidad de IRQ&lt;/strong> —y esa afinidad tiene que apuntar a los cores &lt;strong>housekeeping&lt;/strong> (&lt;code>reserved-cpus&lt;/code>), nunca a los aislados. Aparece así una &lt;strong>cuarta lista&lt;/strong> que mantener coherente con &lt;code>isolcpus&lt;/code> y &lt;code>reserved-cpus&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">isolcpus = 2-31,34-63 # cores exclusivos para vLLM (host, post 2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">reserved-cpus = 0-1,32-33 # housekeeping del kubelet (post 3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IRQ affinity = 0-1,32-33 # NIC IRQs → SOLO housekeeping (este post)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> # nunca 2-31: ahí el softirq robaría al modelo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>La segunda puerta: &lt;strong>netkit + host-routing + BIG TCP&lt;/strong> reducen cuántas veces el paquete cruza el stack y el namespace, lo que &lt;strong>amortigua&lt;/strong> (no elimina) el coste del cruce NUMA. BIG TCP arma super-paquetes de hasta 192k (frente a 64k) para 100Gb/s+; menos travesías del stack es menos trabajo de softirq en el core, y por tanto menos presión sobre el presupuesto inter-socket. Es la analogía del &lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">continuous batching&lt;/a> aplicada al stack de red: amortizar un coste fijo sobre lotes mayores.&lt;/p>
&lt;h3 id="perfil-de-rendimiento-de-cilium-estado-119-kernel-68">Perfil de rendimiento de Cilium (estado 1.19, kernel ≥6.8)&lt;/h3>
&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"># Helm, perfil de rendimiento recomendado (resumen de la tuning guide)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">helm install cilium cilium/cilium --version 1.19.4 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --namespace kube-system &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --set &lt;span class="nv">routingMode&lt;/span>&lt;span class="o">=&lt;/span>native &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --set bpf.datapathMode&lt;span class="o">=&lt;/span>netkit &lt;span class="se">\ &lt;/span> &lt;span class="c1"># overhead de namespace ~0 (kernel &amp;gt;=6.8)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --set bpf.masquerade&lt;span class="o">=&lt;/span>&lt;span class="nb">true&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> --set &lt;span class="nv">kubeProxyReplacement&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\ &lt;/span> &lt;span class="c1"># sustituye kube-proxy de RKE2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --set &lt;span class="nv">enableIPv4BIGTCP&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\ &lt;/span> &lt;span class="c1"># super-paquetes 192k (NIC mlx5/ice)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --set &lt;span class="nv">enableIPv6BIGTCP&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&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> --set bpf.distributedLRU.enabled&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\#&lt;/span> mapas BPF per-CPU: menos contención de spinlock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --set bpf.mapDynamicSizeRatio&lt;span class="o">=&lt;/span>0.08 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --set &lt;span class="nv">bpfClockProbe&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&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"># Verificación dentro de un pod de Cilium:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cilium status --verbose &lt;span class="p">|&lt;/span> grep -E &lt;span class="s2">&amp;#34;Device Mode|Host Routing|BIG TCP|XDP&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Device Mode: netkit · Host Routing: BPF · IPv4 BIG TCP: enabled · XDP Acceleration: Native&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Salvedad escéptica: netkit y BIG TCP son &lt;strong>beta&lt;/strong> y exigen kernel ≥6.8 y NICs concretas (mlx4/mlx5/ice). No son in-place: cambian fundamentos del datapath y obligan a reiniciar pods o, mejor, a aplicarlos por &lt;em>per-node config&lt;/em> solo en nodos nuevos. Para un cluster ENS en producción, eso es una ventana de mantenimiento, no un &lt;code>helm upgrade&lt;/code> a ciegas.&lt;/p>
&lt;h2 id="el-estado-del-arte-2026-dra-y-dranet-el-maître-que-por-fin-mira-la-puerta">El estado del arte 2026: DRA y DRANET, el maître que por fin mira la puerta&lt;/h2>
&lt;p>Lo que cierra el hueco de raíz no es Cilium —es el &lt;strong>mecanismo de admisión&lt;/strong> que el kubelet no tenía para la NIC: &lt;strong>Dynamic Resource Allocation (DRA)&lt;/strong>, beta desde Kubernetes 1.32 y con avances en cada release hasta la 1.36 (mayo 2026). DRA generaliza el modelo de &amp;ldquo;devices&amp;rdquo; más allá de la GPU: un driver descubre el hardware, publica &lt;code>ResourceSlices&lt;/code> con sus atributos —incluida la &lt;strong>topología NUMA y el PCIe root&lt;/strong>— y el scheduler resuelve &lt;code>ResourceClaims&lt;/code> que pueden exigir afinidad entre dispositivos.&lt;/p>
&lt;p>&lt;strong>DRANET&lt;/strong> (proyecto &lt;code>kubernetes-sigs&lt;/code>) es el driver DRA de red. Descubre las NICs (incluidas las RDMA-capaces), las anuncia como &lt;code>ResourceSlices&lt;/code>, y vía &lt;strong>NRI&lt;/strong> las inyecta en el namespace del pod —compatible con el CNI que ya tengas, Cilium incluido. La pieza clave para esta historia: combinado con el &lt;strong>NVIDIA GPU DRA driver&lt;/strong>, permite &lt;strong>co-programar GPU y NIC que comparten PCIe root&lt;/strong> (la relación que NVIDIA llama &lt;code>NODE&lt;/code>), que es justo la condición de &lt;strong>GPUDirect RDMA&lt;/strong>. El maître por fin tiene su cuarto ayudante: &lt;em>&amp;quot;¿hay una NIC NUMA-local a esta GPU?&amp;quot;&lt;/em>.&lt;/p>
&lt;p>El &lt;code>ResourceClaimTemplate&lt;/code> usa selectores CEL para pedir exactamente esa alineación:&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="c"># Pedir una NIC RDMA NUMA-local a la GPU asignada (esquema ilustrativo DRANET/DRA)&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">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">resource.k8s.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">ResourceClaimTemplate&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">gpu-nic-numa-aligned&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">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">devices&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">requests&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">rdma-nic&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">deviceClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dra.net &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># NICs publicadas por DRANET&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">constraints&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">requests&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;rdma-nic&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">matchAttribute&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;dra.net/pcieRoot&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># misma raíz PCIe que la GPU&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="c"># → habilita GPUDirect RDMA sobre camino NUMA-local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Por qué importa para inferencia, no para &amp;ldquo;AI training&amp;rdquo; abstracto: en &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">disaggregated serving&lt;/a>, RDMA es lo que mueve el KV-cache entre el pool de prefill y el de decode con la latencia que el &lt;a href="https://blog.lo0.es/posts/prefill-optimizaciones-vllm/">TTFT&lt;/a> exige; y en multinodo, GPUDirect RDMA sustituye al &lt;a href="https://blog.lo0.es/posts/nvlink-nvswitch-nccl-tensor-parallel/">NVLink&lt;/a> como medio del colectivo. Alinear GPU+NIC en el mismo PCIe root es lo que convierte un &amp;ldquo;RDMA que funciona&amp;rdquo; en un &amp;ldquo;RDMA que rinde&amp;rdquo; —los +60% de bus bandwidth de DRANET.&lt;/p>
&lt;p>Estado y salvedades: DRA es &lt;strong>beta&lt;/strong> (gates a habilitar a mano), DRANET es joven (proyecto SIG, en evolución) y la oferta gestionada existe sobre todo en cloud (GKE managed DRANET en preview, AKS para RDMA). Para on-premise ENS es &lt;strong>camino, no producto cerrado&lt;/strong>: el valor hoy es entender que la cuarta pata ya tiene mecanismo estándar OSS, y empezar a pilotarlo en un nodo de laboratorio, no meterlo en producción crítica este trimestre.&lt;/p>
&lt;h2 id="cómo-se-conecta-con-el-resto-del-stack">Cómo se conecta con el resto del stack&lt;/h2>
&lt;p>&lt;strong>Con el host (post 2).&lt;/strong> La afinidad de IRQ de la NIC es una &lt;strong>tercera lista&lt;/strong> que casar con &lt;code>isolcpus&lt;/code> y &lt;code>reserved-cpus&lt;/code>. Las IRQ van a housekeeping; los cores aislados, intactos. Descoordinarlas mete por la puerta de la red el jitter que &lt;code>isolcpus&lt;/code> echó por la de cómputo.&lt;/p>
&lt;p>&lt;strong>Con la orquestación (post 3).&lt;/strong> DRA es la extensión natural del Topology Manager: el mismo principio de &amp;ldquo;admite solo si encaja en el NUMA node&amp;rdquo; llevado a la NIC. Donde el Device Manager dejaba la red fuera del censo, DRANET la mete.&lt;/p>
&lt;p>&lt;strong>Con el interconnect (post 1).&lt;/strong> Dentro del nodo manda NVLink; al cruzar el límite del nodo, GPUDirect RDMA sobre la NIC es el medio del colectivo. La política NUMA del kubelet garantiza que GPU y CPUs comparten socket; &lt;strong>DRANET añade que la NIC también&lt;/strong> —y solo entonces el RDMA va por el camino corto.&lt;/p>
&lt;p>&lt;strong>Con disaggregated serving.&lt;/strong> El KV-cache prefill→decode es el tráfico que más castiga una NIC mal ubicada. La cuarta pata es lo que hace que &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">separar prefill y decode&lt;/a> no se pague en latencia de transferencia.&lt;/p>
&lt;p>&lt;strong>Con capacity planning.&lt;/strong> El &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">dimensionado&lt;/a> gana una dimensión: no basta con &amp;ldquo;GPUs por nodo y cores por NUMA node&amp;rdquo;; hay que contar &lt;strong>cuántas NICs NUMA-locales a GPU&lt;/strong> tiene el chasis. Un nodo con 4 GPUs y una sola NIC en el socket 0 tiene dos GPUs &amp;ldquo;lejos de la puerta&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>Con la observabilidad.&lt;/strong> Lo que confirma que la cuarta pata está bien puesta no es un dashboard de aplicación: es &lt;code>/proc/softirqs&lt;/code> (¿&lt;code>NET_RX&lt;/code> concentrado en housekeeping?), &lt;code>nvidia-smi topo -m&lt;/code> (¿relación &lt;code>NODE&lt;/code>/&lt;code>PHB&lt;/code> GPU↔NIC?) y los contadores de la NIC. Encaja con la &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">observabilidad GPU con DCGM&lt;/a>: la GPU &amp;ldquo;al 60% sin razón&amp;rdquo; puede ser el host esperando paquetes que cruzan el socket.&lt;/p>
&lt;h2 id="trampas-y-cosas-que-no-son-lo-que-parecen">Trampas y cosas que no son lo que parecen&lt;/h2>
&lt;p>&lt;strong>Creer que cambiar a Cilium &amp;ldquo;ya optimiza la red&amp;rdquo;.&lt;/strong> Cilium eBPF sustituye a kube-proxy y Canal y rinde mejor de serie, pero el despliegue por defecto prioriza compatibilidad, no rendimiento. Sin &lt;code>irqbalance&lt;/code> desactivado, sin IRQ fijadas a housekeeping y sin netkit/host-routing, dejas la mayor parte de la mejora en la mesa. La doc de Cilium lo dice; mucha gente no lee la tuning guide.&lt;/p>
&lt;p>&lt;strong>Fijar las IRQ de la NIC a cores aislados.&lt;/strong> El error simétrico del knob 6 del post 3: si pones la afinidad de IRQ sobre &lt;code>isolcpus&lt;/code>, el softirq &lt;code>NET_RX&lt;/code> le roba ciclos a vLLM justo en los cores que aislaste para que nadie lo molestara. Las IRQ van a &lt;code>reserved-cpus&lt;/code>, siempre.&lt;/p>
&lt;p>&lt;strong>Asumir que el Topology Manager ya alinea la NIC.&lt;/strong> No lo hace: la NIC clásica no es un Hint Provider. Si necesitas localidad NIC↔GPU, hoy el mecanismo es DRA/DRANET, no una política del kubelet. Esperar a que &lt;code>single-numa-node&lt;/code> lo resuelva es esperar a algo que no está en su diseño.&lt;/p>
&lt;p>&lt;strong>Meter DRA/DRANET en producción ENS este trimestre.&lt;/strong> Es beta y joven. El movimiento sensato es pilotarlo en un nodo de laboratorio, medir &lt;code>all_reduce&lt;/code>/&lt;code>all_gather&lt;/code> con y sin alineación, y decidir con datos. La cifra del +60% es de un entorno concreto; reprodúcela en el tuyo antes de prometerla.&lt;/p>
&lt;p>&lt;strong>BIG TCP / netkit sin leer los requisitos.&lt;/strong> Kernel ≥6.8, NICs mlx4/mlx5/ice, sin túnel ni cifrado para BIG TCP, y nada de in-place: obliga a reiniciar pods o a per-node config. En un cluster con IPsec o con NICs no soportadas, parte de esto no aplica. Verifica &lt;code>cilium status --verbose&lt;/code> antes de dar por hecho que está activo.&lt;/p>
&lt;p>&lt;strong>Confundir el datapath eBPF (kernel) con el agente Cilium (pod).&lt;/strong> &lt;code>cilium-agent&lt;/code> es un DaemonSet &lt;code>Burstable&lt;/code> que debe vivir en housekeeping (lo cubre &lt;code>system-reserved&lt;/code>). Pero el procesamiento del datapath corre en &lt;strong>softirq&lt;/strong>, gobernado por la afinidad de IRQ del host, &lt;strong>no&lt;/strong> por &lt;code>reserved-cpus&lt;/code>. Son dos cosas distintas; pinear bien el pod no pinea el softirq.&lt;/p>
&lt;h2 id="conclusión">Conclusión&lt;/h2>
&lt;p>La serie &amp;ldquo;por debajo del motor&amp;rdquo; perseguía una idea: el rendimiento que parece un problema del motor (vLLM lento) o del modelo (cuantización) es, demasiadas veces, un problema de &lt;strong>localidad&lt;/strong> en una capa más baja. La trilogía cubrió tres: el cable (NVLink no usado), el host (NUMA remoto, jitter) y la orquestación (pinning que no ocurrió). Falta&lt;strong>ba&lt;/strong> la cuarta: &lt;strong>la red&lt;/strong>. El Topology Manager sienta al pod en una mesa NUMA perfecta y nunca pregunta por qué puerta entran los platos ni quién los lleva. En un nodo a 25 Gb/s daba igual; en uno a 400 Gb/s con KV-cache cruzando por RDMA, esa puerta decide el TTFT y el ancho de banda del colectivo. &lt;strong>Cilium eBPF&lt;/strong> sustituye kube-proxy y Canal por un datapath que rinde —si coordinas la afinidad de IRQ con &lt;code>isolcpus&lt;/code>/&lt;code>reserved-cpus&lt;/code>, una cuarta lista que alinear—, y &lt;strong>DRA/DRANET&lt;/strong> aporta por fin el censo que faltaba: co-programar GPU y NIC NUMA-locales en el mismo PCIe root, con la magnitud de mejora (+60% de bus bandwidth NCCL) que mide lo grande que era el hueco. Bajar de nivel no es esnobismo: es que la causa raíz vivía, una vez más, una capa por debajo de donde mira el dashboard.&lt;/p>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/hardening-secretos-stack-llm-soberano/">Hardening y secretos del stack LLM soberano: defensa en profundidad&lt;/a> — las NetworkPolicy default-deny y el mTLS con Cilium en el hardening del stack.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/pcie-topology-gpudirect-p2p-acs/">Los pasillos y el guardia: PCIe, GPUDirect P2P y ACS&lt;/a> — el GPUDirect RDMA que DRANET coloca NUMA-local lo rompe el ACS si fuerza el tráfico por el root complex; el bus por debajo de la localidad NIC↔GPU.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/kubelet-resource-managers-rke2-numa/">El maître que solo te sienta si cabéis en una mesa: resource managers en RKE2&lt;/a> — el post 3, padre directo de éste: el Topology Manager pinnea CPU+memoria+GPU pero no la NIC; aquí se abre esa cuarta pata.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/numa-hugepages-aislamiento-cpu-inferencia/">NUMA, hugepages y aislamiento de CPU&lt;/a> — el post 2; la afinidad de IRQ de la NIC es una tercera lista que casar con &lt;code>isolcpus&lt;/code> y &lt;code>reserved-cpus&lt;/code>, y el softirq &lt;code>NET_RX&lt;/code> es el mismo jitter entrando por la red.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/nvlink-nvswitch-nccl-tensor-parallel/">NVLink, NVSwitch y NCCL&lt;/a> — el post 1; al cruzar el nodo, GPUDirect RDMA sobre la NIC sustituye a NVLink, y DRANET es lo que garantiza que ese RDMA va por el camino NUMA-local.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving: prefill y decode separados&lt;/a> — el caso que más castiga una NIC mal ubicada: el KV-cache prefill→decode viaja por RDMA y paga cada cruce de socket.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">El stack de inferencia LLM on-premise en siete capas&lt;/a> — el edificio completo; la red es el plano que sostiene la inferencia multinodo.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoescalado de LLMs en Kubernetes con KEDA&lt;/a> — cada réplica nueva no solo pasa por la admisión NUMA del kubelet; con DRA, también por la del &lt;code>ResourceClaim&lt;/code> de NIC.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning de inferencia on-premise&lt;/a> — el sizing gana una dimensión: cuántas NICs NUMA-locales a GPU tiene el chasis, no solo cuántas GPUs.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/">Entornos mixtos NVIDIA + Intel&lt;/a> — la afinidad NUMA NIC↔acelerador se complica cuando el nodo mezcla GPUs, aceleradores y NICs heterogéneas.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU con DCGM&lt;/a> — cómo confirmar, métrica en mano, que la &amp;ldquo;GPU al 60%&amp;rdquo; no es el host esperando paquetes cruzando el socket.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM: cold start y carga del modelo&lt;/a> — el mismo principio de &amp;ldquo;saca a la CPU del medio&amp;rdquo; que aquí da GPUDirect RDMA, aplicado al disco con GPUDirect Storage para cargar pesos directos NVMe→HBM.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/sm-cuda-streams-cuda-graphs-inferencia/">SM, CUDA streams y CUDA graphs&lt;/a> — bajado un piso más: una vez los datos están en HBM, qué pasa en el silicio que los ejecuta y por qué el decode se vuelve launch-bound.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/aislar-agentes-ia-cliente-cluster/">El contratista con la llave maestra: aislar agentes de IA del workstation al cluster&lt;/a> — el otro uso de esta misma capa de kernel: sobre el datapath eBPF de Cilium, Tetragon engancha sus kprobes para observar y matar lo que hace un agente de IA en el cluster. Su &lt;a href="https://blog.lo0.es/posts/runbook-aislar-agentes-ia-bubblewrap-tetragon/">runbook&lt;/a> trae las &lt;code>TracingPolicy&lt;/code> concretas.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>Cilium, &lt;em>Tuning Guide&lt;/em> (netkit, host-routing, BIG TCP, XDP, fijar IRQ y matar irqbalance): &lt;a href="https://docs.cilium.io/en/stable/operations/performance/tuning/">https://docs.cilium.io/en/stable/operations/performance/tuning/&lt;/a>.&lt;/li>
&lt;li>Cilium 1.19 (febrero 2026), &lt;em>Cilium at Ten Years&lt;/em> — endurecimiento de cifrado, políticas y observabilidad: &lt;a href="https://www.infoq.com/news/2026/02/cilium-119/">https://www.infoq.com/news/2026/02/cilium-119/&lt;/a>.&lt;/li>
&lt;li>Isovalent, &lt;em>Cilium 1.18&lt;/em> (IPv6, encrypted overlay, ingress bandwidth, policy perf): &lt;a href="https://isovalent.com/blog/post/cilium-1-18/">https://isovalent.com/blog/post/cilium-1-18/&lt;/a>.&lt;/li>
&lt;li>RKE2, &lt;em>Network Options&lt;/em> (Canal por defecto; Cilium con kube-proxy replacement): &lt;a href="https://docs.rke2.io/networking/basic_network_options">https://docs.rke2.io/networking/basic_network_options&lt;/a>.&lt;/li>
&lt;li>Kubernetes, &lt;em>Dynamic Resource Allocation&lt;/em>: &lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/dynamic-resource-allocation/">https://kubernetes.io/docs/concepts/scheduling-eviction/dynamic-resource-allocation/&lt;/a>.&lt;/li>
&lt;li>Kubernetes blog, &lt;em>v1.36: More Drivers, New Features, and the Next Era of DRA&lt;/em> (mayo 2026): &lt;a href="https://kubernetes.io/blog/2026/05/07/kubernetes-v1-36-dra-136-updates/">https://kubernetes.io/blog/2026/05/07/kubernetes-v1-36-dra-136-updates/&lt;/a>.&lt;/li>
&lt;li>DRANET (kubernetes-sigs), driver DRA de red y paper &lt;em>The Kubernetes Network Driver Model&lt;/em> (+59,6% all_gather / +58,1% all_reduce): &lt;a href="https://github.com/kubernetes-sigs/dranet">https://github.com/kubernetes-sigs/dranet&lt;/a>.&lt;/li>
&lt;li>AKS Engineering, &lt;em>Optimizing RDMA performance for AI workloads on AKS with DRANET&lt;/em> (abril 2026): &lt;a href="https://blog.aks.azure.com/2026/04/01/dranet-rdma-optimization-for-ai-on-aks">https://blog.aks.azure.com/2026/04/01/dranet-rdma-optimization-for-ai-on-aks&lt;/a>.&lt;/li>
&lt;li>Linux network tuning — IRQ affinity, RSS/RPS/RFS y softirq NUMA: &lt;a href="https://andreaskaris.github.io/blog/networking/rss-irq-affinity-and-rps/">https://andreaskaris.github.io/blog/networking/rss-irq-affinity-and-rps/&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>