Prhub

#27073 [router] Configure experimental sgl-router via CLI flags instead of a config file

原始 PR 作者 Kangyan-Zhou 合并时间 2026-06-05 10:02 文件变更 43 提交数 4 评论 1 代码增减 +2355 / -1237

执行摘要

sgl-router 从配置文件驱动改为纯 CLI 标志驱动

原始实验性 sgl-router 仅支持通过 --config 参数读取 TOML/YAML 配置文件,缺乏 CLI 灵活性,且与 Python 版本接口不一致。本 PR 将它改为全 CLI 标志驱动,并统一为单一模型服务,提高可用性和可发现性。

值得精读,特别是 CLI 设计原则(互斥、条件校验、类型驱动验证)以及从 serde 迁移到 clap 的平滑过渡。对设计 Rust CLI 应用有参考价值。

讨论亮点

该 PR 审核评论为 0,未发生实质性讨论。但作者在描述中阐述了关键设计决策:发现方式互斥(--worker-urls XOR --service-discovery)、条件参数校验(cb 相关标志仅在 --cb-threshold 设置时有效)、以及 K8s 选择器组合在构建时解析为不可枚举状态的枚举(K8sDiscoveryMode),避免运行时校验失败。

实现拆解

  1. 新增 config/cli.rs:定义 Cli 结构体使用 clap 派生所有标志,实现 into_config 方法构建验证后的 Config,包括发现方式互斥检查、K8s 选择器语法解析和条件参数校验。
  2. 重构 config/types.rs:移除所有 serde 派生和相关依赖,将 Config.models 改为 Config.model(单一模型),DiscoveryConfig 包装内联为直接 DiscoveryBackend,PolicyKind/LogFormat 改用 clap::ValueEnum,无效状态无法表示。
  3. 重构 config/mod.rs:删除 Config::from_path 方法和文件加载测试,简化 validate 仅检查类型系统无法保证的不变量,避免重复验证。
  4. 适配所有使用者:更新策略工厂、API 路由(/v1/models)、tokenizer 加载、观测性等模块,重写所有测试(直接构造 Config 而非通过文件),删除 serde_yaml/toml/humantime-serde 依赖。
  5. 附带增强(相同 PR 的后继提交):添加 bigram token_ids 解码支持、per-request 访问日志 + overlap_blocks 指标、EAGLE 模型的 bigram 块哈希算法,提升 cache-aware 路由兼容性。
文件 模块 状态 重要度
experimental/sgl-router/src/config/cli.rs 路由器配置 added 9.36
experimental/sgl-router/src/config/types.rs 路由器配置 modified 9.05
experimental/sgl-router/src/config/mod.rs 路由器配置 modified 8.86
experimental/sgl-router/src/policies/cache_aware_zmq.rs 路由策略 modified 8.94
experimental/sgl-router/src/policies/kv_events/hash.rs 路由策略 modified 8.55
experimental/sgl-router/src/policies/kv_events/block_size_oracle.rs 路由策略 modified 8.03

关键符号

Cli::into_config Cli::build_discovery Config::validate CacheAwareZmqPolicy::with_metrics compute_block_hashes_bigram resolve_mode block_size_oracle::set_bigram adapter::load

关键源码片段

experimental/sgl-router/src/config/cli.rs entrypoint

CLI 入口,新增全部命令行标志定义和 into_config 核心转换逻辑,是本次重构的中心文件。

