Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ crate.from_cargo(
"stargate-bench",
"stargate-forwarding",
"stargate-k8s-router",
"stargate-routing-sim",
"stargate-runtime",
"stargate-telemetry",
"stargate-tls",
Expand Down
9 changes: 5 additions & 4 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions src/libraries/rust/stargate/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/libraries/rust/stargate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ members = [
"crates/mock-dynamo",
"crates/mock-engine",
"crates/stargate-bench",
"crates/stargate-routing-sim",
"crates/pylon",
]
resolver = "3"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Routing simulator. Not packaged into an OCI image.

load("@stargate_crates//:defs.bzl", "aliases", "all_crate_deps")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")

# Workspace-local Cargo deps (own-workspace `workspace = true`).
_WORKSPACE_DEPS = [
"//src/libraries/rust/stargate/crates/mock-engine",
"//src/libraries/rust/stargate/crates/proto:stargate-proto",
"//src/libraries/rust/stargate/crates/protocol:stargate-protocol",
"//src/libraries/rust/stargate/crates/stargate:stargate_lib",
]

rust_binary(
name = "stargate-routing-sim",
srcs = glob(["src/**/*.rs"]),
aliases = aliases(),
edition = "2024",
proc_macro_deps = all_crate_deps(proc_macro = True),
visibility = ["//visibility:public"],
deps = _WORKSPACE_DEPS + all_crate_deps(normal = True),
)

rust_test(
name = "stargate-routing-sim_test",
crate = ":stargate-routing-sim",
deps = _WORKSPACE_DEPS + all_crate_deps(normal_dev = True),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[package]
name = "stargate-routing-sim"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Discrete-event simulator that drives the real Stargate load balancers in virtual time"

[lints]
workspace = true

[[bin]]
name = "stargate-routing-sim"
path = "src/main.rs"

[dependencies]
anyhow = { workspace = true }
clap = { workspace = true }
mock-engine = { workspace = true }
rand = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
stargate = { workspace = true }
stargate-proto = { workspace = true }
stargate-protocol = { workspace = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# stargate-routing-sim

Discrete-event simulator for comparing Stargate load-balancer policies and
configurations. It calls the production `LoadBalancer` implementations from the
`stargate` crate in virtual time. It models the surrounding system:

- Stargate: one load-balancer instance per Stargate, stale per-backend stats,
optimistic reservations cleared by heartbeats, the routing wait and retry
loop, and queue-mismatch reroutes.
- Pylon: live request phases, queue estimates, queue-mismatch admission, and
the fallback input-throughput window that sets `last_mean_input_tps` and
`max_input_tps`.
- Engine: the `mock-engine` crate that MockDynamo also runs. Each backend is a
Dynamo deployment with `num_gpu_workers` workers that batch prefill and
decode in shared steps, keep their own KV caches, and receive requests by
perfect KV routing.
- Network: per-region round-trip times between Stargates and backends.
- Clients: fixed sessions with open-loop Poisson arrivals, or growing
conversations whose sessions start with Poisson arrivals and whose later
turns follow the previous response after a think time. Failed growing turns
retry with backoff. A client timeout cancels engine work, and a TTFT SLO
defines goodput.

## Usage

Run from `src/libraries/rust/stargate`:

```sh
cargo run --release -p stargate-routing-sim -- \
--config crates/stargate-routing-sim/configs/stargate-dev-parity.json \
--output /tmp/routing-sim.json
```

Each config runs every combination of seed, rate, and policy. All policies see
the same arrival sequence for a seed. Policy `load_balancer` objects use the
same JSON shape as Stargate per-model load-balancer config.

## Fidelity limits

- Engine step costs are estimates until calibrated against a real engine.
- Load balancers use an unseeded thread RNG internally and read a small amount
of real-clock jitter through the request receive time, so runs with the same
seed are not bit-for-bit reproducible. Use several seeds.
- Offered requests and SLO attainment count every attempt, including
growing-session retries. A retry's TTFT starts at the retry.
- Arrivals stop at the end of the measurement window. Requests that arrived in
the window run to completion.
- A request succeeds when the engine completes it, even if the client timeout
fires during the final one-way trip back to the Stargate.
- One backend per routed cluster. Shared-engine cluster aggregation and
sibling-backend retries are not modeled.
- `max_input_work_seconds` admission, `consider_kv_free_tokens`, priorities,
and registration churn are not modeled. Simulated Pylons publish no KV-cache
stats.
- Without `pylon.stats_update_coalesce_ms`, Pylons publish only on the
heartbeat. Production Pylons also publish on request state changes.
- Retries for upstream errors other than queue-mismatch rejections are not
modeled.
- Results are relative comparisons until the engine model is calibrated
against real-engine measurements.
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
{
"name": "stargate-dev parity: five regions, one H100 batched worker per backend, fixed 6,661-token sessions",
"seeds": [
1,
2,
3
],
"topology": {
"regions": [
{
"name": "usw2",
"stargates": 3,
"backends": 4,
"traffic_weight": 1.0
},
{
"name": "ue1",
"stargates": 3,
"backends": 4,
"traffic_weight": 0.0
},
{
"name": "ew1",
"stargates": 3,
"backends": 4,
"traffic_weight": 0.0
},
{
"name": "an1",
"stargates": 3,
"backends": 4,
"traffic_weight": 0.0
},
{
"name": "as2",
"stargates": 3,
"backends": 4,
"traffic_weight": 0.0
}
],
"rtt_ms": [
[
0,
65,
125,
100,
140
],
[
65,
0,
70,
150,
200
],
[
125,
70,
0,
210,
255
],
[
100,
150,
210,
0,
105
],
[
140,
200,
255,
105,
0
]
],
"intra_region_rtt_ms": 1.0,
"stats_relay_delay_ms": 0.0
},
"engine": {
"num_gpu_workers": 1,
"max_num_seqs": 25,
"max_batched_tokens": 2048,
"step_fixed_ms": 4.0,
"step_decode_ms_per_seq": 0.085,
"step_prefill_ms_per_token": 0.05,
"kv_cache_capacity_tokens": 400000
},
"pylon": {
"heartbeat_ms": 1000.0,
"input_tps": {
"model": "fallback-window",
"initial": 20000.0,
"window": 8,
"duration_floor_ms": 10.0
},
"queue_mismatch": {
"enabled": true,
"min_delta_ms": 10000,
"tolerance_factor": 4.0
},
"stats_update_coalesce_ms": 10.0
},
"stargate": {
"max_request_retries": 2,
"routing_key": "stargate-dev",
"model_id": "stargate-dev-model"
},
"client": {
"request_slo_ms": 10000,
"max_wait_ms": 10000,
"timeout_ms": 30000,
"ttft_slo_ms": 10000
},
"workload": {
"rates_rps": [
80.0,
160.0,
240.0,
320.0
],
"warmup_s": 60.0,
"measure_s": 180.0,
"fixed": {
"sessions": 768,
"input_tokens_min": 6661,
"input_tokens_max": 6661,
"output_tokens": 256,
"session_zipf_s": 0.0
}
},
"policies": [
{
"name": "waw-deployed",
"load_balancer": {
"algorithm": "wait-and-widen",
"cache_affinity_backend_selection_count": 1,
"cache_affinity_input_tokens_scale": 0.1,
"cache_affinity_virtual_nodes": 150,
"cache_affinity_wait_ms": 300,
"comparator": "utilization",
"max_queued": 4,
"n": 2,
"next_bucket_unlock_factor": 0.25,
"require_cache_affinity_key": true,
"require_input_tokens": true,
"ttft_bucket_size_ms": 20
}
},
{
"name": "pulsar-waw-deployed",
"load_balancer": {
"algorithm": "pulsar-wait-and-widen",
"max_queue_time_floor_ms": 4000,
"max_queue_time_ceil_ms": 4000,
"max_queued": 4,
"n": 2,
"next_bucket_unlock_factor": 0.25,
"require_cache_affinity_key": true,
"require_input_tokens": true,
"ttft_bucket_size_ms": 20
}
},
{
"name": "power-of-2-baseline",
"load_balancer": {
"algorithm": "power-of-n",
"sample_count": 2,
"comparator": "ttft"
}
}
]
}
Loading
Loading