[sgl-router] Prepare dynamo-render dependencies (#39457)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kan Wu
2026-09-14 19:08:16 -07:00
committed by GitHub
co-authored by Claude Fable 5.1
parent a25f213bc4
commit e89d8facab
8 changed files with 4735 additions and 114 deletions
+6 -32
View File
@@ -102,38 +102,12 @@ jobs:
# of self-hosted-runner pip installs that compile Rust extensions.
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "true"
# Supply-chain guard: deny.toml allowlists the dynamo git source but
# cannot pin a SHA. Bumps to this constant must be a deliberate PR.
DYNAMO_TOKENIZERS_EXPECTED_REV: "1efdd4dcb901caeae636131321094090d252c8d6"
steps:
- uses: actions/checkout@v4
- name: Install protoc
run: sudo bash scripts/ci/utils/install_protoc.sh
- name: Verify dynamo-* SHA pin
run: |
set -euo pipefail
revs=$(grep -E '^dynamo-[a-z]+[[:space:]]*=.*\brev[[:space:]]*=' experimental/sgl-router/Cargo.toml \
| sed -E 's/.*rev[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/')
if [ -z "${revs:-}" ]; then
echo "::error::no dynamo-* git deps with rev= found in experimental/sgl-router/Cargo.toml"
exit 1
fi
count=0
while IFS= read -r r; do
count=$((count + 1))
if [ "$r" != "$DYNAMO_TOKENIZERS_EXPECTED_REV" ]; then
echo "::error::dynamo-* SHA pin mismatch in experimental/sgl-router/Cargo.toml"
echo " expected: $DYNAMO_TOKENIZERS_EXPECTED_REV"
echo " actual: $r"
echo "All revs found:"
echo "$revs" | sed 's/^/ /'
exit 1
fi
done <<< "$revs"
echo "dynamo-* SHA pin OK: $count entries all at $DYNAMO_TOKENIZERS_EXPECTED_REV"
# sccache install is best-effort: the GitHub release CDN intermittently
# returns 5xx and the action's built-in retry is shallow. If it fails,
# we unset RUSTC_WRAPPER below and proceed without compile caching —
@@ -163,11 +137,11 @@ jobs:
- name: cargo check
working-directory: experimental/sgl-router
run: cargo check --workspace --all-targets
run: cargo check --locked --workspace --all-targets
- name: cargo clippy
working-directory: experimental/sgl-router
run: cargo clippy --workspace --all-targets -- -D warnings
run: cargo clippy --locked --workspace --all-targets -- -D warnings
- name: cargo fmt
working-directory: experimental/sgl-router
@@ -238,7 +212,7 @@ jobs:
working-directory: experimental/sgl-router
run: |
START=$(date +%s)
cargo build --release --all-targets
cargo build --locked --release --all-targets
END=$(date +%s)
BUILD_SECONDS=$((END - START))
echo "tier_2_build_duration_seconds=${BUILD_SECONDS}" >> "$GITHUB_ENV"
@@ -252,7 +226,7 @@ jobs:
# the Qwen3-0.6B tokenizer.json. Filter is matched against the
# full test path `tokenizer::parity::parity_matrix`; substring
# `parity_matrix` is unique to that test.
run: cargo test --release --workspace -- --skip parity_matrix
run: cargo test --locked --release --workspace -- --skip parity_matrix
# Regenerate the cross-impl block-hash parity fixture and fail if it
# differs from the committed file. The Python script replicates
@@ -372,7 +346,7 @@ jobs:
working-directory: experimental/sgl-router
run: |
source "$HOME/.cargo/env"
cargo build --release --workspace
cargo build --locked --release --workspace
# Install SGLang from the local checkout in editable mode (no PyPI
# version pin) — mirrors `pr-test-rust.yml` so the router e2e runs
@@ -411,7 +385,7 @@ jobs:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
source "$HOME/.cargo/env"
cargo test --release --test component tokenizer::parity
cargo test --locked --release --test component tokenizer::parity
sgl-router-finish:
name: finish
+1
View File
@@ -233,6 +233,7 @@ work_dirs/
Cargo.lock
!rust/Cargo.lock
!rust/sglang-radix-tree/Cargo.lock
!experimental/sgl-router/Cargo.lock
# Generated vision test fixtures (regenerate with: python scripts/generate_vision_golden.py)
sgl-model-gateway/tests/fixtures/golden/
+13 -19
View File
@@ -12,11 +12,9 @@
# manifests → cargo fetch → copy src" approach caches only the fetched
# registry; every source change still recompiles every dep.
#
# `Cargo.lock` is gitignored repo-wide (root .gitignore "# Rust lib"
# block), so we generate it inside the chef stage with `cargo
# generate-lockfile` and propagate that lockfile to the builder via
# `COPY --from=chef`. Both stages thus build against the same lockfile,
# preserving --locked semantics within a single Docker build.
# `experimental/sgl-router/Cargo.lock` is committed, and every cargo step
# below runs with `--locked`, so the image ships exactly the dependency
# graph CI checked, tested, and license-audited.
#
# Build (from the repo root):
# docker build -f docker/sgl-router.Dockerfile -t sgl-router:dev .
@@ -28,24 +26,23 @@
# Image budget: < 100 MB stripped (M6 acceptance). Verify with
# `docker image inspect sgl-router:dev --format '{{.Size}}'`.
ARG RUST_VERSION=1.90
ARG RUST_VERSION=1.92
ARG DEBIAN_VERSION=bookworm
######################## STAGE 1 — chef recipe ##########################
FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS chef
RUN cargo install cargo-chef --locked --version ^0.1
WORKDIR /work/sgl-router
COPY experimental/sgl-router/Cargo.toml ./
COPY experimental/sgl-router/Cargo.toml experimental/sgl-router/Cargo.lock ./
COPY experimental/sgl-router/sgl-kv-indexer/Cargo.toml sgl-kv-indexer/Cargo.toml
# Stub a minimal src tree so cargo can resolve the workspace, generate
# the lockfile (gitignored upstream), then prepare the chef recipe.
# Stub a minimal src tree so cargo can see the workspace targets, then
# prepare the chef recipe.
RUN mkdir -p src sgl-kv-indexer/src/bin \
&& echo "fn main() {}" > src/main.rs \
&& echo "" > src/lib.rs \
&& echo "" > sgl-kv-indexer/src/lib.rs \
&& echo "fn main() {}" > sgl-kv-indexer/src/bin/kv-indexer-server.rs \
&& echo "fn main() {}" > sgl-kv-indexer/src/bin/kv-indexer-bridge.rs \
&& cargo generate-lockfile \
&& cargo chef prepare --recipe-path recipe.json \
&& rm -rf src sgl-kv-indexer/src
@@ -66,26 +63,23 @@ WORKDIR /work/sgl-router
ENV PCRE2_SYS_STATIC=1
COPY --from=chef /work/sgl-router/recipe.json ./recipe.json
COPY --from=chef /work/sgl-router/Cargo.lock ./Cargo.lock
COPY experimental/sgl-router/sgl-kv-indexer/Cargo.toml sgl-kv-indexer/Cargo.toml
# Cook (compile + cache) the dep graph from the recipe. The recipe carries every
# workspace member's manifest, so chef recreates the Indexer's source stubs itself.
RUN cargo chef cook --release --recipe-path recipe.json
# workspace member's manifest and the lockfile, so chef recreates the Indexer's
# source stubs itself.
RUN cargo chef cook --locked --release --recipe-path recipe.json
# Now bring in the real sources and the manifest they need.
COPY experimental/sgl-router/Cargo.toml ./
# Now bring in the real sources and the manifests they need.
COPY experimental/sgl-router/Cargo.toml experimental/sgl-router/Cargo.lock ./
COPY experimental/sgl-router/src ./src
COPY experimental/sgl-router/sgl-kv-indexer/Cargo.toml sgl-kv-indexer/Cargo.toml
COPY experimental/sgl-router/sgl-kv-indexer/build.rs sgl-kv-indexer/build.rs
COPY experimental/sgl-router/sgl-kv-indexer/proto sgl-kv-indexer/proto
COPY experimental/sgl-router/sgl-kv-indexer/src sgl-kv-indexer/src
# --locked is intentionally omitted: the lockfile is generated in-container
# (gitignored upstream) and `cargo chef cook` may have mutated it during the
# dep-cook step, so a strict --locked check would spuriously fail.
RUN touch sgl-kv-indexer/build.rs \
&& cargo build --release --bin sgl-router \
&& cargo build --locked --release --bin sgl-router \
&& strip target/release/sgl-router
######################## STAGE 3 — runtime ##############################
+4687
View File
File diff suppressed because it is too large Load Diff
+13 -14
View File
@@ -22,10 +22,19 @@ path = "src/main.rs"
unused_qualifications = "warn"
[dependencies]
# Dynamo crates — pinned by SHA. Bumps are manual PRs.
dynamo-protocols = { git = "https://github.com/ai-dynamo/dynamo", rev = "1efdd4dcb901caeae636131321094090d252c8d6" }
dynamo-tokenizers = { git = "https://github.com/ai-dynamo/dynamo", rev = "1efdd4dcb901caeae636131321094090d252c8d6" }
dynamo-parsers = { git = "https://github.com/ai-dynamo/dynamo", rev = "1efdd4dcb901caeae636131321094090d252c8d6" }
# Pin Dynamo versions and commit Cargo.lock so builds are reproducible.
dynamo-tokenizers = "=1.8.1"
# Prepare the Dynamo renderer dependency for the following migration.
dynamo-renderer = "=5.1.2"
# Chat-template rendering for cache-aware routing, retained until the
# renderer migration replaces it: the engine caches tokens AFTER applying the
# model's chat template, so the router must render the same template before
# hashing or its token_ids diverge from the engine's stored blocks. `pycompat`
# supplies the Python str/dict methods HF templates call; `chrono` backs
# `strftime_now`.
minijinja = { version = "2.24", features = ["loop_controls", "json"] }
minijinja-contrib = { version = "2", features = ["pycompat"] }
chrono = { version = "0.4", default-features = false, features = ["clock"] }
# Async runtime + http
tokio = { version = "1.42", features = ["full"] }
@@ -44,16 +53,6 @@ serde_json = { version = "1", features = ["preserve_order"] }
# this pulls no openssl/native-tls (matching reqwest's rustls-tls above).
hf-hub = { version = "0.4", default-features = false, features = ["ureq"] }
# Chat-template rendering for cache-aware routing: the engine caches tokens
# AFTER applying the model's chat template, so the router renders the same
# template (from tokenizer_config.json) before hashing — otherwise its query
# token_ids diverge from the engine's stored blocks. `pycompat` supplies the
# Python str/dict methods HF chat templates rely on (.startswith, .items, ...).
minijinja = { version = "2", features = ["loop_controls", "json"] }
minijinja-contrib = { version = "2", features = ["pycompat"] }
# `strftime_now` chat-template helper (some templates inject the current date).
chrono = { version = "0.4", default-features = false, features = ["clock"] }
# Utilities
anyhow = "1"
thiserror = "2"
+9 -43
View File
@@ -3,11 +3,12 @@ all-features = true
[advisories]
yanked = "warn"
# Unmaintained advisories are demoted to warnings: the affected crates
# (unic-*, paste, number_prefix) are all transitive through dynamo-parsers
# and have no available upgrades. They pose no security risk; revisit
# if/when dynamo-parsers feature-flags rustpython-parser off upstream.
unmaintained = "none"
# Unmaintained advisories are checked for direct dependencies only. The
# transitive ones we cannot fix from here (`paste` via tokenizers ->
# dynamo-tokenizers, `number_prefix` via indicatif -> hf-hub, `backoff` and
# `instant` via kube-runtime, `rustls-pemfile` via kube-client) pose no
# security risk; revisit when the upstream crates drop them.
unmaintained = "workspace"
# `ignore` left empty — we want to be notified of new CVEs.
ignore = []
@@ -24,52 +25,17 @@ allow = [
"Zlib",
"CC0-1.0",
"MPL-2.0",
# Both added per the initial license review:
"NCSA", # libfuzzer-sys (transitive via rav1e) — BSD-equivalent permissive.
# Added per the initial license review:
"CDLA-Permissive-2.0", # webpki-roots — Linux Foundation permissive license.
]
confidence-threshold = 0.93
# LGPL-3.0-only is accepted on a per-crate exception basis. Rationale:
# - sgl-router is Apache-2.0 and ships full source on the public sglang
# repo, so the LGPL "users must be able to relink" requirement is
# satisfied by the conventional Rust-ecosystem interpretation (anyone
# can git-clone the repo, bump a malachite version, rebuild).
# - The malachite-* crates are pure-Rust arbitrary-precision math, used
# four levels deep through dynamo-parsers → rustpython-parser → malachite-bigint.
# They are NOT on the routing hot path; dynamo-parsers is only wired
# into chat-completions for tool-call parsing.
# - Revisit if/when: (a) a regulated-enterprise customer objects, or
# (b) dynamo-parsers feature-flags rustpython-parser off upstream.
[[licenses.exceptions]]
name = "malachite"
allow = ["LGPL-3.0-only"]
[[licenses.exceptions]]
name = "malachite-base"
allow = ["LGPL-3.0-only"]
[[licenses.exceptions]]
name = "malachite-nz"
allow = ["LGPL-3.0-only"]
[[licenses.exceptions]]
name = "malachite-q"
allow = ["LGPL-3.0-only"]
[[licenses.exceptions]]
name = "malachite-bigint"
allow = ["LGPL-3.0-only"]
[bans]
multiple-versions = "warn"
wildcards = "deny"
# Git deps (e.g. dynamo-* pinned by SHA) have no semver version req and would
# otherwise trip the wildcard check. allow-wildcard-paths exempts non-registry
# (git + path) sources so we only deny bare '*' on crates.io deps.
# Workspace path dependencies have no semver requirement.
allow-wildcard-paths = true
[sources]
unknown-registry = "deny"
unknown-git = "allow" # dynamo git dep pinned by SHA in Cargo.toml.
allow-git = ["https://github.com/ai-dynamo/dynamo"]
unknown-git = "deny"
+1 -1
View File
@@ -1,4 +1,4 @@
[toolchain]
channel = "1.90"
channel = "1.92"
profile = "minimal"
components = ["clippy", "rustfmt"]
@@ -3,14 +3,14 @@
# Context root: repo root (one level above experimental/sgl-router/).
# Matches rust-toolchain.toml's pinned channel, avoiding an in-build rustup channel-sync.
FROM rust:1.90-bookworm AS builder
FROM rust:1.92-bookworm AS builder
# Pin to the exact toolchain pre-installed in the base image so rustup
# doesn't try to sync the channel manifest when it sees rust-toolchain.toml's
# `channel = "1.90"`.
ENV RUSTUP_TOOLCHAIN=1.90.0
# `channel = "1.92"`.
ENV RUSTUP_TOOLCHAIN=1.92.0
# libssl-dev + pkg-config ship with rust:1.90-bookworm already; protoc does not,
# libssl-dev + pkg-config ship with rust:1.92-bookworm already; protoc does not,
# and the Indexer's build script needs it to compile the KV-indexer protos.
RUN apt-get update \
&& apt-get install -y --no-install-recommends protobuf-compiler \
@@ -25,7 +25,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/build/experimental/sgl-router/target \
cd /build/experimental/sgl-router \
&& cargo build --release --bin sgl-router \
&& cargo build --locked --release --bin sgl-router \
&& cp target/release/sgl-router /usr/local/bin/sgl-router
FROM debian:bookworm-slim