// SPDX-FileCopyrightText: Copyright (c) 2026 The SGLang Authors
// SPDX-License-Identifier: Apache-2.0//! 命令行界面。路由器完全通过标志配置 —— 没有配置文件。
//! [`Cli::into_config`] 将标志解析为验证后的 [`Config`]。use anyhow::{anyhow, Result};
use clap::Parser;
use std::num::NonZeroU32;use crate::config::{
    default_cb_cool_down, default_proxy_request_timeout_secs, default_stale_request_timeout_secs,
    resolve_mode, ActiveLoadConfig, CacheAwareConfig, CircuitBreakerConfig, Config,
    DiscoveryBackend, K8sDiscoveryConfig, LogFormat, ModelConfig, ObservabilityConfig, PolicyKind,
    ProxyConfig, ServerConfig, StaticUrlsDiscoveryConfig,
};/// `sgl-router` — 轻量 KV 感知 OpenAI 兼容 SGLang worker 路由器。
///
/// 发现方式互斥:传递 `--worker-urls`(静态地址)或 `--service-discovery`
/// (Kubernetes EndpointSlice)—— 必须且仅需一种。
#[derive(Parser, Debug)]
#[command(
    name = "sgl-router",
    version,
    about = "Slim KV-aware OpenAI-compatible router for SGLang workers"
)]
pub struct Cli {
    // ---- server ----
    #[arg(long, default_value = "127.0.0.1")]
    pub host: String,
    #[arg(long, default_value_t = 30000)]
    pub port: u16,    // ---- model (exactly one) ----
    #[arg(long)]
    pub model_id: String,
    /// Tokenizer 来源:本地路径或 HuggingFace repo id;省略时降级为 `--model-id`
    #[arg(long)]
    pub tokenizer_path: Option<String>,
    /// 路由策略:round_robin / random / power_of_two / cache_aware_zmq
    #[arg(long, value_enum, default_value = "round_robin")]
    pub policy: PolicyKind,    // ---- circuit breaker (opt-in) ----
    #[arg(long)]
    pub cb_threshold: Option<NonZeroU32>,
    #[arg(long)]
    pub cb_cool_down_secs: Option<u64>,
    // ... 其余字段(缓存调优、发现、代理、日志等)
}impl Cli {
    /// 将解析后的标志转换为验证过的 [`Config`]。
    pub fn into_config(self) -> Result<Config> {
        // 构建发现后端(互斥检查 + K8s 选择器解析)
        let discovery = self.build_discovery()?;
        // 构建模型配置(包含 tokenizer_path 降级逻辑)
        let model = self.with_model();
        // 可选断路器
        let circuit_breaker = self.cb_threshold.map(|threshold| CircuitBreakerConfig {
            threshold,
            cool_down_secs: self.cb_cool_down_secs.unwrap_or_else(default_cb_cool_down),
        });
        // ... 组装 Config 并验证
        let cfg = Config { /* ... */ };
        cfg.validate()?;
        Ok(cfg)
    }
}
experimental/sgl-router/src/config/types.rs entrypoint

类型核心定义,移除了 serde 派生,Config.models → Config.model,DiscoveryBackend 直接内联,PolicyKind/LogFormat 改用 clap::ValueEnum。

use std::num::NonZeroU32;/// 内存中路由器配置,由 [`cli::Cli::into_config`] 从 CLI 标志构建。
/// 路由器仅服务一个模型。
#[derive(Debug, Clone)]
pub struct Config {
    pub server: ServerConfig,
    pub observability: ObservabilityConfig,
    pub model: ModelConfig, // 单一模型(原 Vec<ModelConfig>)
    pub discovery: DiscoveryBackend, // 直接使用后端枚举(原 DiscoveryConfig 包装)
    pub proxy: ProxyConfig,
    pub active_load: ActiveLoadConfig,
}/// 模型配置,每个路由器实例仅一个。
#[derive(Debug, Clone)]
pub struct ModelConfig {
    pub id: String,
    /// Tokenizer 来源路径或 HuggingFace repo id;省略时降级为 id
    pub tokenizer_path: String,
    pub policy: PolicyKind,
    pub circuit_breaker: Option<CircuitBreakerConfig>,
    /// cache_aware_zmq 策略专有调优
    pub cache_aware: Option<CacheAwareConfig>,
}/// 路由策略枚举,改用 clap::ValueEnum 而非 serde,在 CLI 解析时失效。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
pub enum PolicyKind {
    #[default]
    #[value(name = "round_robin")]
    RoundRobin,
    #[value(name = "random")]
    Random,
    #[value(name = "power_of_two")]
    PowerOfTwo,
    /// KV cache 感知路由,需要 tokenizer 和 ZMQ 事件
    #[value(name = "cache_aware_zmq")]
    CacheAwareZmq,
}

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

主要风险包括:(1)配置接口破坏:现有配置文件的用户必须迁移到 CLI 标志;(2)单一模型变更:原多模型用户受影响;(3)包含多增强增大了回退复杂度。但 experimental 模块不保证稳定性,测试全通过,风险可控。

对用户:启动命令改变但更直观且与 Python 版一致;对系统:简化配置逻辑,移除 serde 和文件 I/O 依赖;对团队:类型设计使无效状态不可表示,提高健壮性。影响范围限于 experimental/router 模块,程度较大。

配置接口破坏 单一模型限制 多增强增加回退复杂度

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论