ckg-mcp / gpu-inference.html
danyarm's picture
Upload gpu-inference.html with huggingface_hub
5c20df9 verified
Raw
History Blame Contribute Delete
46.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GPU → LLM Inference Full Stack — Knowledge Graph · Graphify.md</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#ffffff;--surface:#f8fafc;--border:#e2e8f0;
--text:#0f172a;--dim:#64748b;--muted:#94a3b8;
--nv:#76b900;--nv-lt:#a3e635;--nv-dim:rgba(118,185,0,0.08);--nv-bdr:rgba(118,185,0,0.3);
--t1:#009485;--t2:#7c3aed;--t3:#94a3b8;
--hdr:48px;--bot:190px;
}
html,body{width:100%;height:100%;overflow:hidden;background:var(--bg);
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:var(--text)}
/* HEADER */
#hdr{position:fixed;top:0;left:0;right:0;height:var(--hdr);
background:#ffffff;border-bottom:1px solid var(--border);
display:flex;align-items:center;gap:12px;padding:0 20px;z-index:100;
box-shadow:0 1px 3px rgba(0,0,0,.06)}
.h-logo{display:flex;align-items:center;gap:10px;flex-shrink:0}
.h-wordmark{font-size:13px;font-weight:800;letter-spacing:.01em;color:var(--nv)}
.h-dot{width:3px;height:3px;border-radius:50%;background:var(--border);flex-shrink:0}
.h-title{font-size:12px;font-weight:600;color:var(--text);white-space:nowrap}
.h-sub{font-size:9px;color:var(--dim);white-space:nowrap}
.h-fill{flex:1}
.tier-btns{display:flex;gap:5px}
.tbtn{padding:3px 10px;border-radius:5px;font-size:10px;font-weight:700;
border:1px solid;cursor:pointer;transition:all .15s;background:transparent}
.tbtn[data-tier="1"]{border-color:var(--nv);color:var(--nv)}
.tbtn[data-tier="2"]{border-color:var(--t2);color:var(--t2)}
.tbtn[data-tier="3"]{border-color:var(--border);color:var(--dim)}
.tbtn.on[data-tier="1"]{background:var(--nv);color:#fff}
.tbtn.on[data-tier="2"]{background:var(--t2);color:#fff}
.tbtn.on[data-tier="3"]{background:var(--dim);color:#fff}
.path-sel{background:#fff;border:1px solid var(--border);border-radius:5px;
color:var(--dim);font-size:10px;padding:3px 8px;cursor:pointer;margin-left:6px}
.path-sel option{background:#fff}
.h-sep{width:1px;height:20px;background:var(--border);flex-shrink:0}
.h-badge{font-size:9px;font-weight:700;padding:3px 8px;border-radius:4px;
background:var(--nv-dim);border:1px solid var(--nv-bdr);color:var(--nv)}
/* GRAPH */
#wrap{position:fixed;top:var(--hdr);left:0;right:0;bottom:var(--bot);background:var(--bg)}
svg{width:100%;height:100%}
/* NODE FLOAT */
#node-float{position:absolute;top:14px;left:14px;width:230px;
background:rgba(255,255,255,.97);border:1px solid var(--border);border-radius:9px;
padding:12px 14px;display:none;pointer-events:none;
box-shadow:0 4px 12px rgba(0,0,0,.09)}
.nf-name{font-size:13px;font-weight:700;color:var(--text);margin-bottom:3px;line-height:1.3}
.nf-type{font-size:8px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:5px}
.nf-desc{font-size:10px;color:var(--dim);line-height:1.55;margin-bottom:5px}
.nf-perf{font-size:9px;color:#b45309;font-weight:700}
.nf-tier{font-size:8px;color:var(--muted);margin-top:4px}
/* LEGEND */
#legend-float{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.97);
border:1px solid var(--border);border-radius:9px;padding:12px 14px;pointer-events:auto;
box-shadow:0 2px 8px rgba(0,0,0,.07)}
.lf-title{font-size:8px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
color:var(--dim);margin-bottom:8px}
.lf-row{display:flex;align-items:center;gap:7px;margin-bottom:5px;cursor:pointer;
padding:2px 4px;border-radius:4px;transition:background .1s}
.lf-row:hover{background:var(--surface)}
.lf-row.off{opacity:.3}
.lf-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.lf-label{font-size:9.5px;color:var(--text)}
.tier-strip{display:flex;gap:10px;margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}
.ts-item{display:flex;align-items:center;gap:5px;font-size:9px;color:var(--dim)}
/* BOTTOM STRIP */
#bot{position:fixed;bottom:0;left:0;right:0;height:var(--bot);
background:#fff;border-top:2px solid var(--border);
display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:0;overflow:hidden}
.bot-card{border-right:1px solid var(--border);padding:14px 18px;cursor:pointer;
transition:background .15s;position:relative;overflow:hidden}
.bot-card:last-child{border-right:none}
.bot-card:hover{background:var(--surface)}
.bot-card.active{background:rgba(118,185,0,.04);border-top:2px solid var(--nv);margin-top:-2px}
.bc-num{font-size:8px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
color:var(--dim);margin-bottom:4px}
.bc-title{font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px;line-height:1.3}
.bc-body{font-size:10px;color:var(--dim);line-height:1.6}
.bc-perf{font-size:9px;color:#b45309;font-weight:700;margin-top:6px}
.bc-stack{margin-top:8px}
.bc-sys{font-size:11px;font-weight:700;margin-bottom:2px}
.bc-req{font-size:9px;color:var(--dim);line-height:1.5;margin-bottom:4px}
.bc-req span{background:#f1f5f9;padding:1px 5px;border-radius:3px;font-family:monospace;font-size:8px;color:var(--text)}
</style>
</head>
<body>
<!-- HEADER -->
<div id="hdr">
<div>
<div class="h-title">GPU → LLM Inference Full Stack — Knowledge Graph</div>
<div class="h-sub">H100 · CUDA · FlashAttention · KV Cache · vLLM · SGLang · TRT-LLM · 85 nodes · 140+ edges · 9 domains</div>
</div>
<div class="h-fill"></div>
<div class="tier-btns">
<button class="tbtn on" data-tier="1">Tier 1</button>
<button class="tbtn on" data-tier="2">Tier 2</button>
<button class="tbtn on" data-tier="3">Tier 3</button>
</div>
<select class="path-sel" id="path-sel">
<option value="">All nodes</option>
<option value="hardware">Hardware path</option>
<option value="latency">Latency path</option>
<option value="throughput">Throughput path</option>
<option value="memory">Memory path</option>
</select>
<div class="h-sep"></div>
<div class="h-badge">85 nodes · 9 domains</div>
</div>
<!-- GRAPH -->
<div id="wrap">
<svg id="svg"></svg>
<div id="legend-float">
<div class="lf-title">Domains</div>
<div id="lf-rows"></div>
<div class="tier-strip">
<div class="ts-item"><svg width="11" height="11"><circle cx="5.5" cy="5.5" r="5.5" fill="#64748b" opacity=".6"/></svg>Tier 1</div>
<div class="ts-item"><svg width="8" height="8"><circle cx="4" cy="4" r="4" fill="#64748b" opacity=".4"/></svg>Tier 2</div>
<div class="ts-item"><svg width="6" height="6"><circle cx="3" cy="3" r="3" fill="#64748b" opacity=".25"/></svg>Tier 3</div>
</div>
</div>
<div id="node-float">
<div class="nf-name" id="nf-name"></div>
<div class="nf-type" id="nf-type"></div>
<div class="nf-desc" id="nf-desc"></div>
<div class="nf-perf" id="nf-perf"></div>
<div class="nf-tier" id="nf-tier"></div>
</div>
</div>
<!-- BOTTOM STRIP -->
<div id="bot">
<div class="bot-card" id="ins-0">
<div class="bc-num">Insight 1 · Hardware Foundation</div>
<div class="bc-title">Know your roofline before you optimize</div>
<div class="bc-body">H100: 989 TFLOP/s FP16, 3.35 TB/s HBM3. Ridge = 295 FLOP/byte. Decode is memory-bound; prefill is compute-bound. Every serving optimization is a response to this constraint. Profile the axis before you pick the technique.</div>
<div class="bc-perf">⬡ H100 vs A100: 3× attention throughput · NVLink 4.0: 900 GB/s all-to-all</div>
</div>
<div class="bot-card" id="ins-1">
<div class="bc-num">Insight 2 · Memory</div>
<div class="bc-title">The KV cache is the constraint. Everything else is a response</div>
<div class="bc-body">Decode is 95% memory-bandwidth bound — you pay KV read cost every step. PagedAttention cuts fragmentation to ~0%. MQA/GQA cuts KV size 8×. MLA (DeepSeek-V2) cuts it 8× further via low-rank compression. The whole serving stack traces back to this one bottleneck.</div>
<div class="bc-perf">⬡ PagedAttention: 20–60% → ~0% waste · Llama-3 70B GQA: 8× KV reduction</div>
</div>
<div class="bot-card" id="ins-2">
<div class="bc-num">Insight 3 · Scheduling</div>
<div class="bc-title">36.9× from scheduling tokens, not requests</div>
<div class="bc-body">Orca's 2022 insight: swap sequences at iteration level — finished slots refill immediately. No change to hardware, model, or quantization. Speculative decoding adds 2–4× latency reduction on top: draft K tokens fast, verify all K in one pass with tree attention. Same quality.</div>
<div class="bc-perf">⬡ Orca OSDI 2022: 36.9× vs static · EAGLE: 3× speedup on LLaMA-2 70B</div>
</div>
<div class="bot-card" id="ins-3">
<div class="bc-num">System Stacks · Why They're Co-dependent</div>
<div class="bc-title">You cannot deploy one without the full dependency chain</div>
<div class="bc-stack">
<div class="bc-sys" style="color:#166534">vLLM</div>
<div class="bc-req">requires <span>PagedAttention</span> + <span>continuous batching</span> + <span>FlashInfer</span> + <span>preemption</span></div>
<div class="bc-sys" style="color:#059669">SGLang</div>
<div class="bc-req">requires <span>RadixAttention</span> + <span>cascade attention</span> + <span>KV sharing</span> + <span>jump-forward</span></div>
<div class="bc-sys" style="color:#76b900">TensorRT-LLM</div>
<div class="bc-req">requires <span>FP8 / Transformer Engine</span> + <span>in-flight batching</span> + <span>WGMMA</span> + <span>tensor parallelism</span></div>
</div>
</div>
</div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
const DOMAINS = [
{id:'HW', label:'GPU Hardware', color:'#76b900', gx:.13, gy:.28},
{id:'EXEC', label:'CUDA Execution', color:'#2563eb', gx:.20, gy:.62},
{id:'KERN', label:'Attention Kernels', color:'#7c3aed', gx:.40, gy:.38},
{id:'KV', label:'KV Cache', color:'#009485', gx:.40, gy:.65},
{id:'BATCH', label:'Batching & Scheduling', color:'#059669', gx:.60, gy:.50},
{id:'OPT', label:'Quantization & Optim', color:'#c2410c', gx:.70, gy:.72},
{id:'SYS', label:'Serving Systems', color:'#166534', gx:.82, gy:.42},
{id:'PARA', label:'Parallelism', color:'#4338ca', gx:.68, gy:.18},
{id:'BENCH', label:'Metrics & Benchmarks', color:'#475569', gx:.55, gy:.85},
];
const domMap={};
DOMAINS.forEach(d=>domMap[d.id]=d);
const TR={1:11,2:7,3:5};
const NODES=[
// GPU Hardware
{id:'hw_h100', label:'NVIDIA H100', domain:'HW', tier:1, type:'CHIP', desc:'Hopper architecture. 80GB HBM3, 3.35 TB/s bandwidth, 989 TFLOP/s FP16, 4th-gen Tensor Cores with FP8 support. The defining inference hardware of 2024–2025.', perf:'989 TFLOP/s FP16 · 3.35 TB/s · 80GB HBM3'},
{id:'hw_a100', label:'NVIDIA A100', domain:'HW', tier:2, type:'CHIP', desc:'Ampere architecture. 80GB HBM2e, 2 TB/s, 312 TFLOP/s FP16. Reference baseline for most LLM inference benchmarks.', perf:'312 TFLOP/s FP16 · 2 TB/s · 80GB HBM2e'},
{id:'hw_te', label:'Transformer Engine', domain:'HW', tier:2, type:'HARDWARE', desc:'H100/A100 dedicated unit for FP8 matrix multiplications with per-tensor dynamic scaling. Enables 2× throughput vs FP16 with automatic precision management.', perf:'2× throughput vs FP16 on H100'},
{id:'hw_tc', label:'4th Gen Tensor Cores', domain:'HW', tier:1, type:'HARDWARE', desc:'H100: native FP8 GEMM. 1979 TFLOP/s TF32, 3958 TFLOP/s FP8. Warp-group matrix multiply (WGMMA) executes asynchronously via TMA.', perf:'3958 TFLOP/s FP8 · 2× FP16 ops vs A100'},
{id:'hw_hbm3', label:'HBM3 Memory (3.35 TB/s)', domain:'HW', tier:2, type:'HARDWARE', desc:'High-Bandwidth Memory Gen 3. 80GB, 3.35 TB/s bandwidth on H100. Every decode step reads the full KV cache — bandwidth is the binding constraint.', perf:'3.35 TB/s · 10% over A100 HBM2e'},
{id:'hw_nvlink', label:'NVLink 4.0', domain:'HW', tier:2, type:'INTERCONNECT',desc:'900 GB/s bidirectional GPU-GPU bandwidth (H100 DGX). Enables tensor parallelism within a node without PCIe bottleneck.', perf:'900 GB/s · 7× PCIe 5.0 bandwidth'},
{id:'hw_nvswitch',label:'NVSwitch', domain:'HW', tier:3, type:'HARDWARE', desc:'All-to-all GPU interconnect fabric. DGX H100: 8 GPUs each with 900 GB/s NVLink, all connected via NVSwitch. Eliminates inter-GPU bandwidth bottleneck.'},
{id:'hw_roofline',label:'Roofline Model', domain:'HW', tier:1, type:'MODEL', desc:'Memory BW ceiling vs compute FLOP/s ceiling. Arithmetic intensity (FLOP/byte) determines which bound applies. H100 ridge: ~295 FLOP/byte. Decode: memory-bound. Prefill: compute-bound.', perf:'H100 ridge point: 295 FLOP/byte'},
// CUDA Execution
{id:'ex_cuda', label:'CUDA Programming Model', domain:'EXEC', tier:1, type:'MODEL', desc:'Grid → Block → Thread hierarchy. SIMT: 32 threads execute same instruction in lockstep (warp). Foundation for all GPU kernel engineering.'},
{id:'ex_sm', label:'Streaming Multiprocessor',domain:'EXEC', tier:1, type:'HARDWARE', desc:'H100: 132 SMs. Each SM: 128 CUDA cores, 4 warp schedulers, up to 64 active warps, 228KB shared memory. WGMMA ops run per-SM.', perf:'H100: 132 SMs · 228KB SMEM per SM'},
{id:'ex_warp', label:'Warp / SIMT Execution', domain:'EXEC', tier:1, type:'CONCEPT', desc:'32 threads executing in lockstep. Warp divergence = serialized branches. Latency hiding: warp scheduler swaps stalled warps immediately — hides HBM latency behind compute.'},
{id:'ex_smem', label:'Shared Memory (SMEM)', domain:'EXEC', tier:2, type:'RESOURCE', desc:'On-chip scratchpad per SM. 228KB on H100. ~19× faster than HBM3. FlashAttention tiles KQ/V blocks into SMEM to eliminate O(N²) HBM writes.', perf:'~19× faster than HBM · 228KB per SM H100'},
{id:'ex_occ', label:'Occupancy', domain:'EXEC', tier:2, type:'METRIC', desc:'Active warps / max warps per SM. Limited by register file and SMEM usage per thread block. Higher occupancy hides latency better but may reduce per-warp resource budget.'},
{id:'ex_coal', label:'Memory Coalescing', domain:'EXEC', tier:2, type:'TECHNIQUE', desc:'32 threads in a warp accessing consecutive, aligned global memory addresses merge into a single 128-byte transaction. Non-coalesced access costs 32 separate transactions.'},
{id:'ex_async', label:'Async Copies (cp.async)', domain:'EXEC', tier:3, type:'INSTRUCTION', desc:'Transfer global → SMEM without stalling the warp. Overlaps compute and memory stages. Used in FlashAttention-3 software pipelining.'},
{id:'ex_graphs', label:'CUDA Graphs', domain:'EXEC', tier:3, type:'TECHNIQUE', desc:'Pre-record full kernel launch sequence as graph, replay without CPU overhead. Reduces kernel launch latency from ~5μs to <1μs per launch. Critical for small batches.'},
// Attention Kernels
{id:'k_mha', label:'Multi-Head Attention', domain:'KERN', tier:1, type:'ALGORITHM', desc:'Q×K^T÷√d → softmax → ×V per head. Naive: O(N²) HBM writes per layer per step. Prefill: compute-bound. Decode: single-query, memory-bandwidth-bound KV read.'},
{id:'k_fa2', label:'FlashAttention-2', domain:'KERN', tier:1, type:'ALGORITHM', desc:'IO-aware tiled attention. Tiles Q, K, V into SMEM blocks; online softmax eliminates O(N²) HBM. 2× FA1, 73% A100 peak FLOP/s on prefill. Used by all major serving systems.', perf:'73% A100 peak · 2× FA1 · O(N) HBM vs O(N²)'},
{id:'k_fa3', label:'FlashAttention-3', domain:'KERN', tier:1, type:'ALGORITHM', desc:'Hopper-specific: WGMMA async pipeline, FP8, overlapped GEMM+softmax. ~75% H100 peak on prefill. TMA-based SMEM transfers, 1.5–2× FA2.', perf:'~75% H100 FP16 peak · 1.5–2× FA2'},
{id:'k_wgmma', label:'WGMMA Instruction', domain:'KERN', tier:2, type:'INSTRUCTION', desc:'H100 Hopper: Warp-Group Matrix Multiply-Accumulate. Operates on warp-group register files asynchronously. Native to FlashAttention-3 and TRT-LLM kernels.'},
{id:'k_flashinfer',label:'FlashInfer', domain:'KERN', tier:1, type:'FRAMEWORK', desc:'Unified attention kernel library: paged + cascade + FA3 + KV quantization. MLSys 2025 Best Paper. Used by vLLM (v0.5+) and SGLang as drop-in attention backend.', perf:'MLSys 2025 Best Paper · all-in-one attention backend'},
{id:'k_cascade', label:'Cascade Attention', domain:'KERN', tier:2, type:'ALGORITHM', desc:'Two-pass attention: first pass over shared prefix KV (cached), second pass over unique suffix. Enables prefix KV reuse without reprocessing shared tokens in every request.'},
{id:'k_gqa_k', label:'GQA Decode Kernel', domain:'KERN', tier:2, type:'COMPONENT', desc:'Decode attention kernel optimized for grouped-query attention: broadcast fewer KV heads across Q groups. Reads fewer bytes from HBM per decode step vs full MHA.'},
{id:'k_paged_k', label:'Paged Attention Kernel', domain:'KERN', tier:3, type:'COMPONENT', desc:'Custom CUDA kernel for attention over non-contiguous block-table KV layout. Block-sparse by construction. Now largely replaced by FlashInfer in production stacks.'},
// KV Cache
{id:'kv_fund', label:'KV Cache', domain:'KV', tier:1, type:'CONCEPT', desc:'K and V tensors from attention stored per-layer per-token. Eliminates recomputation during autoregressive decoding. Central bottleneck: every decode step reads full KV across all layers.', perf:'Decode: 95% memory-bandwidth bound · ∝ seq_len × layers × heads'},
{id:'kv_mqa', label:'MQA / GQA', domain:'KV', tier:1, type:'TECHNIQUE', desc:'MQA: 1 KV head shared across all Q heads. GQA: groups of Q heads share one KV head. Llama-3 70B: 8 KV heads vs 64 Q = 8× KV size reduction on every decode read.', perf:'Llama-3 70B: 8× KV size reduction'},
{id:'kv_paged', label:'PagedAttention', domain:'KV', tier:1, type:'ALGORITHM', desc:'Non-contiguous KV storage via fixed-size blocks + block table. Eliminates internal and external fragmentation. Static allocation wastes 20–60% VRAM; PagedAttention → ~0%.', perf:'Fragmentation: 20–60% → ~0% · vLLM SOSP 2023'},
{id:'kv_block', label:'Block Table', domain:'KV', tier:2, type:'STRUCTURE', desc:'Per-sequence virtual-to-physical KV block mapping. Enables non-contiguous storage and copy-on-write prefix sharing. Core data structure of all paged-KV serving systems.'},
{id:'kv_cow', label:'Copy-on-Write KV Sharing',domain:'KV', tier:2, type:'TECHNIQUE', desc:'Multiple sequences share physical KV blocks for common prefixes (system prompt). Block copied only on write. Eliminates redundant KV computation for shared prefixes.'},
{id:'kv_radix', label:'Radix Tree Prefix Cache',domain:'KV', tier:2, type:'STRUCTURE', desc:'Trie of KV blocks keyed by token sequence. Automatic prefix reuse across requests. SGLang RadixAttention: 3–5× faster than vLLM on prefix-heavy workloads (chatbot, code).', perf:'SGLang: 3–5× vLLM on prefix-sharing workloads'},
{id:'kv_quant', label:'KV Quantization INT8/FP8',domain:'KV', tier:2, type:'TECHNIQUE', desc:'Quantize stored K, V tensors to INT8 or FP8. Halves KV memory footprint, doubles concurrent sequences at same VRAM. FlashInfer and TRT-LLM native support.', perf:'2× KV capacity · <0.5% quality degradation'},
{id:'kv_mla', label:'MLA — Multi-Head Latent',domain:'KV', tier:2, type:'ALGORITHM', desc:'DeepSeek-V2: compress K,V into a low-rank latent vector (joint KV compression). Decompress at inference time. 8× KV cache reduction vs standard MHA.', perf:'8× KV reduction · DeepSeek-V2 · low-rank joint compression'},
{id:'kv_offload',label:'KV Offload (CPU/NVMe)', domain:'KV', tier:3, type:'TECHNIQUE', desc:'Spill cold KV blocks to CPU RAM or NVMe when VRAM exhausted. Enables longer context or more concurrent sequences at PCIe bandwidth latency cost (~1–3ms per swap).'},
// Batching & Scheduling
{id:'b_cont', label:'Continuous Batching', domain:'BATCH', tier:1, type:'ALGORITHM', desc:'Iteration-level scheduling: swap finished sequences out, new ones in, at every decode step. GPU slots never idle. First demonstrated by Orca (OSDI 2022) with 36.9× over static batching.', perf:'36.9× over static batching (Orca OSDI 2022)'},
{id:'b_spec', label:'Speculative Decoding', domain:'BATCH', tier:1, type:'ALGORITHM', desc:'Draft model generates K candidates; target model verifies all K in one forward pass using tree attention. Accept tokens whose distribution matches target. 2–4× latency reduction, zero quality loss.', perf:'2–4× TPOT reduction · zero quality degradation'},
{id:'b_ifl', label:'In-Flight Batching', domain:'BATCH', tier:1, type:'ALGORITHM', desc:'TRT-LLM implementation of continuous batching. Integrates with NVIDIA Triton Inference Server for production deployment. Adds dynamic request management and priority handling.'},
{id:'b_chunked', label:'Chunked Prefill', domain:'BATCH', tier:2, type:'TECHNIQUE', desc:'Split long prompt prefills into chunks, interleave with decode steps. Prevents long prefills from head-of-line blocking decode for other users. Reduces P99 TTFT spikes.', perf:'Reduces P99 TTFT spikes on mixed workloads'},
{id:'b_iter', label:'Iteration-Level Scheduler',domain:'BATCH',tier:2, type:'COMPONENT', desc:'Per-decode-step decision: which sequences run, which are preempted, which new requests enter. Eliminates head-of-line blocking from static batching. Core of continuous batching.'},
{id:'b_preempt', label:'Preemption', domain:'BATCH', tier:2, type:'MECHANISM', desc:'When VRAM exhausted, evict lower-priority sequences. Two strategies: swap KV blocks to CPU RAM (add PCIe latency), or drop and recompute prefill. Enables VRAM oversubscription.'},
{id:'b_eagle', label:'EAGLE / Speculative Heads',domain:'BATCH',tier:2, type:'SYSTEM', desc:'EAGLE: auto-regressive draft head using target model feature vectors. Higher acceptance rate than separate small LM. EAGLE-2 adds dynamic draft depth. vLLM and SGLang supported.', perf:'EAGLE: 3× speedup on LLaMA-2 70B · EAGLE-2: 3.5×'},
{id:'b_disagg', label:'Prefill/Decode Disaggregation',domain:'BATCH',tier:3,type:'ARCHITECTURE',desc:'Separate GPU pools: prefill fleet (compute-bound, large batches) and decode fleet (memory-bound, small batches). Each optimized independently. Reduces prefill-decode resource conflict.'},
{id:'b_static', label:'Static Batching', domain:'BATCH', tier:3, type:'APPROACH', desc:'Group requests by max sequence length, run until all complete. GPU idles waiting for slowest sequence. Head-of-line blocking. Baseline for all throughput improvement measurements.'},
// Quantization & Optimization
{id:'o_fp8', label:'FP8 Inference', domain:'OPT', tier:1, type:'FORMAT', desc:'FP8 weights + activations on H100 Hopper/Blackwell via Transformer Engine. 2× throughput vs FP16. Native to TRT-LLM and DeepSeek. Requires careful per-tensor calibration.', perf:'2× throughput vs FP16 · H100 native'},
{id:'o_awq', label:'AWQ — Activation-Aware WQ',domain:'OPT', tier:2, type:'TECHNIQUE', desc:'INT4 weight-only quantization guided by activation magnitude. Protects salient channels. 4× weight compression, near-lossless quality, faster than GPTQ. Widely supported.', perf:'4× weight compression · <1% perplexity loss'},
{id:'o_gptq', label:'GPTQ', domain:'OPT', tier:2, type:'TECHNIQUE', desc:'Post-training INT4 quantization via second-order Hessian information. One-shot per-layer compression. Works well on 70B+ models. Slower than AWQ but better for very large models.', perf:'INT4 at near-FP16 quality on 70B+ models'},
{id:'o_sq', label:'SmoothQuant (W8A8)', domain:'OPT', tier:3, type:'TECHNIQUE', desc:'Migrate outlier quantization difficulty from activations to weights via per-channel scaling. Enables simultaneous INT8 weights + INT8 activations (W8A8) without quality loss.'},
{id:'o_sparsity',label:'Structured Sparsity (2:4)',domain:'OPT', tier:3, type:'TECHNIQUE', desc:'2 out of every 4 weights are zero. Ampere+ sparse tensor cores: 2× FLOP/s at 50% sparsity. Negligible quality loss at most model sizes when combined with distillation.'},
{id:'o_gguf', label:'GGUF / llama.cpp', domain:'OPT', tier:2, type:'FORMAT', desc:'CPU-first quantization format (Q4_K_M, Q5_K_S etc). Enables local inference on consumer hardware. Incompatible with GPU serving stacks but critical for local/edge deployment.'},
// Serving Systems
{id:'s_vllm', label:'vLLM', domain:'SYS', tier:1, type:'SYSTEM', desc:'Most widely deployed OSS LLM serving engine. Pioneered PagedAttention (SOSP 2023). Async continuous batching, tensor parallelism, FlashInfer backend, multi-model support.', perf:'SOSP 2023 Best Paper · PagedAttention pioneer'},
{id:'s_sglang', label:'SGLang', domain:'SYS', tier:1, type:'SYSTEM', desc:'RadixAttention + structured generation (grammar-constrained decoding). 3–5× faster than vLLM on prefix-heavy and structured output workloads. FlashInfer + cascade attention.', perf:'3–5× vLLM on prefix-sharing · MLSys 2025'},
{id:'s_trt', label:'TensorRT-LLM', domain:'SYS', tier:1, type:'SYSTEM', desc:'NVIDIA inference library. FP8 via Transformer Engine, kernel fusion, custom attention plugins, in-flight batching. Highest raw throughput on NVIDIA hardware for production workloads.', perf:'Highest H100 FLOP utilization · NVIDIA optimized'},
{id:'s_nim', label:'NVIDIA NIM', domain:'SYS', tier:2, type:'PRODUCT', desc:'Containerized TRT-LLM deployment packaged as enterprise inference microservice. Adds auth, telemetry, SLA monitoring, and multi-model routing on top of TRT-LLM.'},
{id:'s_triton', label:'Triton Inference Server',domain:'SYS', tier:2, type:'SYSTEM', desc:'NVIDIA production serving infrastructure. Multi-model routing, dynamic batching, gRPC/REST API, model versioning. TRT-LLM plugs in as backend. Used in NVIDIA NIM.'},
{id:'s_lmd', label:'LMDeploy / TurboMind', domain:'SYS', tier:2, type:'SYSTEM', desc:'MMLab serving engine. TurboMind kernel: efficient GQA decode attention. AWQ native. Popular for Qwen and InternLM model families. Faster than vLLM on GQA decode.'},
{id:'s_mlc', label:'MLC-LLM', domain:'SYS', tier:3, type:'SYSTEM', desc:'TVM compiler-based: compile any model to any target (GPU/CPU/mobile/browser). WebLLM runs full LLMs in browser via WebGPU. Flexible compilation but slower to iterate than vLLM.'},
// Parallelism
{id:'p_tp', label:'Tensor Parallelism', domain:'PARA', tier:1, type:'TECHNIQUE', desc:'Split attention heads and FFN weight matrices across GPUs (Megatron-style). 8-GPU H100: each GPU handles ~1/8 of attention. NVLink required for all-reduce at < 5ms.', perf:'Required for 70B+ on <8 GPUs · NVLink required'},
{id:'p_pp', label:'Pipeline Parallelism', domain:'PARA', tier:2, type:'TECHNIQUE', desc:'Split transformer layers into stages across GPUs. Stage N processes batch while Stage N+1 processes previous batch. Pipeline bubbles (idle time at stage boundaries) are wasted compute.'},
{id:'p_sp', label:'Sequence Parallelism', domain:'PARA', tier:2, type:'TECHNIQUE', desc:'Distribute sequence tokens across GPUs during attention. Reduces per-GPU activation memory for long contexts. Used alongside tensor parallelism in Megatron-LM.'},
{id:'p_multi', label:'Multi-GPU Inference', domain:'PARA', tier:1, type:'ARCHITECTURE',desc:'Serving 70B+ models across 2–8 GPUs via tensor and/or pipeline parallelism. H100 DGX node: 8× 80GB = 640GB capacity. NVLink 4.0 keeps all-reduce < 5ms at 70B scale.', perf:'H100 DGX: 8× 80GB = 640GB · NVLink < 5ms allreduce'},
{id:'p_ep', label:'Expert Parallelism (MoE)',domain:'PARA', tier:2, type:'TECHNIQUE', desc:'Mixture-of-Experts: route tokens to subset of expert FFN layers on different GPUs. Each GPU hosts a subset of experts. Used in Mixtral, DeepSeek-V3, GPT-4.'},
{id:'p_nccl', label:'NCCL AllReduce', domain:'PARA', tier:3, type:'LIBRARY', desc:'NVIDIA Collective Communications Library. Implements all-reduce, all-gather for tensor parallelism sync. Ring or tree algorithm. NVLink rings achieve near-peak bandwidth.'},
// Metrics
{id:'m_ttft', label:'TTFT — Time to First Token',domain:'BENCH',tier:1,type:'METRIC', desc:'Latency from request submit to first output token. Dominated by prefill compute. Long prompts = high TTFT. Chunked prefill and prefill/decode disaggregation are the levers.', perf:'P99 SLA target: <2s · chunked prefill reduces spikes'},
{id:'m_tpot', label:'TPOT / ITL', domain:'BENCH', tier:1, type:'METRIC', desc:'Time Per Output Token / Inter-Token Latency. Determined by KV cache memory bandwidth. TPOT ∝ KV cache size × 1/bandwidth. Speculative decoding and MQA/GQA are the levers.', perf:'TPOT ∝ KV size · P99 SLA target: <50ms'},
{id:'m_tput', label:'Throughput (tok/s)', domain:'BENCH', tier:1, type:'METRIC', desc:'Total output tokens/second across all concurrent requests. Primary metric for batch/offline workloads. Maximized by continuous batching + quantization + large batch sizes.'},
{id:'m_util', label:'GPU Utilization', domain:'BENCH', tier:2, type:'METRIC', desc:'SM active % across all decode steps. Decode: 40–60% typical (memory-bound). Prefill: 70–85%. Low utilization = memory-bound — optimize KV size and batching before hardware.'},
{id:'m_mfu', label:'Model FLOP Utilization', domain:'BENCH', tier:2, type:'METRIC', desc:'Observed FLOP/s / peak hardware FLOP/s. Decode MFU: 10–30%. Prefill MFU: 50–70%. Low decode MFU is expected (memory-bound) — not a sign of inefficiency in the serving stack.', perf:'Decode MFU: 10–30% (expected) vs prefill: 50–70%'},
{id:'m_sla', label:'SLA / P99 Latency', domain:'BENCH', tier:2, type:'CONCEPT', desc:'Production SLAs: P50 TTFT <500ms, P99 TTFT <2s, TPOT <50ms. Priority scheduling and chunked prefill are tools to meet P99 under variable load. SLA violation = customer churn.'},
{id:'m_tco', label:'Total Cost of Ownership', domain:'BENCH', tier:2, type:'METRIC', desc:'$/1M tokens fully loaded. H100 inference: ~$0.006/1k tokens vs RAG at ~$0.013/1k with CKG compression at ~$0.0005/1k correct answer. TCO drives serving architecture decisions.', perf:'CKG vs RAG: ~10× TCO per correct answer'},
{id:'m_bench', label:'Benchmark Suites', domain:'BENCH', tier:3, type:'TOOL', desc:'Standard: ShareGPT traces (real chat), synthetic Poisson arrivals, Arena conversations. vLLM and SGLang benchmark on same traces. Critical for apples-to-apples comparison.'},
];
const EDGES=[
// HW internal
{s:'hw_h100', t:'hw_te', r:'FEATURES'},
{s:'hw_h100', t:'hw_tc', r:'FEATURES'},
{s:'hw_h100', t:'hw_hbm3', r:'FEATURES'},
{s:'hw_h100', t:'hw_nvlink', r:'FEATURES'},
{s:'hw_a100', t:'hw_tc', r:'FEATURES_PREV_GEN'},
{s:'hw_nvlink',t:'hw_nvswitch',r:'AGGREGATED_BY'},
{s:'hw_h100', t:'hw_roofline',r:'CHARACTERIZED_BY'},
// EXEC internal
{s:'ex_cuda', t:'ex_sm', r:'EXECUTES_ON'},
{s:'ex_sm', t:'ex_warp', r:'CONTAINS'},
{s:'ex_sm', t:'ex_smem', r:'HAS'},
{s:'ex_warp', t:'ex_occ', r:'DETERMINES'},
{s:'ex_smem', t:'ex_coal', r:'COMPLEMENTS'},
{s:'ex_async', t:'ex_smem', r:'TARGETS'},
{s:'ex_graphs',t:'ex_cuda', r:'BUILDS_ON'},
// KERN internal
{s:'k_mha', t:'k_fa2', r:'OPTIMIZED_BY'},
{s:'k_fa2', t:'k_fa3', r:'EXTENDED_BY'},
{s:'k_fa3', t:'k_wgmma', r:'USES'},
{s:'k_paged_k',t:'k_flashinfer',r:'REPLACED_BY'},
{s:'k_cascade',t:'k_flashinfer',r:'IMPLEMENTED_IN'},
{s:'k_mha', t:'k_gqa_k', r:'OPTIMIZED_AS'},
{s:'k_fa2', t:'k_cascade', r:'COMPLEMENTED_BY'},
// KV internal
{s:'kv_fund', t:'kv_mqa', r:'REDUCED_BY'},
{s:'kv_fund', t:'kv_paged', r:'MANAGED_BY'},
{s:'kv_fund', t:'kv_quant', r:'COMPRESSED_BY'},
{s:'kv_fund', t:'kv_mla', r:'COMPRESSED_BY'},
{s:'kv_fund', t:'kv_offload',r:'EXTENDED_TO'},
{s:'kv_paged', t:'kv_block', r:'IMPLEMENTED_VIA'},
{s:'kv_paged', t:'kv_cow', r:'ENABLES'},
{s:'kv_cow', t:'kv_radix', r:'GENERALIZED_BY'},
// BATCH internal
{s:'b_cont', t:'b_iter', r:'REQUIRES'},
{s:'b_cont', t:'b_preempt', r:'REQUIRES'},
{s:'b_chunked',t:'b_cont', r:'EXTENDS'},
{s:'b_spec', t:'b_eagle', r:'IMPROVED_BY'},
{s:'b_ifl', t:'b_cont', r:'IMPLEMENTS'},
{s:'b_preempt',t:'b_disagg', r:'MOTIVATES'},
{s:'b_static', t:'b_cont', r:'SUPERSEDED_BY',a:'36.9×'},
// OPT internal
{s:'o_sq', t:'o_fp8', r:'COMPLEMENTS'},
{s:'o_awq', t:'o_gptq', r:'FASTER_THAN'},
{s:'o_sparsity',t:'o_fp8', r:'COMBINED_WITH'},
// SYS internal
{s:'s_trt', t:'s_nim', r:'PACKAGED_IN'},
{s:'s_trt', t:'s_triton', r:'BACKENDS_TO'},
{s:'s_vllm', t:'s_mlc', r:'ALTERNATIVE_TO'},
// PARA internal
{s:'p_tp', t:'p_multi', r:'ENABLES'},
{s:'p_pp', t:'p_multi', r:'ENABLES'},
{s:'p_multi', t:'p_nccl', r:'USES'},
{s:'p_sp', t:'p_tp', r:'COMBINED_WITH'},
// BENCH internal
{s:'m_tpot', t:'m_sla', r:'CONSTRAINED_BY'},
{s:'m_ttft', t:'m_sla', r:'CONSTRAINED_BY'},
{s:'m_util', t:'m_mfu', r:'RELATED_TO'},
{s:'m_tput', t:'m_bench', r:'MEASURED_BY'},
{s:'m_ttft', t:'m_bench', r:'MEASURED_BY'},
// === CROSS DOMAIN ===
// HW → EXEC
{s:'hw_h100', t:'ex_cuda', r:'PROGRAMMED_VIA'},
{s:'hw_h100', t:'ex_sm', r:'CONTAINS'},
// HW → KERN
{s:'hw_tc', t:'k_fa3', r:'ACCELERATES_VIA_WGMMA'},
{s:'hw_te', t:'o_fp8', r:'ENABLES_NATIVE_FP8'},
{s:'hw_roofline',t:'kv_fund', r:'IDENTIFIES_BOTTLENECK'},
{s:'hw_roofline',t:'k_mha', r:'CHARACTERIZES'},
// HW → PARA
{s:'hw_nvlink',t:'p_multi', r:'INTERCONNECTS'},
{s:'hw_nvswitch',t:'p_multi', r:'SCALES'},
// EXEC → KERN
{s:'ex_smem', t:'k_fa2', r:'ENABLES_TILING'},
{s:'ex_smem', t:'k_fa3', r:'ENABLES_TILING'},
{s:'ex_warp', t:'k_wgmma', r:'WARP_GROUP_CONTEXT'},
{s:'ex_async', t:'k_fa3', r:'SOFTWARE_PIPELINE'},
// KERN → KV
{s:'k_fa2', t:'kv_fund', r:'PREFILL_ATTENTION'},
{s:'k_flashinfer',t:'kv_paged',r:'SUPPORTS_PAGED'},
{s:'k_cascade',t:'kv_cow', r:'REQUIRES'},
{s:'k_cascade',t:'kv_radix', r:'LEVERAGES'},
{s:'k_gqa_k', t:'kv_mqa', r:'IMPLEMENTS_DECODE'},
{s:'k_paged_k',t:'kv_paged', r:'IMPLEMENTS'},
// KV → BATCH
{s:'kv_fund', t:'b_cont', r:'BOTTLENECK_DRIVES'},
{s:'kv_paged', t:'b_preempt', r:'ENABLES_EVICTION'},
{s:'kv_block', t:'b_preempt', r:'UNIT_OF_EVICTION'},
// KV → SYS
{s:'kv_paged', t:'s_vllm', r:'PIONEERED_BY'},
{s:'kv_radix', t:'s_sglang', r:'IMPLEMENTED_AS_RADIXATTN'},
{s:'kv_mla', t:'s_trt', r:'SUPPORTED_IN'},
{s:'kv_cow', t:'s_sglang', r:'ENABLES'},
// BATCH → SYS
{s:'b_cont', t:'s_vllm', r:'IMPLEMENTED_IN'},
{s:'b_cont', t:'s_sglang', r:'IMPLEMENTED_IN'},
{s:'b_ifl', t:'s_trt', r:'NATIVE_TO'},
{s:'b_chunked',t:'s_vllm', r:'SUPPORTED_IN'},
{s:'b_chunked',t:'s_sglang', r:'SUPPORTED_IN'},
{s:'b_spec', t:'s_vllm', r:'INTEGRATED_IN'},
{s:'b_spec', t:'s_sglang', r:'INTEGRATED_IN'},
{s:'b_eagle', t:'s_vllm', r:'SUPPORTED_IN'},
{s:'b_iter', t:'s_vllm', r:'IMPLEMENTS'},
// OPT → SYS
{s:'o_fp8', t:'s_trt', r:'NATIVE_TO'},
{s:'o_fp8', t:'s_vllm', r:'SUPPORTED_IN'},
{s:'o_awq', t:'s_vllm', r:'SUPPORTED_IN'},
{s:'o_awq', t:'s_lmd', r:'NATIVE_TO'},
{s:'o_gptq', t:'s_vllm', r:'SUPPORTED_IN'},
{s:'o_sq', t:'s_trt', r:'INTEGRATED_IN'},
// OPT → KV
{s:'o_fp8', t:'kv_quant', r:'APPLIED_TO_KV'},
// OPT → KERN
{s:'o_fp8', t:'k_fa3', r:'USED_WITH'},
// PARA → HW
{s:'p_tp', t:'hw_nvlink', r:'REQUIRES'},
// PARA → SYS
{s:'p_tp', t:'s_trt', r:'REQUIRED_FOR_LARGE'},
{s:'p_multi', t:'s_vllm', r:'ENABLED_IN'},
{s:'p_tp', t:'s_vllm', r:'ENABLED_IN'},
{s:'p_nccl', t:'s_vllm', r:'USED_BY'},
{s:'p_nccl', t:'s_sglang', r:'USED_BY'},
{s:'p_ep', t:'s_trt', r:'SUPPORTED_IN'},
// BENCH → *
{s:'b_chunked',t:'m_ttft', r:'REDUCES_SPIKES'},
{s:'b_spec', t:'m_tpot', r:'REDUCES'},
{s:'b_cont', t:'m_tput', r:'MAXIMIZES'},
{s:'kv_fund', t:'m_tpot', r:'DETERMINES'},
{s:'m_mfu', t:'m_tput', r:'CORRELATES'},
{s:'m_tco', t:'m_tput', r:'DRIVEN_BY'},
// KERN → SYS
{s:'k_fa2', t:'s_vllm', r:'USED_BY'},
{s:'k_fa2', t:'s_trt', r:'USED_BY'},
{s:'k_flashinfer',t:'s_vllm', r:'BACKEND'},
{s:'k_flashinfer',t:'s_sglang',r:'BACKEND'},
];
const INSIGHT_NODES=[
new Set(['hw_h100','hw_a100','hw_tc','hw_te','hw_hbm3','hw_nvlink','hw_roofline','ex_cuda','ex_sm','ex_warp','ex_smem','ex_occ','k_fa3','k_wgmma','k_mha','k_fa2','m_mfu','m_util']),
new Set(['kv_fund','kv_paged','kv_mqa','kv_block','kv_cow','kv_radix','kv_quant','kv_mla','kv_offload','k_gqa_k','k_cascade','k_flashinfer','m_tpot','o_fp8','o_awq','s_vllm','s_sglang']),
new Set(['b_cont','b_spec','b_ifl','b_chunked','b_iter','b_preempt','b_eagle','b_disagg','s_vllm','s_sglang','s_trt','s_nim','p_tp','p_multi','m_ttft','m_tput','m_sla','m_tco']),
];
const PATHS={
hardware: new Set(['hw_h100','hw_a100','hw_tc','hw_te','hw_hbm3','hw_nvlink','hw_nvswitch','hw_roofline','ex_cuda','ex_sm','ex_warp','ex_smem','ex_occ','ex_coal','k_fa3','k_wgmma','p_tp','p_multi','p_nccl','o_fp8']),
latency: new Set(['b_spec','b_eagle','b_chunked','b_disagg','k_cascade','k_flashinfer','kv_mqa','kv_radix','m_ttft','m_tpot','m_sla','s_sglang','s_vllm','s_trt','hw_roofline']),
throughput:new Set(['b_cont','b_ifl','b_iter','b_preempt','b_static','kv_paged','kv_radix','kv_cow','k_flashinfer','o_fp8','o_awq','p_tp','p_multi','s_vllm','s_sglang','s_trt','m_tput','m_util','m_mfu','m_bench']),
memory: new Set(['kv_fund','kv_mqa','kv_mla','kv_quant','kv_offload','kv_paged','kv_block','kv_cow','o_awq','o_gptq','o_sq','o_sparsity','o_fp8','hw_hbm3','hw_roofline','m_tpot','m_tco','b_preempt']),
};
let activeTiers=new Set([1,2,3]), activeDomains=new Set(DOMAINS.map(d=>d.id));
let activePath='', locked=null, activeIns=-1;
let _nodeData=[], _linkData=[], _nodeSel, _linkSel, _labelSel;
const svg=d3.select('#svg');
const W=()=>document.getElementById('wrap').clientWidth;
const H=()=>document.getElementById('wrap').clientHeight;
const zoom=d3.zoom().scaleExtent([.1,6]).on('zoom',e=>g.attr('transform',e.transform));
svg.call(zoom);
const g=svg.append('g');
const gBg=g.append('g'), gE=g.append('g'), gN=g.append('g');
const defs=svg.append('defs');
DOMAINS.forEach(d=>{
defs.append('marker').attr('id','a-'+d.id)
.attr('viewBox','0 -4 8 8').attr('refX',14).attr('markerWidth',5).attr('markerHeight',5).attr('orient','auto')
.append('path').attr('d','M0,-4L8,0L0,4').attr('fill',d.color).attr('opacity',.35);
});
function vNodes(){return NODES.filter(n=>activeTiers.has(n.tier)&&activeDomains.has(n.domain)&&(!activePath||PATHS[activePath]?.has(n.id)));}
function vEdges(ids){return EDGES.filter(e=>ids.has(e.s)&&ids.has(e.t));}
let sim;
function build(){
gBg.selectAll('*').remove(); gE.selectAll('*').remove(); gN.selectAll('*').remove();
const vn=vNodes(); if(!vn.length) return;
const ids=new Set(vn.map(n=>n.id));
const w=W(), h=H();
_nodeData=vn.map(n=>{
const d=domMap[n.domain];
return{...n, x:d.gx*w+(Math.random()-.5)*40, y:d.gy*h+(Math.random()-.5)*40, _cx:d.gx*w, _cy:d.gy*h};
});
const nIdx={}; _nodeData.forEach(n=>nIdx[n.id]=n);
_linkData=vEdges(ids).map(e=>({...e,source:nIdx[e.s],target:nIdx[e.t]})).filter(e=>e.source&&e.target);
_linkSel=gE.selectAll('line').data(_linkData).join('line')
.attr('stroke',d=>domMap[d.source.domain].color)
.attr('stroke-opacity',.28).attr('stroke-width',1)
.attr('marker-end',d=>'url(#a-'+d.source.domain+')');
_nodeSel=gN.selectAll('circle').data(_nodeData).join('circle')
.attr('r',d=>TR[d.tier]).attr('fill',d=>domMap[d.domain].color)
.attr('fill-opacity',.88).attr('stroke','#ffffff').attr('stroke-width',d=>d.tier===1?2:1.5)
.style('cursor','pointer')
.on('mouseover',(e,d)=>{if(!locked){hl(d);showInfo(d);}})
.on('mouseout',()=>{if(!locked)resetHL();})
.on('click',(e,d)=>{
e.stopPropagation();
if(locked===d.id){locked=null;resetHL();}
else{locked=d.id;hl(d);showInfo(d);}
});
_labelSel=gN.selectAll('text').data(_nodeData).join('text')
.text(d=>d.label).attr('font-size',d=>d.tier===1?8.5:7)
.attr('fill',d=>domMap[d.domain].color).attr('fill-opacity',.82)
.attr('text-anchor','middle').attr('dy',d=>TR[d.tier]+9).attr('pointer-events','none')
.attr('font-weight',d=>d.tier===1?'700':'400');
svg.on('click',()=>{locked=null;resetHL();});
if(sim) sim.stop();
sim=d3.forceSimulation(_nodeData)
.force('link',d3.forceLink(_linkData).id(d=>d.id).distance(52).strength(.13))
.force('charge',d3.forceManyBody().strength(-180).distanceMax(350))
.force('cluster',alpha=>{_nodeData.forEach(n=>{n.vx+=(n._cx-n.x)*alpha*.28;n.vy+=(n._cy-n.y)*alpha*.28;});})
.force('collide',d3.forceCollide(d=>TR[d.tier]+4))
.alphaDecay(.02)
.on('tick',()=>{
_linkSel.attr('x1',d=>d.source.x).attr('y1',d=>d.source.y)
.attr('x2',d=>d.target.x).attr('y2',d=>d.target.y);
_nodeSel.attr('cx',d=>d.x).attr('cy',d=>d.y);
_labelSel.attr('x',d=>d.x).attr('y',d=>d.y);
});
setTimeout(fitAll,2000);
setTimeout(drawHulls,2500);
}
function drawHulls(){
gBg.selectAll('*').remove();
const byDom={};
_nodeData.forEach(n=>{if(!byDom[n.domain])byDom[n.domain]=[];byDom[n.domain].push([n.x,n.y]);});
Object.entries(byDom).forEach(([id,pts])=>{
if(pts.length<3) return;
const hull=d3.polygonHull(pts); if(!hull) return;
const dom=domMap[id];
const cx=d3.mean(pts,p=>p[0]), cy=d3.mean(pts,p=>p[1]);
const pad=26;
const padded=hull.map(p=>{const dx=p[0]-cx,dy=p[1]-cy,l=Math.sqrt(dx*dx+dy*dy)||1;return[p[0]+dx/l*pad,p[1]+dy/l*pad];});
gBg.append('path').attr('d','M'+padded.join('L')+'Z')
.attr('fill',dom.color).attr('fill-opacity',.055)
.attr('stroke',dom.color).attr('stroke-opacity',.22)
.attr('stroke-width',1.5).attr('stroke-dasharray','5,3');
});
}
function fitAll(){
if(!_nodeData.length) return;
const xs=_nodeData.map(n=>n.x),ys=_nodeData.map(n=>n.y);
const x0=Math.min(...xs)-28,x1=Math.max(...xs)+28;
const y0=Math.min(...ys)-28,y1=Math.max(...ys)+28;
const dx=x1-x0,dy=y1-y0,w=W(),h=H();
const sc=Math.min(w/dx,h/dy)*.86;
svg.transition().duration(800).call(zoom.transform,
d3.zoomIdentity.translate((w-dx*sc)/2-x0*sc,(h-dy*sc)/2-y0*sc).scale(sc));
}
function hl(d){
if(!_nodeSel) return;
const conn=new Set([d.id]);
_linkSel.each(e=>{if(e.source.id===d.id)conn.add(e.target.id);if(e.target.id===d.id)conn.add(e.source.id);});
_nodeSel.attr('fill-opacity',n=>conn.has(n.id)?1:.07).attr('r',n=>conn.has(n.id)?TR[n.tier]*1.45:TR[n.tier]);
_labelSel.attr('fill-opacity',n=>conn.has(n.id)?1:.04);
_linkSel.attr('stroke-opacity',e=>(e.source.id===d.id||e.target.id===d.id)?.9:.03)
.attr('stroke-width',e=>(e.source.id===d.id||e.target.id===d.id)?2.5:.5);
}
function hlSet(ns){
if(!_nodeSel) return;
_nodeSel.attr('fill-opacity',n=>ns.has(n.id)?1:.07).attr('r',n=>ns.has(n.id)?TR[n.tier]*1.45:TR[n.tier]);
_labelSel.attr('fill-opacity',n=>ns.has(n.id)?1:.04);
_linkSel.attr('stroke-opacity',e=>(ns.has(e.source.id)&&ns.has(e.target.id))?.9:.03)
.attr('stroke-width',e=>(ns.has(e.source.id)&&ns.has(e.target.id))?2.5:.5);
}
function resetHL(){
if(!_nodeSel) return;
_nodeSel.attr('fill-opacity',.9).attr('r',d=>TR[d.tier]);
_labelSel.attr('fill-opacity',.82);
_linkSel.attr('stroke-opacity',.28).attr('stroke-width',1);
document.getElementById('node-float').style.display='none';
}
function showInfo(d){
const dom=domMap[d.domain];
document.getElementById('nf-name').textContent=d.label;
document.getElementById('nf-type').textContent=d.type+' · '+dom.label;
document.getElementById('nf-type').style.color=dom.color;
document.getElementById('nf-desc').textContent=d.desc;
document.getElementById('nf-perf').textContent=d.perf?'⬡ '+d.perf:'';
document.getElementById('nf-tier').textContent='Tier '+d.tier+' · '+['','Foundation','Advanced','Cutting Edge'][d.tier];
document.getElementById('node-float').style.display='block';
}
// Legend
const lfRows=document.getElementById('lf-rows');
DOMAINS.forEach(dom=>{
const row=document.createElement('div');
row.className='lf-row'; row.dataset.id=dom.id;
row.innerHTML=`<div class="lf-dot" style="background:${dom.color}"></div><div class="lf-label">${dom.label}</div>`;
row.addEventListener('click',e=>{
e.stopPropagation();
if(activeDomains.has(dom.id))activeDomains.delete(dom.id);else activeDomains.add(dom.id);
row.classList.toggle('off',!activeDomains.has(dom.id));
locked=null;build();
});
lfRows.appendChild(row);
});
document.querySelectorAll('.tbtn').forEach(btn=>{
btn.addEventListener('click',()=>{
const t=+btn.dataset.tier;
if(activeTiers.has(t))activeTiers.delete(t);else activeTiers.add(t);
btn.classList.toggle('on',activeTiers.has(t));
locked=null;build();
});
});
document.getElementById('path-sel').addEventListener('change',e=>{activePath=e.target.value;locked=null;build();});
[0,1,2,3].forEach(i=>{
const el=document.getElementById('ins-'+i);
if(!el||i===3) return;
el.addEventListener('click',()=>{
if(activeIns===i){activeIns=-1;el.classList.remove('active');locked=null;resetHL();}
else{
activeIns=i;
[0,1,2].forEach(j=>{const e=document.getElementById('ins-'+j);if(e)e.classList.remove('active');});
el.classList.add('active');
locked=null;hlSet(INSIGHT_NODES[i]);
}
});
});
build();
window.addEventListener('resize',()=>{if(sim)build();});
</script>
</body>
</html>