[sgl-router] Prepare dynamo-render dependencies (#39457)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
a25f213bc4
commit
e89d8facab
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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 ##############################
|
||||
|
||||
Generated
+4687
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user