<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Interconnect on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/interconnect/</link><description>Recent content in Interconnect on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Sat, 06 Jun 2026 07:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/interconnect/index.xml" rel="self" type="application/rss+xml"/><item><title>La mesa compartida: NVLink, NVSwitch y NCCL, el cable por el que pasa cada token en tensor parallel</title><link>https://blog.lo0.es/posts/nvlink-nvswitch-nccl-tensor-parallel/</link><pubDate>Sat, 06 Jun 2026 07:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/nvlink-nvswitch-nccl-tensor-parallel/</guid><description>&lt;blockquote>
&lt;p>Este post baja un piso por debajo del motor. En el &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">stack de inferencia en siete capas&lt;/a> y en &lt;a href="https://blog.lo0.es/posts/tp-replicas-una-grande-vs-n-pequenas/">una grande vs N pequeñas&lt;/a> se decidía &lt;em>cuántas&lt;/em> GPUs y &lt;em>cómo&lt;/em> repartir el modelo; aquí se explica el &lt;strong>cable&lt;/strong> que hace que ese reparto funcione —o que lo estrangule. Es el primero de una mini-serie &amp;ldquo;por debajo del motor&amp;rdquo;: interconnect (este) → kernel y NUMA → resource managers de Kubernetes.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>&lt;strong>Tensor parallelism (TP) no parte un modelo en cuatro trozos que corren solos.&lt;/strong> Reparte cada capa entre las GPUs, pero después de la atención y después del MLP las GPUs tienen que &lt;strong>sumar sus resultados parciales&lt;/strong> con un &lt;em>all-reduce&lt;/em>. En un Llama-70B con 80 capas, eso son ~160 all-reduces &lt;strong>por cada token generado&lt;/strong>. Ese all-reduce viaja por el interconnect, así que el interconnect está en la &lt;strong>ruta crítica de cada token&lt;/strong>, no en la fontanería de fondo. En un baseboard HGX H100, las 8 GPUs hablan todas-con-todas a &lt;strong>900 GB/s bidireccionales&lt;/strong> vía cuatro &lt;strong>NVSwitch&lt;/strong>; sin NVSwitch/NVLink, ese mismo tráfico cae al CPU vía PCIe y pierde un orden de magnitud. &lt;strong>NCCL&lt;/strong> es la librería que decide cómo se hace cada colectivo (ring, tree, o &lt;strong>NVLS&lt;/strong> = NVLink-SHARP, que descarga la suma en el propio switch). Y hay una asimetría que casi nadie tiene en la cabeza: &lt;strong>el decode es latency-bound&lt;/strong> (mensajes diminutos, 16 KB) y &lt;strong>el prefill es bandwidth-bound&lt;/strong> (activaciones enormes batcheadas) — por eso &amp;ldquo;más ancho de banda NVLink&amp;rdquo; acelera el prefill pero apenas toca el decode token-a-token. Este post explica el mecanismo, da los 10 knobs reales de NCCL/driver donde se toca, y conecta con el custom all-reduce de vLLM, el disaggregated serving y la observabilidad GPU. Con escepticismo sobre qué palancas mueven la aguja.&lt;/p>
&lt;h2 id="dónde-estás-el-piso-por-debajo-del-motor">Dónde estás: el piso por debajo del motor&lt;/h2>
&lt;div class="diagram" style="max-width:560px;margin:1.2rem auto;">
&lt;svg viewBox="0 0 560 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Capas del stack: estás en el interconnect, debajo del motor">
&lt;text x="280" y="24" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">El stack vertical · estás en el cable&lt;/text>
&lt;rect x="120" y="40" width="320" height="40" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.3"/>
&lt;text x="280" y="65" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">Modelo · pesos, quantization, KV cache&lt;/text>
&lt;rect x="120" y="86" width="320" height="40" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.3"/>
&lt;text x="280" y="111" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">Motor · vLLM / SGLang (TP, PP, batching)&lt;/text>
&lt;rect x="120" y="132" width="320" height="40" rx="6" fill="#eef3fb" stroke="#4a6fa5" stroke-width="1.4"/>
&lt;text x="280" y="157" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">CUDA · kernels + NCCL (colectivos)&lt;/text>
&lt;rect x="120" y="178" width="320" height="52" rx="6" fill="#dceede" stroke="#3c8c54" stroke-width="3"/>
&lt;text x="280" y="200" text-anchor="middle" font-family="sans-serif" font-size="12.5" font-weight="700" fill="#1f5c34">ESTÁS AQUÍ · NVLink + NVSwitch&lt;/text>
&lt;text x="280" y="218" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="#2c6b42">el interconnect físico entre GPUs&lt;/text>
&lt;rect x="120" y="236" width="320" height="40" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.3"/>
&lt;text x="280" y="261" text-anchor="middle" font-family="sans-serif" font-size="12" fill="#333">Hardware · GPU H100 SXM, HBM3, SM&lt;/text>
&lt;text x="280" y="298" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="#777">cada all-reduce del motor de arriba cruza esta capa, por token&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-cuatro-mecánicos-y-una-sola-mesa">La analogía: cuatro mecánicos y una sola mesa&lt;/h2>
&lt;p>Cuatro mecánicos montan &lt;strong>un mismo motor de coche&lt;/strong>. No es que cada uno monte su propio motor en paralelo —eso sería tener cuatro coches (cuatro réplicas del modelo, otra estrategia). Aquí montan &lt;strong>uno solo, a la vez&lt;/strong>, repartiéndose las piezas: uno hace los pistones, otro la culata, otro el cigüeñal. El problema es que las piezas encajan entre sí: antes de seguir, &lt;strong>los cuatro tienen que juntar lo que llevan y comprobar que casa&lt;/strong>. Ese &amp;ldquo;juntar y comprobar&amp;rdquo; pasa decenas de veces durante el montaje.&lt;/p>
&lt;p>Hay dos formas de organizar el taller:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Una sola mesa grande, todos alrededor (NVSwitch).&lt;/strong> Cada mecánico alarga el brazo y pasa su pieza directamente a cualquier otro, todos a la vez, sin levantarse. Es instantáneo y simultáneo. Esto es &lt;strong>NVLink + NVSwitch&lt;/strong>: las GPUs forman un &lt;em>all-to-all&lt;/em> donde cualquiera habla con cualquiera a 900 GB/s al mismo tiempo.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Cuatro talleres separados con un mensajero (PCIe vía CPU).&lt;/strong> Cada pieza que un mecánico quiere pasar a otro va metida en una caja, baja a recepción (la memoria del host, vía CPU), y de ahí sube al taller destino. Más lento, y serializado por la recepción. Esto es lo que ocurre cuando &lt;strong>no hay NVLink&lt;/strong>: el tráfico inter-GPU cae a PCIe y rebota por el CPU, ~14× más lento que NVLink.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>La tesis del post se deriva sola: &lt;strong>tensor parallelism solo tiene sentido si los mecánicos comparten la mesa.&lt;/strong> En cuanto el &amp;ldquo;juntar y comprobar&amp;rdquo; (el all-reduce) tiene que pasar por la recepción, el reparto del trabajo cuesta más de lo que ahorra. Por eso, en una plataforma seria, TP &lt;strong>no cruza el límite del NVLink&lt;/strong>: TP=4 u 8 &lt;em>dentro&lt;/em> del baseboard donde hay NVSwitch, y de ahí para arriba se replica o se usa pipeline, nunca se estira TP por PCIe o por red. Cuándo conviene cada cosa está en &lt;a href="https://blog.lo0.es/posts/tp-replicas-una-grande-vs-n-pequenas/">una grande vs N pequeñas&lt;/a>; aquí explicamos &lt;em>por qué&lt;/em> el cable manda esa decisión.&lt;/p>
&lt;h2 id="el-mecanismo-qué-es-realmente-un-all-reduce-y-por-qué-hay-160-por-token">El mecanismo: qué es realmente un all-reduce y por qué hay 160 por token&lt;/h2>
&lt;p>Tensor parallelism parte las matrices de pesos por columnas/filas entre las $N$ GPUs. Cada GPU calcula una &lt;strong>porción&lt;/strong> de la salida de la capa. Pero la siguiente operación necesita la salida &lt;strong>completa&lt;/strong>, así que hay que recombinar. Esa recombinación es una &lt;strong>operación colectiva&lt;/strong>: un &lt;code>all-reduce&lt;/code>, que suma elemento a elemento los tensores parciales de todas las GPUs y deja el resultado &lt;strong>idéntico en todas&lt;/strong>.&lt;/p>
&lt;p>En un bloque transformer estándar hay &lt;strong>dos puntos de sincronización por capa&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>Tras la proyección de salida de la &lt;strong>atención&lt;/strong> (el &lt;code>o_proj&lt;/code> que recombina las cabezas repartidas).&lt;/li>
&lt;li>Tras la segunda matriz del &lt;strong>MLP&lt;/strong> (el &lt;code>down_proj&lt;/code> que recombina el feed-forward repartido).&lt;/li>
&lt;/ol>
&lt;p>$$ \text{all-reduces por token} = 2 \times L_{\text{capas}} $$&lt;/p>
&lt;p>Para un Llama-70B ($L = 80$): $2 \times 80 = 160$ all-reduces &lt;strong>por token generado&lt;/strong>. No por petición, no por secuencia: &lt;strong>por token&lt;/strong>. Multiplica por el throughput de decode y entiendes por qué el interconnect no es infraestructura de fondo sino ruta caliente.&lt;/p>
&lt;div class="diagram" style="max-width:800px;margin:1.2rem auto;">
&lt;svg viewBox="0 0 800 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="All-reduce en el bucle de decode de tensor parallel">
&lt;defs>&lt;marker id="nva" 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="400" y="22" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">Una capa transformer en TP=4 · dos all-reduce por capa&lt;/text>
&lt;!-- 4 GPUs -->
&lt;g>
&lt;rect x="40" y="50" width="150" height="40" rx="6" fill="#dceede" stroke="#3c8c54" stroke-width="1.4"/>&lt;text x="115" y="75" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">GPU0 · ¼ cabezas&lt;/text>
&lt;rect x="40" y="98" width="150" height="40" rx="6" fill="#dceede" stroke="#3c8c54" stroke-width="1.4"/>&lt;text x="115" y="123" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">GPU1 · ¼ cabezas&lt;/text>
&lt;rect x="40" y="146" width="150" height="40" rx="6" fill="#dceede" stroke="#3c8c54" stroke-width="1.4"/>&lt;text x="115" y="171" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">GPU2 · ¼ cabezas&lt;/text>
&lt;rect x="40" y="194" width="150" height="40" rx="6" fill="#dceede" stroke="#3c8c54" stroke-width="1.4"/>&lt;text x="115" y="219" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">GPU3 · ¼ cabezas&lt;/text>
&lt;/g>
&lt;!-- AllReduce 1 -->
&lt;rect x="240" y="70" width="120" height="144" rx="9" fill="#f7efda" stroke="#c79a32" stroke-width="2"/>
&lt;text x="300" y="130" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="700" fill="#222">ALL-REDUCE&lt;/text>
&lt;text x="300" y="148" text-anchor="middle" font-family="sans-serif" font-size="10" fill="#444">attn o_proj&lt;/text>
&lt;text x="300" y="164" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#777">(NVLink)&lt;/text>
&lt;!-- MLP -->
&lt;rect x="410" y="70" width="120" height="144" rx="6" fill="#eef3fb" stroke="#4a6fa5" stroke-width="1.4"/>
&lt;text x="470" y="135" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">MLP&lt;/text>
&lt;text x="470" y="152" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#444">(repartido)&lt;/text>
&lt;!-- AllReduce 2 -->
&lt;rect x="580" y="70" width="120" height="144" rx="9" fill="#f7efda" stroke="#c79a32" stroke-width="2"/>
&lt;text x="640" y="130" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="700" fill="#222">ALL-REDUCE&lt;/text>
&lt;text x="640" y="148" text-anchor="middle" font-family="sans-serif" font-size="10" fill="#444">mlp down_proj&lt;/text>
&lt;text x="640" y="164" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#777">(NVLink)&lt;/text>
&lt;!-- siguiente capa -->
&lt;rect x="720" y="98" width="60" height="88" rx="6" fill="#f4f4f4" stroke="#888" stroke-width="1.2"/>
&lt;text x="750" y="138" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="700" fill="#333">capa&lt;/text>
&lt;text x="750" y="152" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="700" fill="#333">i+1&lt;/text>
&lt;path d="M190,142 L240,142" fill="none" stroke="#666" stroke-width="1.5" marker-end="url(#nva)"/>
&lt;path d="M360,142 L410,142" fill="none" stroke="#666" stroke-width="1.5" marker-end="url(#nva)"/>
&lt;path d="M530,142 L580,142" fill="none" stroke="#666" stroke-width="1.5" marker-end="url(#nva)"/>
&lt;path d="M700,142 L720,142" fill="none" stroke="#666" stroke-width="1.5" marker-end="url(#nva)"/>
&lt;p>&lt;text x="400" y="262" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#444">× 80 capas = 160 all-reduce por token · cada uno cruza el interconnect&lt;/text>
&lt;text x="400" y="282" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="#999">si el cable es lento, el decode se desploma — el motor espera al cable&lt;/text>
&lt;/svg>&lt;/p>
&lt;/div>
&lt;h3 id="cómo-se-hace-el-all-reduce-ring-tree-nvls">Cómo se hace el all-reduce: ring, tree, NVLS&lt;/h3>
&lt;p>NCCL no tiene una sola forma de hacer un all-reduce; elige un &lt;strong>algoritmo&lt;/strong> según topología y tamaño del mensaje:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ring.&lt;/strong> Las GPUs forman un anillo; cada una pasa un trozo al vecino, suma, y rota. Hace falta dar $2(N-1)$ pasos. Es &lt;strong>óptimo en ancho de banda&lt;/strong> para mensajes grandes: el coste de mover los datos es $\frac{2(N-1)}{N} \times M$ bytes por el enlace, casi independiente de $N$. Lo malo: $2(N-1)$ saltos de latencia, malo para mensajes pequeños.&lt;/li>
&lt;li>&lt;strong>Tree.&lt;/strong> Reducción en árbol: $\log N$ niveles. &lt;strong>Mejor latencia&lt;/strong> para mensajes pequeños y muchos nodos, peor aprovechamiento de banda.&lt;/li>
&lt;li>&lt;strong>NVLS (NVLink SHARP).&lt;/strong> El truco de Hopper: la suma &lt;strong>no la hacen las GPUs, la hace el NVSwitch&lt;/strong>. El switch tiene unidades de reducción; las GPUs envían sus tensores, el switch los suma en tránsito y devuelve el resultado. Quita trabajo a las GPUs (libera SMs) y reduce saltos. Disponible &lt;strong>solo con NVSwitch de 3ª generación (NVLink4) + Hopper o superior&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>La regla mental: &lt;strong>decode (mensajes diminutos) quiere latencia → tree/LL o el custom kernel de vLLM; prefill (mensajes enormes) quiere banda → ring/NVLS&lt;/strong>. Por eso no hay un &amp;ldquo;NCCL_ALGO óptimo&amp;rdquo; global; depende de qué fase estés mirando.&lt;/p>
&lt;h2 id="las-matemáticas-que-importan-por-qué-decode-y-prefill-estresan-el-cable-al-revés">Las matemáticas que importan: por qué decode y prefill estresan el cable al revés&lt;/h2>
&lt;p>Aquí está la asimetría que casi todo el mundo se salta. El tamaño del tensor que se all-reducea en cada capa es, aproximadamente:&lt;/p>
&lt;p>$$ M \approx B \times S \times h \times 2\ \text{bytes (BF16)} $$&lt;/p>
&lt;p>donde $B$ = batch, $S$ = tokens procesados en este forward, $h$ = hidden size.&lt;/p>
&lt;p>&lt;strong>En decode&lt;/strong>, generas &lt;strong>1 token por secuencia&lt;/strong> por iteración. Para una sola secuencia ($B \times S = 1$) y $h = 8192$ (Llama-70B):&lt;/p>
&lt;p>$$ M_{\text{decode}} \approx 1 \times 8192 \times 2 = 16\ \text{KB por all-reduce} $$&lt;/p>
&lt;p>16 KB es &lt;strong>minúsculo&lt;/strong>. A 900 GB/s, mover 16 KB tarda ~18 &lt;strong>nanosegundos&lt;/strong> de transferencia pura —pero el coste real lo domina la &lt;strong>latencia de lanzamiento del colectivo&lt;/strong> (sincronización, kernel launch), del orden de &lt;strong>single-digit microsegundos&lt;/strong>. Con 160 all-reduces por token:&lt;/p>
&lt;p>$$ t_{\text{comms/token}} \approx 160 \times (5\text{–}10,\mu s) \approx 0{,}8\text{–}1{,}6\ \text{ms} $$&lt;/p>
&lt;p>Ese es el suelo de comunicación por token, &lt;strong>independiente del ancho de banda&lt;/strong>. Implicación incómoda y contraintuitiva: &lt;strong>comprar más ancho de banda NVLink no acelera el decode token-a-token de una sola secuencia.&lt;/strong> Lo que ayuda en decode es &lt;strong>bajar la latencia por colectivo&lt;/strong> (protocolo LL, el custom all-reduce de vLLM, NVLS para quitar saltos) y &lt;strong>batchear&lt;/strong> (subir $B$ amortiza la latencia fija sobre más tokens — la razón profunda por la que el continuous batching existe, cubierto en &lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">continuous batching&lt;/a>).&lt;/p>
&lt;p>&lt;strong>En prefill&lt;/strong>, procesas el prompt entero de golpe: $S$ puede ser miles de tokens, y con batching $B \times S$ llega a decenas de miles. Ahí:&lt;/p>
&lt;p>$$ M_{\text{prefill}} \approx 8000 \times 8192 \times 2 \approx 131\ \text{MB por all-reduce} $$&lt;/p>
&lt;p>131 MB &lt;strong>sí&lt;/strong> estresan el ancho de banda. A 900 GB/s (NVSwitch) el all-reduce ring mueve $\frac{2 \cdot 3}{4} \times 131 \approx 196$ MB efectivos en ~0,22 ms; por PCIe (~64 GB/s agregados, rebotando por CPU) serían &lt;strong>~3 ms y serializados&lt;/strong>. Aquí el cable es el cuello de botella y NVLS/banda mandan.&lt;/p>
&lt;p>Resumen en una línea: &lt;strong>prefill es bandwidth-bound, decode es latency-bound.&lt;/strong> Cualquier tuning del interconnect que no diga en qué fase ayuda es ruido.&lt;/p>
&lt;h2 id="el-hardware-nvlink-4-y-nvswitch-sobre-el-baseboard-hgx">El hardware: NVLink 4 y NVSwitch sobre el baseboard HGX&lt;/h2>
&lt;p>Sobre el cluster genérico de referencia —&lt;strong>4×H100 SXM&lt;/strong> dentro de un baseboard HGX— las cifras concretas:&lt;/p>
&lt;ul>
&lt;li>Cada &lt;strong>H100 SXM5&lt;/strong> tiene &lt;strong>18 enlaces NVLink 4&lt;/strong>, cada uno 50 GB/s bidireccionales ⇒ &lt;strong>900 GB/s bidireccionales agregados por GPU&lt;/strong>. Eso es &lt;strong>&amp;gt;14× el ancho de banda de un PCIe Gen4 x16&lt;/strong> (~64 GB/s bidir).&lt;/li>
&lt;li>En un baseboard HGX H100 de 8 GPUs, los 18 enlaces de cada GPU se reparten contra &lt;strong>cuatro NVSwitch&lt;/strong> de 3ª generación (agrupación 5+4+4+5). El resultado es &lt;strong>all-to-all&lt;/strong>: cualquier GPU habla con cualquier otra a 900 GB/s &lt;strong>simultáneamente&lt;/strong>, sin pasar por CPU ni PCIe.&lt;/li>
&lt;li>Un baseboard de 4 GPUs es media-placa: mismo principio, NVSwitch mediante. &lt;strong>Clave de diseño&lt;/strong>: si tus 4 H100 están conectadas por NVSwitch, tienes all-to-all real; si están en &lt;strong>placas distintas conectadas por PCIe&lt;/strong> (algunas configuraciones &amp;ldquo;4×PCIe&amp;rdquo;), &lt;strong>no tienes NVLink entre todas&lt;/strong> y TP=4 sufre. Verifícalo, no lo asumas.&lt;/li>
&lt;/ul>
&lt;div class="diagram" style="max-width:800px;margin:1.2rem auto;">
&lt;svg viewBox="0 0 800 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="NVSwitch all-to-all vs PCIe a través del CPU">
&lt;defs>&lt;marker id="nvt" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#3c8c54"/>&lt;/marker>&lt;/defs>
&lt;text x="200" y="24" text-anchor="middle" font-family="sans-serif" font-size="12.5" font-weight="700" fill="#1f5c34">Con NVSwitch · all-to-all 900 GB/s&lt;/text>
&lt;circle cx="120" cy="80" r="26" fill="#dceede" stroke="#3c8c54" stroke-width="1.6"/>&lt;text x="120" y="84" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G0&lt;/text>
&lt;circle cx="280" cy="80" r="26" fill="#dceede" stroke="#3c8c54" stroke-width="1.6"/>&lt;text x="280" y="84" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G1&lt;/text>
&lt;circle cx="120" cy="220" r="26" fill="#dceede" stroke="#3c8c54" stroke-width="1.6"/>&lt;text x="120" y="224" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G2&lt;/text>
&lt;circle cx="280" cy="220" r="26" fill="#dceede" stroke="#3c8c54" stroke-width="1.6"/>&lt;text x="280" y="224" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G3&lt;/text>
&lt;rect x="170" y="130" width="60" height="40" rx="6" fill="#f7efda" stroke="#c79a32" stroke-width="1.8"/>&lt;text x="200" y="155" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="700" fill="#222">NVSw&lt;/text>
&lt;path d="M138,98 L180,135" stroke="#3c8c54" stroke-width="1.6" fill="none"/>
&lt;path d="M262,98 L220,135" stroke="#3c8c54" stroke-width="1.6" fill="none"/>
&lt;path d="M138,202 L180,165" stroke="#3c8c54" stroke-width="1.6" fill="none"/>
&lt;path d="M262,202 L220,165" stroke="#3c8c54" stroke-width="1.6" fill="none"/>
&lt;path d="M120,106 L120,194" stroke="#3c8c54" stroke-width="1.2" fill="none" stroke-dasharray="3 2"/>
&lt;path d="M280,106 L280,194" stroke="#3c8c54" stroke-width="1.2" fill="none" stroke-dasharray="3 2"/>
&lt;line x1="420" y1="40" x2="420" y2="270" stroke="#ccc" stroke-width="1"/>
&lt;p>&lt;text x="610" y="24" text-anchor="middle" font-family="sans-serif" font-size="12.5" font-weight="700" fill="#a85454">Sin NVLink · PCIe vía CPU (~14× más lento)&lt;/text>
&lt;circle cx="530" cy="80" r="26" fill="#f3dede" stroke="#b35454" stroke-width="1.6"/>&lt;text x="530" y="84" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G0&lt;/text>
&lt;circle cx="690" cy="80" r="26" fill="#f3dede" stroke="#b35454" stroke-width="1.6"/>&lt;text x="690" y="84" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G1&lt;/text>
&lt;circle cx="530" cy="220" r="26" fill="#f3dede" stroke="#b35454" stroke-width="1.6"/>&lt;text x="530" y="224" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G2&lt;/text>
&lt;circle cx="690" cy="220" r="26" fill="#f3dede" stroke="#b35454" stroke-width="1.6"/>&lt;text x="690" y="224" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="700" fill="#222">G3&lt;/text>
&lt;rect x="580" y="130" width="60" height="40" rx="6" fill="#e8e8e8" stroke="#888" stroke-width="1.6"/>&lt;text x="610" y="155" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="700" fill="#333">CPU&lt;/text>
&lt;path d="M548,98 L590,135" stroke="#b35454" stroke-width="1.4" fill="none"/>
&lt;path d="M672,98 L630,135" stroke="#b35454" stroke-width="1.4" fill="none"/>
&lt;path d="M548,202 L590,165" stroke="#b35454" stroke-width="1.4" fill="none"/>
&lt;path d="M672,202 L630,165" stroke="#b35454" stroke-width="1.4" fill="none"/>
&lt;text x="610" y="292" text-anchor="middle" font-family="sans-serif" font-size="10" font-style="italic" fill="#999">todo el tráfico inter-GPU rebota por la memoria del host, serializado&lt;/text>
&lt;/svg>&lt;/p>
&lt;/div>
&lt;h2 id="los-10-knobs-donde-tocar">Los 10 knobs donde tocar&lt;/h2>
&lt;p>Casi todos son variables de entorno de &lt;strong>NCCL&lt;/strong> (se inyectan en el proceso del motor de inferencia) o ajustes de &lt;strong>driver&lt;/strong>. Ordenados por impacto/frecuencia en un despliegue on-premise. El detalle canónico está en la &lt;a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/env.html">doc de env vars de NCCL&lt;/a>.&lt;/p>
&lt;h3 id="knob-1--nccl_debug--topology-dump-ver-qué-está-pasando-antes-de-tocar-nada">Knob 1 — &lt;code>NCCL_DEBUG&lt;/code> + topology dump: ver qué está pasando antes de tocar nada&lt;/h3>
&lt;p>No optimices a ciegas: &lt;strong>primero confirma qué topología y algoritmos eligió NCCL&lt;/strong>. Esto te dice si de verdad está usando NVLink o si, en silencio, cayó a PCIe/SHM —el fallo nº1 y el más caro.&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="nv">NCCL_DEBUG&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="c1"># imprime topología, rings/trees construidos, transporte elegido&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_DEBUG_SUBSYS&lt;/span>&lt;span class="o">=&lt;/span>GRAPH,TUNING,NET &lt;span class="c1"># acota a lo que importa&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># busca en el log: &amp;#34;via NVLink&amp;#34; / &amp;#34;via P2P&amp;#34; (bien) vs &amp;#34;via SHM&amp;#34; / &amp;#34;via PCI&amp;#34; (mal)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Si ves &lt;code>via SHM&lt;/code> o &lt;code>via PCI&lt;/code> entre GPUs que deberían tener NVLink, &lt;strong>tienes un problema de topología&lt;/strong> (ACS de PCIe activo, IOMMU, GPUs en placas distintas) y ningún otro knob lo arregla. Este es el knob 1 por una razón: la mitad de los &amp;ldquo;NVLink va lento&amp;rdquo; son &amp;ldquo;NVLink no se está usando&amp;rdquo;.&lt;/p>
&lt;h3 id="knob-2--nccl_algo-ring-vs-tree-vs-nvls">Knob 2 — &lt;code>NCCL_ALGO&lt;/code>: ring vs tree vs NVLS&lt;/h3>
&lt;p>Fuerza o excluye algoritmos. Por defecto NCCL elige según tamaño, y suele acertar; tócalo solo con medición delante.&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="nv">NCCL_ALGO&lt;/span>&lt;span class="o">=&lt;/span>NVLS,Tree,Ring &lt;span class="c1"># orden de preferencia&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_ALGO&lt;/span>&lt;span class="o">=&lt;/span>^Ring &lt;span class="c1"># excluir Ring (prefijo ^)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Regla: prefill/entrenamiento (banda) ⇒ Ring/NVLS; decode (latencia) ⇒ Tree o, mejor, el custom kernel de vLLM (knob 10/stack). En la mayoría de inferencia, &lt;strong>dejarlo en auto y validar con el knob 1 es lo correcto&lt;/strong>; forzarlo &amp;ldquo;por si acaso&amp;rdquo; suele empeorar.&lt;/p>
&lt;h3 id="knob-3--nccl_proto-ll--ll128--simple">Knob 3 — &lt;code>NCCL_PROTO&lt;/code>: LL / LL128 / Simple&lt;/h3>
&lt;p>El protocolo controla el trade-off latencia/banda a bajo nivel:&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="nv">NCCL_PROTO&lt;/span>&lt;span class="o">=&lt;/span>Simple &lt;span class="c1"># máxima banda, más latencia (mensajes grandes)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_PROTO&lt;/span>&lt;span class="o">=&lt;/span>LL &lt;span class="c1"># low-latency, half-bandwidth (mensajes diminutos: decode)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_PROTO&lt;/span>&lt;span class="o">=&lt;/span>LL128 &lt;span class="c1"># compromiso, default en plataformas que lo soportan&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>LL&lt;/code> (low-latency) usa flags en vez de barreras y gana en los mensajes de 16 KB del decode; &lt;code>Simple&lt;/code> gana en los 131 MB del prefill. El default &lt;code>LL,LL128,Simple&lt;/code> deja a NCCL elegir por tamaño —de nuevo, normalmente lo mejor.&lt;/p>
&lt;h3 id="knob-4--nccl_nvls_enable-descargar-la-suma-en-el-nvswitch">Knob 4 — &lt;code>NCCL_NVLS_ENABLE&lt;/code>: descargar la suma en el NVSwitch&lt;/h3>
&lt;p>NVLink SHARP (NVLS) hace que el switch reduzca, liberando SMs de las GPUs:&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="nv">NCCL_NVLS_ENABLE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="c1"># default: ON donde hay NVSwitch NVLink4+ (Hopper)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Matiz escéptico importante&lt;/strong>: NVLS &lt;strong>requiere NVSwitch&lt;/strong> (3ª gen, NVLink4). En un nodo con NVLink por &lt;em>bridges&lt;/em> directos GPU-a-GPU (sin switch) o en 4×PCIe, &lt;strong>NVLS no está disponible&lt;/strong> y este knob no hace nada. Antes de &amp;ldquo;activarlo&amp;rdquo;, confirma con el knob 1 que tu topología tiene switch. Donde aplica, su mayor ventaja es liberar SMs para el cómputo —relevante cuando comms y kernels compiten (knob 5).&lt;/p>
&lt;h3 id="knob-5--nccl_min_nchannels--nccl_max_nchannels-cuántos-sm-roba-la-comunicación">Knob 5 — &lt;code>NCCL_MIN_NCHANNELS&lt;/code> / &lt;code>NCCL_MAX_NCHANNELS&lt;/code>: cuántos SM roba la comunicación&lt;/h3>
&lt;p>Cada &amp;ldquo;channel&amp;rdquo; de NCCL consume &lt;strong>SMs&lt;/strong> de la GPU para mover datos. Más channels = más ancho de banda de colectivo, pero &lt;strong>menos SMs para el kernel de inferencia&lt;/strong>. Es un reparto de un recurso fijo.&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="nv">NCCL_MIN_NCHANNELS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_MAX_NCHANNELS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">16&lt;/span> &lt;span class="c1"># subir ayuda al prefill (banda); roba SMs al decode&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>En decode, donde la GPU está infrautilizada de cómputo pero atada a latencia, recortar channels rara vez duele y a veces ayuda; en prefill, más channels exprimen la banda. Knob de medición, no de fe.&lt;/p>
&lt;h3 id="knob-6--nccl_buffsize-el-tamaño-del-buffer-por-channel">Knob 6 — &lt;code>NCCL_BUFFSIZE&lt;/code>: el tamaño del buffer por channel&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="nv">NCCL_BUFFSIZE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">8388608&lt;/span> &lt;span class="c1"># 8 MB (default 4 MB); buffers mayores → mejor BW en mensajes grandes&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Subirlo ayuda al prefill bandwidth-bound a costa de memoria por channel. Para cargas dominadas por mensajes pequeños (decode puro), el default sobra.&lt;/p>
&lt;h3 id="knob-7--nccl_p2p_level--nccl_p2p_disable-garantizar-p2p-sobre-nvlink">Knob 7 — &lt;code>NCCL_P2P_LEVEL&lt;/code> / &lt;code>NCCL_P2P_DISABLE&lt;/code>: garantizar P2P sobre NVLink&lt;/h3>
&lt;p>P2P es lo que permite que una GPU lea la memoria de otra directamente por NVLink sin pasar por el host. Si se desactiva o degrada, el tráfico cae a SHM/PCIe.&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="nv">NCCL_P2P_LEVEL&lt;/span>&lt;span class="o">=&lt;/span>NVL &lt;span class="c1"># usa P2P hasta el nivel NVLink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># NCCL_P2P_DISABLE=1 ← solo como workaround si P2P CUELGA (PCIe multi-NUMA, ciertas Blackwell)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Atención a la trampa: &lt;code>NCCL_P2P_DISABLE=1&lt;/code> y &lt;code>--disable-custom-all-reduce&lt;/code> se recomiendan como &lt;strong>parche&lt;/strong> cuando vLLM se cuelga en topologías PCIe-only multi-NUMA. Es un parche de &lt;strong>robustez que sacrifica rendimiento&lt;/strong>: úsalo si cuelga, nunca &amp;ldquo;por defecto&amp;rdquo;.&lt;/p>
&lt;h3 id="knob-8--gpudirect-rdma-para-multinodo-nccl_net_gdr_level">Knob 8 — GPUDirect RDMA para multinodo: &lt;code>NCCL_NET_GDR_LEVEL&lt;/code>&lt;/h3>
&lt;p>Cuando el TP cabe en un nodo, esto no aplica. Cuando hay que cruzar nodos (modelo enorme, pipeline parallel entre baseboards), GPUDirect RDMA permite que la GPU hable con la NIC &lt;strong>sin rebotar por la memoria del host&lt;/strong>:&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="nv">NCCL_NET_GDR_LEVEL&lt;/span>&lt;span class="o">=&lt;/span>PHB &lt;span class="c1"># habilita GDR según cercanía GPU–NIC en el bus PCIe&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sin GDR, cada salto inter-nodo añade una copia host. Con InfiniBand/RoCE + GDR, el KV o las activaciones viajan GPU→NIC→red→NIC→GPU. Es la base del multinodo serio y de &lt;a href="https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/">entornos mixtos&lt;/a>.&lt;/p>
&lt;h3 id="knob-9--nccl_ib_hca--nccl_socket_ifname-fijar-la-nic-correcta">Knob 9 — &lt;code>NCCL_IB_HCA&lt;/code> / &lt;code>NCCL_SOCKET_IFNAME&lt;/code>: fijar la NIC correcta&lt;/h3>
&lt;p>El error multinodo más común y silencioso: NCCL elige la &lt;strong>NIC de gestión&lt;/strong> (1 GbE) en vez de la de fabric (InfiniBand/100 GbE). Resultado: colectivos a paso de tortuga sin error visible.&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="nv">NCCL_SOCKET_IFNAME&lt;/span>&lt;span class="o">=&lt;/span>eth0 &lt;span class="c1"># interfaz de control (bootstrap)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_IB_HCA&lt;/span>&lt;span class="o">=&lt;/span>mlx5_0,mlx5_1 &lt;span class="c1"># las HCA InfiniBand reales del fabric&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">NCCL_IB_GID_INDEX&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="c1"># GID correcto para RoCE v2&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Fíjalas explícitamente. &amp;ldquo;Auto&amp;rdquo; acierta en clusters limpios y falla en cuanto hay más de una NIC.&lt;/p>
&lt;h3 id="knob-10--driver-persistence-mode-clocks-y-contadores-de-error-nvlink">Knob 10 — Driver: persistence mode, clocks y contadores de error NVLink&lt;/h3>
&lt;p>Por debajo de NCCL, el driver tiene palancas y, sobre todo, &lt;strong>telemetría que hay que mirar&lt;/strong>:&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">nvidia-smi -pm &lt;span class="m">1&lt;/span> &lt;span class="c1"># persistence mode: evita re-init del driver (latencia/jitter)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi nvlink --status &lt;span class="c1"># ¿los 18 enlaces activos y a velocidad plena?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi nvlink -e &lt;span class="c1"># contadores de error/CRC por enlace&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ECC &lt;span class="c1"># errores de memoria que degradan en silencio&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Un enlace NVLink que negocia a media velocidad o acumula errores CRC degrada el all-reduce &lt;strong>sin lanzar ningún error&lt;/strong> —el sistema &amp;ldquo;funciona&amp;rdquo;, solo va más lento. Estos contadores son la diferencia entre diagnosticar en cinco minutos o perseguir un fantasma durante días. Se integran en DCGM (knob/stack: observabilidad).&lt;/p>
&lt;h3 id="tabla-resumen">Tabla resumen&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>#&lt;/th>
&lt;th>Knob&lt;/th>
&lt;th>Variable / comando&lt;/th>
&lt;th>Fase que ayuda&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>Diagnóstico topología&lt;/td>
&lt;td>&lt;code>NCCL_DEBUG=INFO&lt;/code> + &lt;code>SUBSYS=GRAPH&lt;/code>&lt;/td>
&lt;td>siempre, primero&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>Algoritmo colectivo&lt;/td>
&lt;td>&lt;code>NCCL_ALGO&lt;/code> (NVLS/Tree/Ring)&lt;/td>
&lt;td>según fase; auto suele ganar&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>Protocolo&lt;/td>
&lt;td>&lt;code>NCCL_PROTO&lt;/code> (LL/LL128/Simple)&lt;/td>
&lt;td>LL=decode, Simple=prefill&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>NVLink SHARP&lt;/td>
&lt;td>&lt;code>NCCL_NVLS_ENABLE=1&lt;/code>&lt;/td>
&lt;td>prefill; libera SMs (requiere NVSwitch)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>Channels (SMs)&lt;/td>
&lt;td>&lt;code>NCCL_MIN/MAX_NCHANNELS&lt;/code>&lt;/td>
&lt;td>+banda prefill / −robo SM decode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>6&lt;/td>
&lt;td>Buffer&lt;/td>
&lt;td>&lt;code>NCCL_BUFFSIZE&lt;/code>&lt;/td>
&lt;td>prefill bandwidth-bound&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>7&lt;/td>
&lt;td>P2P NVLink&lt;/td>
&lt;td>&lt;code>NCCL_P2P_LEVEL=NVL&lt;/code>&lt;/td>
&lt;td>crítico; disable solo si cuelga&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>GPUDirect RDMA&lt;/td>
&lt;td>&lt;code>NCCL_NET_GDR_LEVEL&lt;/code>&lt;/td>
&lt;td>multinodo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>9&lt;/td>
&lt;td>NIC de fabric&lt;/td>
&lt;td>&lt;code>NCCL_IB_HCA&lt;/code>/&lt;code>SOCKET_IFNAME&lt;/code>&lt;/td>
&lt;td>multinodo (evita NIC mgmt)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>10&lt;/td>
&lt;td>Driver + telemetría&lt;/td>
&lt;td>&lt;code>nvidia-smi -pm 1&lt;/code> / &lt;code>nvlink -e&lt;/code>&lt;/td>
&lt;td>jitter + diagnóstico silencioso&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&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>El interconnect no es una isla; toca casi todas las capas de arriba.&lt;/p>
&lt;p>&lt;strong>Con vLLM — el custom all-reduce.&lt;/strong> vLLM no siempre usa NCCL: para los mensajes diminutos del decode (&lt;code>world_size==2&lt;/code> o topología fully-connected por NVLink, por debajo de cierto &lt;code>max_size&lt;/code>) usa un &lt;strong>kernel propio de all-reduce&lt;/strong> que bate a NCCL en latencia —exactamente el cuello de botella del decode que vimos en las matemáticas. Cae a NCCL para mensajes grandes y para topologías sin NVLink (donde su custom kernel &amp;ldquo;aporta poco sobre NCCL&amp;rdquo;). El flag &lt;code>--disable-custom-all-reduce&lt;/code> / &lt;code>VLLM_DISABLE_CUSTOM_ALL_REDUCE&lt;/code> lo apaga; es el parche cuando cuelga en PCIe multi-NUMA. Traducción: &lt;strong>el knob de latencia de decode más efectivo a veces no es de NCCL, es elegir bien entre el custom kernel de vLLM y NCCL.&lt;/strong>&lt;/p>
&lt;p>&lt;strong>Con TP vs réplicas.&lt;/strong> Todo lo de &lt;a href="https://blog.lo0.es/posts/tp-replicas-una-grande-vs-n-pequenas/">una grande vs N pequeñas&lt;/a> descansa sobre esto: TP alto solo es viable dentro del dominio NVLink. La frontera de &amp;ldquo;¿TP=4 o 4 réplicas TP=1?&amp;rdquo; la dibuja el cable: cruzar NVLink con TP es pagar el all-reduce a precio de PCIe.&lt;/p>
&lt;p>&lt;strong>Con disaggregated serving.&lt;/strong> En &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">prefill/decode desagregado&lt;/a>, el KV cache generado en el pool de prefill tiene que viajar al pool de decode. Ese traslado es &lt;strong>otro consumidor del interconnect&lt;/strong> (NVLink intra-nodo, GPUDirect RDMA inter-nodo) y compite con los all-reduce. Diseñar la desagregación sin contar el coste de transferencia de KV es la trampa clásica.&lt;/p>
&lt;p>&lt;strong>Con MoE.&lt;/strong> Los modelos Mixture-of-Experts añaden &lt;strong>expert parallelism&lt;/strong>: un &lt;code>all-to-all&lt;/code> (no all-reduce) que enruta cada token a su experto, posiblemente en otra GPU. Es un patrón de comunicación distinto y más pesado en banda; &lt;a href="https://blog.lo0.es/posts/moe-inference-fundamentos/">MoE en inferencia&lt;/a> vive o muere por el mismo cable, con un colectivo aún más exigente.&lt;/p>
&lt;p>&lt;strong>Con la observabilidad GPU.&lt;/strong> Los contadores NVLink (&lt;code>nvidia-smi nvlink -e&lt;/code>, bytes TX/RX por enlace, errores CRC) y la utilización de NVSwitch se exponen vía &lt;strong>DCGM&lt;/strong> y aterrizan en Prometheus/Grafana. La pregunta &amp;ldquo;¿está el interconnect sano y saturado?&amp;rdquo; se responde ahí, junto al resto de &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">observabilidad GPU con DCGM&lt;/a>. Un all-reduce lento se ve antes en un contador de errores NVLink que en la latencia de la API.&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 de inferencia&lt;/a> que asume &amp;ldquo;TP=4 escala casi lineal&amp;rdquo; &lt;strong>solo se cumple dentro del NVLink&lt;/strong>. Fuera de él, la eficiencia de escalado se cae y el plan de capacidad miente. El cable es un parámetro del modelo de capacidad, no un detalle.&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>&amp;ldquo;Más ancho de banda NVLink = decode más rápido.&amp;rdquo;&lt;/strong> Falso para una secuencia. El decode es latency-bound; el ancho de banda apenas se toca con mensajes de 16 KB. Lo que acelera el decode es batchear (amortizar la latencia fija) y bajar latencia por colectivo (LL, custom kernel, NVLS). El ancho de banda manda en prefill.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Tengo 4 H100, luego tengo NVLink entre las cuatro.&amp;rdquo;&lt;/strong> No necesariamente. Hay configuraciones donde las GPUs están en placas distintas unidas por PCIe, o con bridges NVLink solo por pares. Confírmalo con &lt;code>nvidia-smi nvlink --status&lt;/code> y el knob 1 &lt;strong>antes&lt;/strong> de planificar TP=4. Un TP=4 sobre P2P-por-PCIe rinde mucho peor de lo que dice el folleto.&lt;/p>
&lt;p>&lt;strong>Forzar &lt;code>NCCL_ALGO&lt;/code>/&lt;code>NCCL_PROTO&lt;/code> &amp;ldquo;para ir más rápido&amp;rdquo;.&lt;/strong> NCCL elige bien por tamaño en la mayoría de casos. Forzar un algoritmo sin medir suele empeorar una de las dos fases. La secuencia correcta es: knob 1 (ver qué hace) → medir → tocar solo si hay evidencia.&lt;/p>
&lt;p>&lt;strong>Desactivar P2P/custom all-reduce por defecto.&lt;/strong> Son parches de robustez para topologías rotas (PCIe multi-NUMA, ciertas Blackwell). Dejarlos puestos &amp;ldquo;por estabilidad&amp;rdquo; en un nodo con NVLink sano tira rendimiento a la basura.&lt;/p>
&lt;p>&lt;strong>Estirar TP por la red.&lt;/strong> TP=8 cruzando dos nodos por InfiniBand porque &amp;ldquo;hay banda&amp;rdquo; ignora que el all-reduce por capa ahora paga latencia de red ×160 por token. Para cruzar nodos, &lt;strong>pipeline parallel&lt;/strong> (que comunica una vez por micro-batch, no por capa) casi siempre gana. El patrón de comunicación, no solo la banda, decide.&lt;/p>
&lt;p>&lt;strong>Ignorar los contadores de error NVLink.&lt;/strong> Un enlace degradado no lanza excepción: el sistema funciona, solo va lento. Sin vigilar &lt;code>nvlink -e&lt;/code> y ECC, persigues un fantasma de rendimiento que un contador te habría señalado en cinco minutos.&lt;/p>
&lt;h2 id="conclusión">Conclusión&lt;/h2>
&lt;p>Tensor parallelism vende una promesa simple —parte el modelo, multiplica la VRAM, sirve modelos que no caben en una GPU— pero la letra pequeña es que cada capa obliga a las GPUs a juntarse y sumar, dos veces, decenas de veces por token. Ese all-reduce es el verdadero protagonista oculto del rendimiento, y vive en el cable: NVLink lo hace por la mesa compartida del NVSwitch a 900 GB/s, o PCIe lo arrastra por la recepción del CPU 14× más lento. De los diez knobs, el primero —&lt;strong>mirar con &lt;code>NCCL_DEBUG&lt;/code> qué está pasando de verdad&lt;/strong>— resuelve la mitad de los problemas, porque la mitad de los &amp;ldquo;NVLink va lento&amp;rdquo; son &amp;ldquo;NVLink no se usa&amp;rdquo;. El resto son afinados que solo significan algo si sabes &lt;strong>en qué fase&lt;/strong> estás: prefill quiere banda (NVLS, Simple, channels, buffer), decode quiere latencia (LL, el custom kernel de vLLM, batching). Y por encima de todo, una idea que reordena la intuición: en inferencia on-premise, el interconnect no es fontanería que se instala y se olvida —es ruta caliente, parámetro de capacidad y, cuando se degrada en silencio, la causa raíz que ningún dashboard de la API te va a señalar si no miras los contadores del propio cable.&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/siete-capas-stack-inferencia-llm-on-premise/">El stack de inferencia LLM on-premise en siete capas&lt;/a> — el edificio completo donde el interconnect es el cimiento sobre el que se apoyan las siete capas; aquí se abre ese cimiento.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tp-replicas-una-grande-vs-n-pequenas/">Una grande vs N pequeñas: TP y réplicas&lt;/a> — la decisión de cuántas GPUs y cómo repartir el modelo; este post explica &lt;em>por qué&lt;/em> el límite del NVLink dibuja esa frontera.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving: prefill y decode separados&lt;/a> — el traslado del KV cache entre pools es otro consumidor del mismo interconnect que compite con los all-reduce.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> — la razón profunda por la que batchear acelera el decode es que amortiza la latencia fija del all-reduce sobre más tokens.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/decode-optimizaciones-vllm/">Optimizaciones de decode en vLLM&lt;/a> — la fase latency-bound donde el custom all-reduce de vLLM y el protocolo LL deciden el TPS por secuencia.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/moe-inference-fundamentos/">MoE en inferencia&lt;/a> — el expert parallelism añade un &lt;code>all-to-all&lt;/code> aún más exigente sobre el mismo cable.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU con DCGM&lt;/a> — dónde aterrizan los contadores NVLink y de NVSwitch para responder &amp;ldquo;¿está el interconnect sano y saturado?&amp;rdquo;.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning de inferencia on-premise&lt;/a> — por qué &amp;ldquo;TP escala casi lineal&amp;rdquo; solo es cierto dentro del dominio NVLink, y cómo el cable entra en el modelo de capacidad.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/">Entornos mixtos NVIDIA + Intel&lt;/a> — cuando se cruza el límite del nodo, GPUDirect RDMA sobre InfiniBand/RoCE sustituye a NVLink como medio del colectivo.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>NVIDIA, &lt;em>NVIDIA Hopper Architecture In-Depth&lt;/em> (NVLink 4, 900 GB/s): &lt;a href="https://developer.nvidia.com/blog/nvidia-hopper-architecture-in-depth/">https://developer.nvidia.com/blog/nvidia-hopper-architecture-in-depth/&lt;/a>.&lt;/li>
&lt;li>NVIDIA, &lt;em>Introducing NVIDIA HGX H100&lt;/em> (4× NVSwitch, all-to-all): &lt;a href="https://developer.nvidia.com/blog/introducing-nvidia-hgx-h100-an-accelerated-server-platform-for-ai-and-high-performance-computing/">https://developer.nvidia.com/blog/introducing-nvidia-hgx-h100-an-accelerated-server-platform-for-ai-and-high-performance-computing/&lt;/a>.&lt;/li>
&lt;li>NVIDIA, &lt;em>NCCL Environment Variables&lt;/em> (todos los knobs de este post): &lt;a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/env.html">https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/env.html&lt;/a>.&lt;/li>
&lt;li>NVIDIA, &lt;em>The NVLink-Network Switch&lt;/em> (Hot Chips 2022, NVLink SHARP): &lt;a href="https://hc34.hotchips.org/">https://hc34.hotchips.org/&lt;/a>.&lt;/li>
&lt;li>vLLM, &lt;em>Why does vLLM use a custom all-reduce method?&lt;/em> (discussion #6159) y &lt;code>custom_all_reduce.py&lt;/code>: &lt;a href="https://github.com/vllm-project/vllm/discussions/6159">https://github.com/vllm-project/vllm/discussions/6159&lt;/a>.&lt;/li>
&lt;li>NVIDIA, &lt;em>NCCL Multi-Node NVLink Tuning Guide&lt;/em>: &lt;a href="https://docs.nvidia.com/multi-node-nvlink-systems/multi-node-tuning-guide/nccl.html">https://docs.nvidia.com/multi-node-nvlink-systems/multi-node-tuning-guide/nccl.html&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>