| Field | Value |
|---|---|
| Repo | ai-dynamo/dynamo |
| Primary language | Rust (54.3%), Python (30.4%), Go (13.1%) |
| LOC | ~29.2M total (Rust 15.9M, Python 8.9M, Go 3.8M) |
| License | Apache-2.0 |
| Stars | 7,082 |
| Maintainer / Sponsor | NVIDIA Inc. |
| Stable version | v1.1.1 (2026-05-09) |
| Pre-release | v1.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.
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:
核心技术壁垒: 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).
Module roles:
| Module | Location | Purpose |
|---|---|---|
dynamo-runtime | lib/runtime/ | Tokio executor management, cancellation tokens, service discovery, 3-phase shutdown |
dynamo-llm | lib/llm/ | HTTP frontend, engine abstraction, model cards, telemetry |
dynamo-kv-router | lib/kv-router/ | Concurrent radix tree, scheduling policies, prefix overlap scoring |
kvbm-engine | lib/kvbm-engine/ | 4-tier KV block offload with CUDA copy kernels |
dynamo-protocols | lib/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 |
Python public API — import dynamo:
dynamo.llm: EntrypointArgs, EngineType, KvRouterConfig, RouterConfig, RouterMode, make_engine, run_inputdynamo.runtime: DistributedRuntimedynamo.frontend: main entry point (python -m dynamo.frontend)dynamo.vllm, dynamo.sglang, dynamo.trtllm: backend-specific workersCLI entry points (from pyproject.toml scripts and modules):
| Entry point | What it does |
|---|---|
python -m dynamo.frontend | Starts HTTP/gRPC server with chosen router mode |
python -m dynamo.vllm | Starts vLLM backend worker |
python -m dynamo.sglang | Starts SGLang backend worker |
python -m dynamo.trtllm | Starts TRT-LLM backend worker |
dynamo-kv-indexer | Standalone KV indexer (Rust binary via maturin) |
Top 10 configuration knobs:
| Flag / Env | Default | Effect |
|---|---|---|
--router-mode | round-robin | Routing strategy: kv, random, direct, power-of-two, least-loaded, device-aware-weighted, round-robin |
--http-port | 8000 | Frontend listen port |
--discovery-backend | etcd | Service discovery: etcd, file (local dev), K8s-native |
--kv-cache-block-size | engine default | KV cache block granularity in tokens |
--migration-limit | none | Max concurrent in-flight request migrations |
--interactive | false | Text-mode interactive REPL instead of HTTP |
--kserve-grpc-server | false | Start gRPC instead of HTTP |
SLA YAML (ttft, itl) | none | Planner targets for time-to-first-token and inter-token latency |
autoApply (K8s CR) | false | Let AIConfigurator auto-deploy optimal config |
| Backend-specific flags | varies | Passed through to vLLM / SGLang / TRT-LLM (TP, PP, batch size, etc.) |
Extension points:
examples/custom_backend/ Python templateSchedulingPolicy trait (lib/kv-router/src/scheduling/policy.rs)WorkerSelector traitRuntime (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>>,
}
from_settings(). Owned by the main function. Destroyed on shutdown() (3-phase protocol).Arc; the struct itself is Send + Sync. The dual-runtime design (primary for I/O, secondary for CPU-bound work) avoids executor starvation.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.
WorkerId, block hashes, and child pointers. At scale (millions of cached sequences), memory is dominated by prefix edges.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.
Request hot path: HTTP request → response tokens.
Latency budget (estimated from architecture, not profiled):
| Hop | Estimated | Notes |
|---|---|---|
| HTTP parse + tokenize | ~0.5 ms | Tokenizer is Rust (fastokens) via PyO3 |
| Router scheduling | ~0.01-0.1 ms | Radix tree lookup + policy evaluation; designed for <100 µs |
| NATS request forward | ~0.1-0.5 ms | If using event plane; direct TCP is lower |
| Prefill | 10-1000+ ms | Dominates TTFT; varies with prompt length |
| Decode per token | 5-50 ms | Varies 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.
出处说明:本节不来自 2026-05-26 的仓库快照,而是为回答"xPyD 里 P 和 D 能否用不同并行策略"另行核实。标注「设计文档」的来自 Dynamo 官方 design-docs;「vLLM 源码」的来自 vllm-project/vllm main 分支,2026-08 读取;「未核实」的来自第三方博客。
约束的归属不在 Dynamo。 Dynamo 只把 backend flag(TP / PP / batch size)透传给 vLLM / SGLang / TRT-LLM(见 §4),per-role 并行度的可行性由 backend 的 KV connector 决定。所以"Dynamo 支不支持异构并行"这个问法本身错位——要问的是 connector 支不支持。
设计意图就是异构,而且方向反直觉。 官方 design-doc 把异构并行列为解耦的首要动机:"using a larger TP for the memory-bound decoding phase while a smaller TP for the computation-bound prefill phase allows both phases to be computed efficiently."(设计文档)xPyD 本身运行时可重配:worker 注册时发布 RuntimeConfig(含 KV 容量),可在线增删,退出前 drain 在飞请求。
维护者在 issue #2295 给出的论证与"prefill 算得重所以给大 TP"的直觉相反,值得记住:
历史约束已解除,但关闭方式有误导性。 2025-08 时 dynamo-vllm 在 P_TP=4 / D_TP=2 下会抛 AssertionError: Decode TP cannot be smaller than prefill TP。该 issue 于 2025-08-22 由报告者自行关闭(留言"I'll proceed to implement it myself"),不是上游实现后关闭的——GitHub 的 state_reason: completed 在这里会误导读者。
当前 vLLM main 已有专门的 nixl/tp_mapping.py,两个方向都支持(vLLM 源码):
| 关系 | 映射方式 |
|---|---|
| $D_{TP} \ge P_{TP}$ | 多个 D rank 从同一个 P rank 读不同 KV head 分片:attn_ranks = [tp_rank * remote_tp_size // tp_size] |
| $P_{TP} > D_{TP}$ | 一个 D rank 从多个 P rank 读;GQA 下多个 P rank 可能持有同一 KV head,用 np.unique 去重,每个唯一 head 只发一次读 |
| MLA | KV 在各 rank 上是复制的,只需一个 remote rank,不做 head 切分 |
映射在握手时按 remote engine 算一次(compute_tp_mapping),不是每请求算,代价被摊掉。rank_offset_factor 处理 $D_{TP} > P_{TP}$ 时的 FA head 偏移,并对 tp_size > total_num_kv_heads(TP 比 KV head 还多)单独分支。
残留约束(均在 nixl/base_worker.py,vLLM 源码):
| 约束 | 说明 |
|---|---|
| 布局 | 异构 TP 需按 head 切分,只在 block-contiguous 布局(如 LBHNC)下可行;MLA 与 replicated-KV 不切 head |
| Mamba / SSM | 不支持异构 TP;Mamba hybrid 下 physical_blocks_per_logical 异构时拒绝开 prefix caching |
| 复制 KV × 方向 | 非 mamba 情况下 num_kv_heads > tp_size 且 $P_{TP} > D_{TP}$ 不支持 |
| 异构 block size | 不能与 $P_{TP} > D_{TP}$ 的 head-sharded 读同时用;也不支持 host buffer |
| 整除性 | tp_ratio 是带符号整数比,配置被限制在整数比上 |
| 生命周期 | 异构 TP 下 P 必须等所有被分配的 D rank 读完才能安全释放 block(consumer_notification_counts_by_req 做 fan-out 计数) |
最后一条是最容易被忽略的真实代价:异构 TP 把 P 侧的 block 释放从一对一通知变成扇出计数,P 的显存回收因此被最慢的那个 D rank 拖住。
push / pull 已一等公民化。 vLLM 现在有独立的 pull_scheduler.py / pull_worker.py 与 push_scheduler.py / push_worker.py。有第三方报告称异构配置必须用 push connector 否则退化到 TCP(未核实,官方文档未见此说法)。
与 serving-simulation-digital-twin 的接点。 L1 roadmap 里的 "Mocker becomes AIConfigurator-backed latency predictor with offline agg/disagg replay" 表明 Dynamo 正把 AIConfigurator 接进 Mocker 做离线 agg/disagg 回放,使 Dynamo 成为该主题"真正成熟的产物是仿真器本体"这一判断的上游实例。TRT-LLM 侧对同一问题的陈述是:给解耦画性能曲线需穷举 TP/EP/DP/PP,且 context 与 generation 必须分别评估,配置数随 CTX/GEN server 数指数增长——这正是 AIConfigurator 存在的理由。
Concurrency model: hybrid async + threaded.
| Layer | Model | Details |
|---|---|---|
| Rust runtime | tokio async | Dual-runtime: primary for I/O, secondary for CPU-bound tasks. block_in_place gated by semaphore to prevent pool saturation. |
| KV router | Lock-free reads + fine-grained write locks | Radix tree uses epoch-based memory reclamation. ZMQ-based inter-node sync for the KV indexer. |
| Python frontend | asyncio (single-threaded event loop) | Bridges to Rust via PyO3 asyncio.Future. No GIL contention on hot path since compute is in Rust. |
| Backend workers | Per-engine | vLLM/SGLang run their own async loops. TRT-LLM uses thread-based execution. |
| K8s operator (Go) | goroutines | Standard controller-runtime concurrency. |
Shutdown protocol (3-phase):
endpoint_shutdown_token — stop accepting new requestsGracefulShutdownTracker::wait_for_completion() — drain in-flight requestscancellation_token — disconnect NATS/etcdThis 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:
Arc reference counting everywhere. No raw pointers in user-facing code. KVBM uses CUDA cudaMallocHost for pinned CPU buffers.Headline benchmarks (from README, externally validated):
| Metric | Value | Config |
|---|---|---|
| Throughput gain | 7x per GPU | DeepSeek R1, GB200 NVL72, Dynamo vs standalone B200 (InferenceX) |
| Throughput gain | 750x | DeepSeek-R1, GB300 NVL72 (InferenceXv2) |
| TTFT improvement | 2x | KV-aware routing, Qwen3-Coder 480B (Baseten) |
| SLA breach reduction | 80% fewer | Planner autoscaling at 5% lower TCO (Alibaba APSARA 2025) |
| Cold-start speedup | 7x | ModelExpress weight streaming, DeepSeek-V3 on H200 |
Scaling dimensions:
Where the time goes (architectural analysis):
Dependency health:
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.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:
tests/ directory; E2E tests for each backend.benchmarks/ covers LLM latency/throughput, router microbenchmarks, frontend load testing, and multimodal.lib/mocker/ module enables testing without GPUs via a mock inference engine.Known warts:
v1.2.0-deepseek-v4-dev.3 suggest model-specific forks that may diverge from mainline, creating merge debt.| Metric | Value |
|---|---|
| Stars | 7,082 |
| Contributors (v1.1.0) | 113 (over 896 PRs) |
| Release cadence | ~biweekly (v1.0.2 → v1.1.0 → v1.1.1 in 16 days) |
| Governance | Corporate-backed (NVIDIA) |
| License | Apache-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.
| Dimension | Dynamo | Ray Serve / vLLM standalone | Triton Inference Server |
|---|---|---|---|
| Multi-engine support | SGLang + TRT-LLM + vLLM | vLLM only (or Ray Serve generic) | Multi-framework but no LLM-specific routing |
| Disaggregated P/D | Native with independent scaling | vLLM 0.8+ has basic P/D | Not supported |
| KV-aware routing | Radix tree + overlap scoring | vLLM prefix caching is intra-node only | None |
| Multi-tier KV cache | 4-tier (GPU→CPU→SSD→S3) | GPU-only KV cache | N/A |
| Autoscaling | SLA-driven Planner + AIConfigurator | Ray autoscaler (generic) | Triton model analyzer (static) |
| K8s integration | Custom operator (Grove) + Inference Gateway | KubeRay | Triton Kubernetes integration |
| Language | Rust + Python + Go | Python | C++ + Python |
| Cold-start | 7x faster via ModelExpress/NIXL | Standard model loading | Standard model loading |
| Maturity | v1.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.
When to adopt:
When NOT to adopt:
关键实现细节 (easy-to-miss tricks):
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.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.| Component | Location |
|---|---|
| Runtime 3-phase shutdown | lib/runtime/src/runtime.rs — Runtime::shutdown() |
| KV router radix tree | lib/kv-router/src/indexer/ — ConcurrentRadixTree, ConcurrentRadixTreeCompressed |
| Scheduling policies | lib/kv-router/src/scheduling/policy.rs — FcfsPolicy, WsptPolicy |
| KVBM 4-tier structs | lib/kvbm-engine/src/lib.rs — G1, G2, G3, G4 |
| Frontend entry point | components/src/dynamo/frontend/main.py — async_main() |
| Python bindings | lib/bindings/python/ — PyO3/maturin |
| K8s operator | deploy/operator/ — Go controller-runtime |
| CUDA copy kernels | lib/kvbm-kernels/ |
| 异构 TP 映射(backend 侧) | vLLM vllm/distributed/kv_transfer/kv_connector/v1/nixl/tp_mapping.py — compute_tp_mapping(), TPMapping |
| 异构 TP 残留约束(backend 侧) | vLLM .../v1/nixl/base_worker.py — 布局/Mamba/block-size 断言与 consumer_notification_counts_by_req |
| push / pull connector(backend 侧) | vLLM .../v1/nixl/{push,pull}_{scheduler,worker}.py |