99 lines
3.2 KiB
Rust
99 lines
3.2 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2026 The SGLang Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use serde_json::{json, Value};
|
|
use sgl_router::tokenizer::{adapter, chat_formatter::ChatFormatter};
|
|
use sha2::{Digest, Sha256};
|
|
|
|
fn check_fixture(fixture: &str, model_type: &str) {
|
|
let fixture: Value = serde_json::from_str(fixture).unwrap();
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = dir.path().join("tokenizer.json");
|
|
std::fs::write(&path, "{}").unwrap();
|
|
let hf_home = std::env::var_os("HF_HOME")
|
|
.map(PathBuf::from)
|
|
.or_else(|| dirs::home_dir().map(|p| p.join(".cache/huggingface")));
|
|
let cached = hf_home.map(|p| {
|
|
p.join("hub")
|
|
.join(format!(
|
|
"models--{}",
|
|
fixture["model"].as_str().unwrap().replace('/', "--")
|
|
))
|
|
.join("snapshots")
|
|
.join(fixture["revision"].as_str().unwrap())
|
|
.join("tokenizer.json")
|
|
});
|
|
let tokenizer = cached
|
|
.filter(|p| p.is_file())
|
|
.map(|p| adapter::load(p.to_str().unwrap()).unwrap());
|
|
if tokenizer.is_none() {
|
|
eprintln!(
|
|
"{}: checking rendered text; pinned tokenizer is not cached",
|
|
fixture["model"]
|
|
);
|
|
}
|
|
for case in fixture["cases"].as_array().unwrap() {
|
|
std::fs::write(
|
|
dir.path().join("config.json"),
|
|
json!({"model_type":model_type, "dsv4_reasoning_effort_profile":case["profile"]})
|
|
.to_string(),
|
|
)
|
|
.unwrap();
|
|
let formatter = ChatFormatter::load("served-alias", path.to_str().unwrap())
|
|
.unwrap()
|
|
.unwrap();
|
|
let text = formatter
|
|
.render(&case["request"])
|
|
.unwrap_or_else(|e| panic!("{}: {e:#}", case["name"]));
|
|
assert_eq!(text, case["prompt"].as_str().unwrap(), "{}", case["name"]);
|
|
if let Some(tokenizer) = &tokenizer {
|
|
let ids = formatter.encode(tokenizer, &case["request"]).unwrap();
|
|
let mut hash = Sha256::new();
|
|
for id in &ids {
|
|
hash.update(id.to_le_bytes());
|
|
}
|
|
assert_eq!(
|
|
ids.len() as u64,
|
|
case["token_count"].as_u64().unwrap(),
|
|
"{}",
|
|
case["name"]
|
|
);
|
|
assert_eq!(
|
|
format!("{:x}", hash.finalize()),
|
|
case["token_sha256"].as_str().unwrap(),
|
|
"{}",
|
|
case["name"]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn v4_matches_sglang_serving() {
|
|
check_fixture(
|
|
include_str!("../../fixtures/deepseek/v4.json"),
|
|
"deepseek_v4",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn v41_matches_sglang_serving() {
|
|
check_fixture(
|
|
include_str!("../../fixtures/deepseek/v41.json"),
|
|
"deepseek_v41",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn v41_leaves_unsupported_shapes_to_the_worker() {
|
|
let formatter = ChatFormatter::deepseek_native(Some("deepseek_v41"), "alias").unwrap();
|
|
for request in [
|
|
json!({"messages":[{"role":"developer","content":"rule"}]}),
|
|
json!({"messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":"https://example.com/a.png"}}]}]}),
|
|
] {
|
|
assert!(formatter.render(&request).is_err(), "{request}");
|
|
}
|
|
}
|