Dynamo — A Datacenter Scale Distributed Inference Serving Framework

code ai-dynamo-dynamo
inference-servingdisaggregated-servingkv-cacherustdistributed-systems

Dynamo — L2 Deep Dive #

§1 Project Identity #

FieldValue
Repoai-dynamo/dynamo
Primary languageRust (54.3%), Python (30.4%), Go (13.1%)
LOC~29.2M total (Rust 15.9M, Python 8.9M, Go 3.8M)
LicenseApache-2.0
Stars7,082
Maintainer / SponsorNVIDIA Inc.
Stable versionv1.1.1 (2026-05-09)
Pre-releasev1.2.0-deepseek-v4-dev.3

Trilingual project: Rust for the performance-critical runtime, router, and KV block manager; Python for user-facing frontend, planner, and backend integrations; Go for the Kubernetes operator.

§2 What & Why — Motivation Analysis #

One-line pitch: The orchestration layer above inference engines — turns SGLang, TRT-LLM, or vLLM into a coordinated multi-node inference system with disaggregated serving, KV-aware routing, multi-tier KV caching, and SLA-driven autoscaling.

Problem: Single-engine inference servers hit a wall at datacenter scale. Three specific gaps:

  1. Prefill/decode coupling — a single GPU pool handles both phases, forcing a worst-case-of-both resource allocation. Prefill is compute-bound; decode is memory-bound. Coupling them wastes either compute or memory bandwidth.
  2. KV cache locality blindness — standard load-balancers (round-robin, least-loaded) ignore KV cache state. A follow-up request with 90% token overlap with an existing cache still triggers full prefill on whatever node the balancer picks.
  3. Manual scaling — operators hand-tune TP/PP degrees, replica counts, and batch sizes. This breaks when workload distributions shift (e.g., peak reasoning traffic vs. peak chat traffic).
  4. 核心技术壁垒: The KV-aware radix-tree router (dynamo-kv-router). It maintains a concurrent, compressed radix tree of all active KV cache prefixes across the cluster. Routing decisions factor in both prefix overlap score and worker load, solving a joint optimization that simpler heuristics (hash-based affinity, random) miss. The concurrent tree implementation uses lock-free reads with fine-grained locking for writes, enabling microsecond-latency routing decisions at hundreds of thousands of QPS — the single hardest component to replicate because it couples a custom concurrent data structure with domain-specific scheduling policies (FCFS, WSPT).

    §3 Architecture & Module Map #

    flowchart TD subgraph External["User / Client"] CLI["curl / SDK"] end subgraph Frontend["Frontend (Python + Rust)"] HTTP["HTTP Server
    OpenAI / Anthropic API"] GRPC["gRPC Server
    KServe"] end subgraph Routing["Router Layer (Rust)"] RM["Router Mode Select
    kv | random | round-robin
    | least-loaded | power-of-two"] KVR["KV Router
    ConcurrentRadixTree
    + SchedulingPolicy"] KVI["KV Indexer
    (standalone binary)"] end subgraph Backends["Backend Engines"] VLLM["vLLM Worker"] SGL["SGLang Worker"] TRT["TRT-LLM Worker"] end subgraph KVBM["KV Block Manager (Rust + CUDA)"] G1["G1: GPU/HBM"] G2["G2: CPU/DRAM"] G3["G3: NVMe/SSD"] G4["G4: S3/MinIO"] end subgraph Control["Control Plane"] Planner["SLA Planner
    (Python)"] Profiler["Workload Profiler"] AICfg["AIConfigurator
    config-space search"] Grove["Grove K8s Operator
    (Go)"] end subgraph Infra["Infrastructure"] NATS["NATS
    event plane"] ETCD["etcd
    service discovery"] NIXL["NIXL
    GPU-to-GPU transfer"] end CLI --> HTTP CLI --> GRPC HTTP --> RM GRPC --> RM RM --> KVR KVR <--> KVI KVR --> VLLM KVR --> SGL KVR --> TRT VLLM <--> G1 SGL <--> G1 TRT <--> G1 G1 --> G2 --> G3 --> G4 Planner --> Grove Profiler --> Planner AICfg --> Planner KVI <--> NATS VLLM <--> NIXL SGL <--> NIXL

    Module roles:

    ModuleLocationPurpose
    dynamo-runtimelib/runtime/Tokio executor management, cancellation tokens, service discovery, 3-phase shutdown
    dynamo-llmlib/llm/HTTP frontend, engine abstraction, model cards, telemetry
    dynamo-kv-routerlib/kv-router/Concurrent radix tree, scheduling policies, prefix overlap scoring
    kvbm-enginelib/kvbm-engine/4-tier KV block offload with CUDA copy kernels
    dynamo-protocolslib/protocols/OpenAI / Anthropic / Responses API type definitions
    components/components/src/dynamo/Python frontend, router glue, planner, profiler, backend integrations
    deploy/operator/deploy/operator/Go-based K8s operator for topology-aware gang scheduling

    §4 Entry Points & API Surface #

    Python public APIimport dynamo:

    • dynamo.llm: EntrypointArgs, EngineType, KvRouterConfig, RouterConfig, RouterMode, make_engine, run_input
    • dynamo.runtime: DistributedRuntime
    • dynamo.frontend: main entry point (python -m dynamo.frontend)
    • dynamo.vllm, dynamo.sglang, dynamo.trtllm: backend-specific workers

    CLI entry points (from pyproject.toml scripts and modules):

    Entry pointWhat it does
    python -m dynamo.frontendStarts HTTP/gRPC server with chosen router mode
    python -m dynamo.vllmStarts vLLM backend worker
    python -m dynamo.sglangStarts SGLang backend worker
    python -m dynamo.trtllmStarts TRT-LLM backend worker
    dynamo-kv-indexerStandalone KV indexer (Rust binary via maturin)

    Top 10 configuration knobs:

    Flag / EnvDefaultEffect
    --router-moderound-robinRouting strategy: kv, random, direct, power-of-two, least-loaded, device-aware-weighted, round-robin
    --http-port8000Frontend listen port
    --discovery-backendetcdService discovery: etcd, file (local dev), K8s-native
    --kv-cache-block-sizeengine defaultKV cache block granularity in tokens
    --migration-limitnoneMax concurrent in-flight request migrations
    --interactivefalseText-mode interactive REPL instead of HTTP
    --kserve-grpc-serverfalseStart gRPC instead of HTTP
    SLA YAML (ttft, itl)nonePlanner targets for time-to-first-token and inter-token latency
    autoApply (K8s CR)falseLet AIConfigurator auto-deploy optimal config
    Backend-specific flagsvariesPassed through to vLLM / SGLang / TRT-LLM (TP, PP, batch size, etc.)

    Extension points:

    • Custom backends: implement the engine trait in Rust or follow examples/custom_backend/ Python template
    • Custom routing policies: implement SchedulingPolicy trait (lib/kv-router/src/scheduling/policy.rs)
    • Custom worker selectors: implement WorkerSelector trait

    §5 Core Data Structures #

    Runtime (lib/runtime/src/runtime.rs) #

    
    pub struct Runtime {
        id: Arc<String>,
        primary: RuntimeType,          // tokio Runtime for async I/O
        secondary: RuntimeType,        // tokio Runtime for compute-heavy tasks
        cancellation_token: CancellationToken,
        endpoint_shutdown_token: CancellationToken,
        graceful_shutdown_tracker: Arc<GracefulShutdownTracker>,
        compute_pool: Option<Arc<compute::ComputePool>>,
        block_in_place_permits: Option<Arc<tokio::sync::Semaphore>>,
    }
    
    • Lifecycle: Created once at process start via from_settings(). Owned by the main function. Destroyed on shutdown() (3-phase protocol).
    • Thread safety: All fields behind Arc; the struct itself is Send + Sync. The dual-runtime design (primary for I/O, secondary for CPU-bound work) avoids executor starvation.
    • Semaphore for blocking: block_in_place_permits gates tokio::task::block_in_place calls to prevent thread pool exhaustion.

    ConcurrentRadixTree / ConcurrentRadixTreeCompressed (lib/kv-router/src/indexer/) #

    The core routing data structure. Maps token-sequence prefixes to worker IDs with overlap scores.

    • Lifecycle: Created when the KV indexer starts; lives for the process lifetime. Updated on every KV cache event (insert/evict) from backend workers via NATS.
    • Concurrency: Lock-free reads via epoch-based reclamation (crossbeam-epoch style). Writes use fine-grained per-node locks. The compressed variant reduces memory by deduplicating shared prefix edges.
    • Memory: Each tree node stores WorkerId, block hashes, and child pointers. At scale (millions of cached sequences), memory is dominated by prefix edges.

    KVBM Tier Structs (lib/kvbm-engine/src/lib.rs) #

    
    pub struct G1;  // GPU/HBM
    pub struct G2;  // CPU/DRAM (RDMA staging)
    pub struct G3;  // NVMe/SSD
    pub struct G4;  // S3/MinIO
    

    Zero-sized type markers used as generic parameters in the offload pipeline: Offloader moves blocks from GPU to CPU, Offloader from CPU to SSD, etc. Block identity is tracked by BlockId (u64 hash of sequence content), and logical-to-physical mapping by LogicalLayoutHandle.

    EntrypointArgs / RouterConfig (Python side) #

    
    e = EntrypointArgs(EngineType.Dynamic, **kwargs)
    router_config = RouterConfig(router_mode, kv_router_config, **kwargs)
    

    Dataclass-like configuration carriers. EngineType.Dynamic means the frontend discovers backends at runtime via service discovery, as opposed to EngineType.Static for single-engine mode.

    §6 Critical Path Analysis #

    Request hot path: HTTP request → response tokens.

    sequenceDiagram participant C as Client participant F as Frontend (axum HTTP) participant R as Router (Rust) participant KV as KV Indexer participant W as Backend Worker participant GPU as GPU C->>F: POST /v1/chat/completions F->>F: Parse & tokenize request F->>R: SchedulingRequest (tokens, hints) R->>KV: Query prefix overlap scores KV-->>R: OverlapScores per worker R->>R: SchedulingPolicy.select(scores, loads) R-->>F: SchedulingResponse (worker_id) F->>W: Forward request (NATS or direct TCP) W->>GPU: Prefill (compute-bound) W->>GPU: Decode loop (memory-bound) W-->>F: Token stream (SSE/gRPC) F-->>C: Streaming response W->>KV: KV cache event (new prefix registered)

    Latency budget (estimated from architecture, not profiled):

    HopEstimatedNotes
    HTTP parse + tokenize~0.5 msTokenizer is Rust (fastokens) via PyO3
    Router scheduling~0.01-0.1 msRadix tree lookup + policy evaluation; designed for <100 µs
    NATS request forward~0.1-0.5 msIf using event plane; direct TCP is lower
    Prefill10-1000+ msDominates TTFT; varies with prompt length
    Decode per token5-50 msVaries with model size and batch

    The README claims KV-aware routing yields 2x TTFT improvement. This is plausible: if 80% of a prompt's prefix is already cached on a worker, prefill cost drops from $O(n)$ to $O(0.2n)$, which is a 5x reduction in prefill time, partially offset by slightly higher load on the cache-hot worker.

    Mismatch with docs: The README says "7x higher throughput per GPU" for DeepSeek R1 on GB200 NVL72, but this combines disaggregated serving + KV routing + NVLink topology awareness — the 7x is a system-level number, not attributable to any single component.

    §7 N/A (not a PR) #

    §8 N/A (not an issue) #

    §9 Concurrency & Memory #

    Concurrency model: hybrid async + threaded.

    LayerModelDetails
    Rust runtimetokio asyncDual-runtime: primary for I/O, secondary for CPU-bound tasks. block_in_place gated by semaphore to prevent pool saturation.
    KV routerLock-free reads + fine-grained write locksRadix tree uses epoch-based memory reclamation. ZMQ-based inter-node sync for the KV indexer.
    Python frontendasyncio (single-threaded event loop)Bridges to Rust via PyO3 asyncio.Future. No GIL contention on hot path since compute is in Rust.
    Backend workersPer-enginevLLM/SGLang run their own async loops. TRT-LLM uses thread-based execution.
    K8s operator (Go)goroutinesStandard controller-runtime concurrency.

    Shutdown protocol (3-phase):

    1. Cancel endpoint_shutdown_token — stop accepting new requests
    2. GracefulShutdownTracker::wait_for_completion() — drain in-flight requests
    3. Cancel main cancellation_token — disconnect NATS/etcd
    4. This is significantly more sophisticated than a simple CancellationToken::cancel(). The tracker uses an atomic counter; each graceful endpoint increments on entry, decrements on exit. Phase 2 blocks until the counter hits zero.

      Memory management:

      • Rust: Arc reference counting everywhere. No raw pointers in user-facing code. KVBM uses CUDA cudaMallocHost for pinned CPU buffers.
      • KVBM: Custom block pool allocator for G1/G2 tiers with explicit lifecycle (allocate on cache insert, free on evict/offload). G3/G4 use async I/O.
      • Python: Standard GC. The PyO3 boundary ensures Rust objects are properly ref-counted across the FFI.

      §10 Performance Characteristics #

      Headline benchmarks (from README, externally validated):

      MetricValueConfig
      Throughput gain7x per GPUDeepSeek R1, GB200 NVL72, Dynamo vs standalone B200 (InferenceX)
      Throughput gain750xDeepSeek-R1, GB300 NVL72 (InferenceXv2)
      TTFT improvement2xKV-aware routing, Qwen3-Coder 480B (Baseten)
      SLA breach reduction80% fewerPlanner autoscaling at 5% lower TCO (Alibaba APSARA 2025)
      Cold-start speedup7xModelExpress weight streaming, DeepSeek-V3 on H200

      Scaling dimensions:

      • Horizontal: Add workers to pools; Planner auto-adjusts pool sizes. KV indexer is designed as a separate process precisely for horizontal scaling.
      • Disaggregation: Prefill and decode pools scale independently. High prefill demand → add prefill GPUs without touching decode pool.
      • Multi-tier KV cache: Effective context window extends beyond GPU memory. Warm blocks on NVMe can be promoted in milliseconds; cold blocks on S3 in seconds.

      Where the time goes (architectural analysis):

      • GPU time: dominated by attention kernels (prefill) and weight loading (decode). Dynamo doesn't touch these — it orchestrates which GPU runs what.
      • CPU time (Dynamo overhead): router scheduling (~µs), NATS message forwarding (~100 µs), tokenization (~0.5 ms). At high QPS, the radix tree lookup becomes the CPU bottleneck — hence the concurrent lock-free design.
      • Network: NATS pubsub for KV events, NIXL/NVLink for weight streaming and KV block transfer.

      §11 Tech Debt & Code Quality #

      Dependency health:

      • Aggressive exact-pinning: tokio = "=1.48.0", axum = "=0.8.4", hyper = "=1.7.0". Transitive deps pinned via [patch.crates-io] to specific git commits. This prevents supply-chain surprises but creates upgrade debt.
      • Backend version coupling: vLLM 0.19-0.21, SGLang 0.5.9-0.5.12, TRT-LLM 1.3.0rc series. Each Dynamo release is tightly coupled to specific backend versions.
      • velo 0.1.0: Internal/companion library for KVBM peer communication. Not on crates.io — likely an NVIDIA internal dependency. Bus factor = 1 library.

      Testing:

      • Integration tests in tests/ directory; E2E tests for each backend.
      • Benchmark suite in benchmarks/ covers LLM latency/throughput, router microbenchmarks, frontend load testing, and multimodal.
      • The lib/mocker/ module enables testing without GPUs via a mock inference engine.

      Known warts:

      • The LOC counts (29M total) are inflated — likely includes generated code, vendored deps, or proto files. The actual authored code is probably 1-2 orders of magnitude smaller.
      • Pre-release branches like v1.2.0-deepseek-v4-dev.3 suggest model-specific forks that may diverge from mainline, creating merge debt.

      §12 Community Health #

      MetricValue
      Stars7,082
      Contributors (v1.1.0)113 (over 896 PRs)
      Release cadence~biweekly (v1.0.2 → v1.1.0 → v1.1.1 in 16 days)
      GovernanceCorporate-backed (NVIDIA)
      LicenseApache-2.0

      The 113-contributor count for a single release suggests significant internal NVIDIA engineering investment. The Apache-2.0 license enables commercial adoption without reciprocal obligations.

      §13 Comparison with Alternatives #

      DimensionDynamoRay Serve / vLLM standaloneTriton Inference Server
      Multi-engine supportSGLang + TRT-LLM + vLLMvLLM only (or Ray Serve generic)Multi-framework but no LLM-specific routing
      Disaggregated P/DNative with independent scalingvLLM 0.8+ has basic P/DNot supported
      KV-aware routingRadix tree + overlap scoringvLLM prefix caching is intra-node onlyNone
      Multi-tier KV cache4-tier (GPU→CPU→SSD→S3)GPU-only KV cacheN/A
      AutoscalingSLA-driven Planner + AIConfiguratorRay autoscaler (generic)Triton model analyzer (static)
      K8s integrationCustom operator (Grove) + Inference GatewayKubeRayTriton Kubernetes integration
      LanguageRust + Python + GoPythonC++ + Python
      Cold-start7x faster via ModelExpress/NIXLStandard model loadingStandard model loading
      Maturityv1.1 (2026, ~1 yr old)vLLM ~2 yrs, Ray ~5 yrs~6 yrs, battle-tested

      Winner per dimension: Dynamo leads on LLM-specific features (disaggregation, KV routing, multi-tier cache). Triton leads on maturity and non-LLM workloads. Ray Serve leads on ecosystem breadth.

      §14 Verdict & Recommendations #

      When to adopt:

      • You are serving LLMs at multi-node / multi-GPU scale and need to maximize throughput per dollar
      • Your workload has significant prefix sharing (chat, RAG, code completion) where KV-aware routing pays off
      • You need disaggregated prefill/decode to handle bursty traffic patterns
      • You are on NVIDIA hardware (the NVLink/NIXL integration is NVIDIA-specific)
      • You want a single orchestration layer that works across SGLang, vLLM, and TRT-LLM

      When NOT to adopt:

      • Single-GPU or small-scale deployment — the orchestration overhead isn't justified
      • Non-NVIDIA hardware (AMD, Intel) — KVBM CUDA kernels, NIXL, and ModelExpress are NVIDIA-only
      • You need a stable, battle-tested solution today — v1.x is still rapidly evolving with breaking changes between minors
      • Your workload is non-LLM (diffusion model support exists but is secondary)

      关键实现细节 (easy-to-miss tricks):

      1. Dual tokio runtimes: The primary/secondary runtime split prevents CPU-heavy tokenization or scheduling from starving I/O handlers. The block_in_place_permits semaphore is the pressure valve — without it, block_in_place calls can consume all runtime threads.
        1. KV indexer as standalone process: Running the KV indexer out-of-process (dynamo-kv-indexer binary) means a router crash doesn't lose the prefix tree, and the indexer can be independently scaled / monitored. Recovery from node failure uses a peer /dump endpoint to bootstrap the tree from surviving nodes, plus inline ZMQ gap detection for missed events.
        2. 实现 cross-reference #

          ComponentLocation
          Runtime 3-phase shutdownlib/runtime/src/runtime.rsRuntime::shutdown()
          KV router radix treelib/kv-router/src/indexer/ConcurrentRadixTree, ConcurrentRadixTreeCompressed
          Scheduling policieslib/kv-router/src/scheduling/policy.rsFcfsPolicy, WsptPolicy
          KVBM 4-tier structslib/kvbm-engine/src/lib.rsG1, G2, G3, G4
          Frontend entry pointcomponents/src/dynamo/frontend/main.pyasync_main()
          Python bindingslib/bindings/python/ — PyO3/maturin
          K8s operatordeploy/operator/ — Go controller-runtime
          CUDA copy kernelslib/kvbm-kernels/