[Rust Frontend] Honor HF revisions, offline mode, and cache directory (#56386)

Signed-off-by: Bugen Zhao <i@bugenzhao.com> Co-authored-by: Codex <noreply@openai.com>

Bugen Zhao committed Sep 12, 2026 at 11:39 UTC 485421b1c3572597a4cfaece04836a843537435a
11 files changed +201 -28
rust/src/chat/src/backend/hf.rs
+2 -1
@@ -129,7 +129,7 @@ pub(super) async fn load_model_backends(
129 model_id: &str,
130 options: LoadModelBackendsOptions,
131 ) -> Result<LoadedModelBackends> {
132 - let files = ResolvedModelFiles::new(model_id).await?;
132 + let files = ResolvedModelFiles::new(model_id, options.revision.as_deref()).await?;
133 let text_backend = HfTextBackend::from_resolved_model_files(
134 files.clone(),
135 model_id.to_string(),
@@ -231,6 +231,7 @@ mod tests {
231 resolved_files(config_json, tokenizer_config_json),
232 "test-model".to_string(),
233 LoadModelBackendsOptions {
234 + revision: None,
235 generation_config: Default::default(),
236 renderer,
237 language_model_only: false,
rust/src/chat/src/backend/mod.rs
+2
@@ -61,6 +61,8 @@ pub type DynChatTextBackend = Arc<dyn ChatTextBackend>;
61 /// Frontend-side chat backend loading options.
62 #[derive(Debug, Clone, PartialEq, Eq, Default)]
63 pub struct LoadModelBackendsOptions {
64 + /// Model revision on the Hugging Face Hub (branch, tag, or commit SHA).
65 + pub revision: Option<String>,
66 /// Which generation-config sampling defaults to inherit.
67 pub generation_config: GenerationConfigMode,
68 /// Which chat renderer implementation to use.
rust/src/cmd/src/cli.rs
+12
@@ -110,6 +110,9 @@ pub enum BenchCommand {
110 pub struct RenderArgs {
111 /// Model identifier or local model directory containing tokenizer files.
112 model: String,
113 + /// Model revision on the Hugging Face Hub (branch, tag, or commit SHA).
114 + #[arg(long)]
115 + revision: Option<String>,
116 /// HTTP bind host.
117 #[arg(long, default_value = "127.0.0.1")]
118 host: String,
@@ -157,6 +160,7 @@ impl RenderArgs {
160
161 RenderConfig {
162 model: self.model,
163 + revision: self.revision,
164 served_model_name: self.served_model_name,
165 host: self.host,
166 port: self.port,
@@ -195,6 +199,11 @@ pub struct SharedRuntimeArgs {
199 /// public model ID.
200 pub model: String,
201
202 + /// Model revision on the Hugging Face Hub (branch, tag, or commit SHA).
203 + #[arg(long)]
204 + #[serde(default)]
205 + pub revision: Option<String>,
206 +
207 /// The source of generation-config sampling defaults. `"auto"` loads the
208 /// model's defaults, while `"vllm"` uses vLLM's neutral defaults.
209 #[arg(long, default_value_t)]
@@ -481,6 +490,7 @@ impl SharedRuntimeArgs {
490 None => CoordinatorMode::None,
491 },
492 model: self.model,
493 + revision: self.revision,
494 generation_config: self.generation_config,
495 served_model_name: self.served_model_name,
496 listener_mode: HttpListenerMode::InheritedFd { fd: listen_fd },
@@ -536,6 +546,7 @@ impl SharedRuntimeArgs {
546 },
547 coordinator_mode: CoordinatorMode::MaybeInProc,
548 model: self.model,
549 + revision: self.revision,
550 generation_config: self.generation_config,
551 served_model_name: self.served_model_name,
552 listener_mode,
@@ -743,6 +754,7 @@ impl ServeArgs {
754
755 self.managed_engine.clone().into_config(
756 self.runtime.model.clone(),
757 + self.runtime.revision.clone(),
758 self.runtime.max_logprobs,
759 profiler_config,
760 reasoning_parser.as_deref(),
rust/src/cmd/src/cli/tests.rs
+26 -1
@@ -36,6 +36,8 @@ fn render_args_build_config_without_tls() {
36 "vllm-rs",
37 "render",
38 "Qwen/Qwen2.5-0.5B-Instruct",
39 + "--revision",
40 + "release",
41 "--port",
42 "8080",
43 "--max-model-len",
@@ -55,6 +57,7 @@ fn render_args_build_config_without_tls() {
57 let config = args.into_config();
58
59 assert_eq!(config.model, "Qwen/Qwen2.5-0.5B-Instruct");
60 + assert_eq!(config.revision.as_deref(), Some("release"));
61 assert_eq!(config.host, "127.0.0.1");
62 assert_eq!(config.port, 8080);
63 assert_eq!(config.max_model_len, Some(32768));
@@ -173,6 +176,7 @@ fn serve_args_forward_python_flags_with_separator() {
176 uds: None,
177 runtime: SharedRuntimeArgs {
178 model: "Qwen/Qwen3-0.6B",
179 + revision: None,
180 generation_config: Auto,
181 engine_ready_timeout_secs: 600,
182 tool_call_parser: Auto,
@@ -1007,6 +1011,7 @@ fn frontend_args_accept_json() {
1011 data_parallel_size: None,
1012 runtime: SharedRuntimeArgs {
1013 model: "Qwen/Qwen3-0.6B",
1014 + revision: None,
1015 generation_config: Auto,
1016 engine_ready_timeout_secs: 600,
1017 tool_call_parser: None,
@@ -1125,7 +1130,7 @@ fn frontend_args_json_accepts_supported_non_default_fields() {
1130 "--output-address",
1131 "ipc:///tmp/output.sock",
1132 "--args-json",
1128 - r#"{"model_tag":"Qwen/Qwen3-0.6B","generation_config":"vllm","engine_ready_timeout_secs":42,"tool_call_parser":"hermes","reasoning_parser":"qwen3_thinking","tokenizer_mode":"deepseek_v32","language_model_only":true,"max_logprobs":-1,"shutdown_timeout":3}"#,
1133 + r#"{"model_tag":"Qwen/Qwen3-0.6B","generation_config":"vllm","revision":"release","engine_ready_timeout_secs":42,"tool_call_parser":"hermes","reasoning_parser":"qwen3_thinking","tokenizer_mode":"deepseek_v32","language_model_only":true,"max_logprobs":-1,"shutdown_timeout":3}"#,
1134 ])
1135 .unwrap();
1136
@@ -1133,6 +1138,7 @@ fn frontend_args_json_accepts_supported_non_default_fields() {
1138 panic!("expected frontend args");
1139 };
1140 assert_eq!(args.runtime.engine_ready_timeout_secs, 42);
1141 + assert_eq!(args.runtime.revision.as_deref(), Some("release"));
1142 assert_eq!(args.runtime.generation_config, GenerationConfigMode::Vllm);
1143 assert_eq!(
1144 args.runtime.tool_call_parser,
@@ -1604,6 +1610,7 @@ fn serve_args_accept_handshake_aliases() {
1610 uds: None,
1611 runtime: SharedRuntimeArgs {
1612 model: "Qwen/Qwen3-0.6B",
1613 + revision: None,
1614 generation_config: Auto,
1615 engine_ready_timeout_secs: 600,
1616 tool_call_parser: Auto,
@@ -1752,6 +1759,7 @@ fn serve_frontend_config_uses_dp_address_as_advertised_host() {
1759 },
1760 coordinator_mode: MaybeInProc,
1761 model: "Qwen/Qwen3-0.6B",
1762 + revision: None,
1763 generation_config: Auto,
1764 served_model_name: [],
1765 listener_mode: BindTcp {
@@ -1839,6 +1847,7 @@ fn serve_frontend_config_keeps_tcp_transport_for_non_local_only_topology() {
1847 },
1848 coordinator_mode: MaybeInProc,
1849 model: "Qwen/Qwen3-0.6B",
1850 + revision: None,
1851 generation_config: Auto,
1852 served_model_name: [],
1853 listener_mode: BindTcp {
@@ -1948,6 +1957,7 @@ fn frontend_config_uses_external_coordinator_when_coordinator_address_is_present
1957 address: "tcp://127.0.0.1:7000",
1958 },
1959 model: "Qwen/Qwen3-0.6B",
1960 + revision: None,
1961 generation_config: Auto,
1962 served_model_name: [],
1963 listener_mode: InheritedFd {
@@ -2087,3 +2097,18 @@ fn frontend_args_json_disables_profiling_when_profiler_type_is_null() {
2097 let config = args.into_config();
2098 assert_eq!(config.profiler, None);
2099 }
2100 +
2101 +#[test]
2102 +fn serve_revision_reaches_frontend_and_managed_engine() {
2103 + let cli =
2104 + Cli::try_parse_from(["vllm-rs", "serve", "test/model", "--revision", "release"]).unwrap();
2105 + let Command::Serve(args) = cli.command else {
2106 + panic!("expected serve args");
2107 + };
2108 + assert_eq!(
2109 + args.to_frontend_config("tcp://localhost:1234".into()).revision.as_deref(),
2110 + Some("release")
2111 + );
2112 + let engine = args.to_managed_engine_config(1234);
2113 + assert!(engine.python_args.windows(2).any(|args| args == ["--revision", "release"]));
2114 +}
rust/src/managed-engine/src/cli.rs
+5
@@ -84,6 +84,7 @@ impl ManagedEngineArgs {
84 pub fn into_config(
85 self,
86 model: String,
87 + revision: Option<String>,
88 max_logprobs: Option<i32>,
89 profiler_config: Option<String>,
90 reasoning_parser: Option<&str>,
@@ -95,6 +96,10 @@ impl ManagedEngineArgs {
96 ) -> ManagedEngineConfig {
97 let mut python_args = self.python_args;
98 // Manually forward some args to the Python engine.
99 + if let Some(revision) = revision {
100 + python_args.push("--revision".to_string());
101 + python_args.push(revision);
102 + }
103 if let Some(max_model_len) = self.max_model_len {
104 python_args.push("--max-model-len".to_string());
105 python_args.push(max_model_len);
rust/src/server/examples/external_engine_openai_qwen.rs
+1
@@ -60,6 +60,7 @@ async fn main() -> Result<()> {
60 },
61 coordinator_mode: CoordinatorMode::MaybeInProc,
62 model: args.model,
63 + revision: None,
64 generation_config: Default::default(),
65 served_model_name: vec![],
66 listener_mode: HttpListenerMode::BindTcp {
rust/src/server/src/config.rs
+2
@@ -210,6 +210,8 @@ pub struct Config {
210 pub coordinator_mode: CoordinatorMode,
211 /// Backend model identifier used for engine-core loading.
212 pub model: String,
213 + /// Model revision on the Hugging Face Hub (branch, tag, or commit SHA).
214 + pub revision: Option<String>,
215 /// Which generation-config sampling defaults to inherit.
216 pub generation_config: GenerationConfigMode,
217 /// Model name(s) exposed to clients via the OpenAI API. When non-empty,
rust/src/server/src/lib.rs
+1
@@ -98,6 +98,7 @@ async fn build_state(config: &Config) -> Result<Arc<AppState>> {
98 let loaded = load_model_backends(
99 &config.model,
100 LoadModelBackendsOptions {
101 + revision: config.revision.clone(),
102 generation_config: config.generation_config,
103 renderer: config.renderer,
104 language_model_only: config.language_model_only,
rust/src/server/src/render.rs
+3
@@ -24,6 +24,7 @@ use crate::{
24 #[derive(Debug)]
25 pub struct RenderConfig {
26 pub model: String,
27 + pub revision: Option<String>,
28 pub served_model_name: Vec<String>,
29 pub host: String,
30 pub port: u16,
@@ -67,6 +68,7 @@ async fn build_state(config: &RenderConfig) -> Result<Arc<RenderState>> {
68 let loaded = load_model_backends(
69 &config.model,
70 LoadModelBackendsOptions {
71 + revision: config.revision.clone(),
72 generation_config: Default::default(),
73 renderer: config.renderer,
74 language_model_only: true,
@@ -150,6 +152,7 @@ mod tests {
152 let error = serve_render(
153 RenderConfig {
154 model: "test-model".to_string(),
155 + revision: None,
156 served_model_name: Vec::new(),
157 host: "127.0.0.1".to_string(),
158 port: 8000,
rust/src/text/src/backend/hf/model_files.rs
+145 -25
@@ -3,8 +3,8 @@
3
4 use std::path::{Path, PathBuf};
5
6 -use hf_hub::Cache;
6 use hf_hub::api::tokio::{Api, ApiBuilder, ApiRepo};
7 +use hf_hub::{Cache, Repo, RepoType};
8 use thiserror_ext::AsReport as _;
9
10 use super::config::{HfTokenizerConfig, load_tokenizer_config};
@@ -56,14 +56,31 @@ impl ResolvedModelFiles {
56 /// Resolve tokenizer/config files from a local model directory first when
57 /// `model_id` points to one, otherwise consult the local HF cache and
58 /// finally the Hub.
59 - pub async fn new(model_id: &str) -> Result<Self> {
59 + pub async fn new(model_id: &str, revision: Option<&str>) -> Result<Self> {
60 if Path::new(model_id).is_dir() {
61 return resolve_local_model_files(Path::new(model_id));
62 }
63 - if let Some(files) = resolve_cached_model_files(model_id)? {
63 + let repo = Repo::with_revision(
64 + model_id.to_string(),
65 + RepoType::Model,
66 + revision.unwrap_or("main").to_string(),
67 + );
68 + // TODO: Let hf-hub read HF_HUB_CACHE after upgrading to 1.0.
69 + let cache = std::env::var_os("HF_HUB_CACHE")
70 + .map(|path| Cache::new(path.into()))
71 + .unwrap_or_else(Cache::from_env);
72 + if let Some(files) = resolve_cached_model_files(&cache, &repo)? {
73 return Ok(files);
74 }
66 - resolve_remote_model_files(model_id).await
75 + if is_hf_hub_offline() {
76 + return Err(Error::Tokenizer(format!(
77 + "model '{model_id}' at revision '{}' has no supported tokenizer in cache '{}' \
78 + and HF_HUB_OFFLINE is enabled",
79 + repo.revision(),
80 + cache.path().display(),
81 + )));
82 + }
83 + resolve_remote_model_files(model_id, repo, &cache).await
84 }
85 }
86
@@ -87,9 +104,13 @@ fn resolve_local_model_files(model_dir: &Path) -> Result<ResolvedModelFiles> {
104 })
105 }
106
90 -async fn resolve_remote_model_files(model_id: &str) -> Result<ResolvedModelFiles> {
91 - let api = build_api().map_err(|error| Error::Tokenizer(error.to_report_string()))?;
92 - let repo = api.model(model_id.to_string());
107 +async fn resolve_remote_model_files(
108 + model_id: &str,
109 + model_repo: Repo,
110 + cache: &Cache,
111 +) -> Result<ResolvedModelFiles> {
112 + let api = build_api(cache).map_err(|error| Error::Tokenizer(error.to_report_string()))?;
113 + let repo = api.repo(model_repo);
114 let info = repo.info().await.map_err(|error| {
115 Error::Tokenizer(format!(
116 "failed to fetch model '{model_id}': {}",
@@ -146,12 +167,20 @@ async fn resolve_remote_model_files(model_id: &str) -> Result<ResolvedModelFiles
167 })
168 }
169
149 -fn resolve_cached_model_files(model_id: &str) -> Result<Option<ResolvedModelFiles>> {
150 - let cache_repo = Cache::from_env().model(model_id.to_string());
170 +fn resolve_cached_model_files(cache: &Cache, repo: &Repo) -> Result<Option<ResolvedModelFiles>> {
171 + let cache_repo = cache.repo(repo.clone());
172 + // hf-hub 0.5 looks up refs even for commit SHAs; Python caches have no such ref.
173 + let snapshot_dir = (repo.revision().len() == 40
174 + && repo.revision().bytes().all(|byte| byte.is_ascii_hexdigit()))
175 + .then(|| cache.path().join(repo.folder_name()).join("snapshots").join(repo.revision()));
176 + let get_file = |name: &str| match &snapshot_dir {
177 + Some(dir) => local_file_if_exists(dir, name),
178 + None => cache_repo.get(name),
179 + };
180
152 - let tokenizer_config_path = cache_repo.get("tokenizer_config.json");
181 + let tokenizer_config_path = get_file("tokenizer_config.json");
182 let tokenizer_config = load_tokenizer_config(tokenizer_config_path.as_deref())?;
154 - let tokenizer = match resolve_cached_tokenizer_source(&cache_repo, &tokenizer_config)? {
183 + let tokenizer = match resolve_cached_tokenizer_source(&get_file, &tokenizer_config)? {
184 Some(tokenizer) => tokenizer,
185 None => return Ok(None),
186 };
@@ -159,12 +188,12 @@ fn resolve_cached_model_files(model_id: &str) -> Result<Option<ResolvedModelFile
188 let model_dir = tokenizer.path().parent().ok_or_else(|| {
189 Error::Tokenizer("resolved tokenizer file has no parent directory".to_string())
190 })?;
162 - let generation_config_path = cache_repo.get("generation_config.json");
163 - let preprocessor_config_path = cache_repo.get("preprocessor_config.json");
164 - let video_preprocessor_config_path = cache_repo.get("video_preprocessor_config.json");
165 - let processor_config_path = cache_repo.get("processor_config.json");
191 + let generation_config_path = get_file("generation_config.json");
192 + let preprocessor_config_path = get_file("preprocessor_config.json");
193 + let video_preprocessor_config_path = get_file("video_preprocessor_config.json");
194 + let processor_config_path = get_file("processor_config.json");
195 let chat_template_path = discover_chat_template_in_dir(model_dir);
167 - let config_path = cache_repo.get("config.json");
196 + let config_path = get_file("config.json");
197
198 Ok(Some(ResolvedModelFiles {
199 tokenizer,
@@ -207,20 +236,20 @@ async fn resolve_remote_tokenizer_source(
236 }
237
238 fn resolve_cached_tokenizer_source(
210 - cache_repo: &hf_hub::CacheRepo,
239 + get_file: &impl Fn(&str) -> Option<PathBuf>,
240 tokenizer_config: &HfTokenizerConfig,
241 ) -> Result<Option<TokenizerSource>> {
213 - let tekken_path = cache_repo.get("tekken.json");
242 + let tekken_path = get_file("tekken.json");
243
244 if let Some(tekken_path) = tekken_path {
245 return Ok(Some(TokenizerSource::Tekken(tekken_path)));
246 }
247
219 - let Some(tokenizer_path) = cache_repo.get("tokenizer.json").or_else(|| {
248 + let Some(tokenizer_path) = get_file("tokenizer.json").or_else(|| {
249 // tiktoken.model is the most common name, try it first.
221 - cache_repo.get("tiktoken.model").or_else(|| {
250 + get_file("tiktoken.model").or_else(|| {
251 // Scan for any *.tiktoken file in the cache snapshot directory.
223 - let snapshot_dir = cache_repo.get("config.json")?.parent()?.to_path_buf();
252 + let snapshot_dir = get_file("config.json")?.parent()?.to_path_buf();
253 discover_tiktoken_in_dir(&snapshot_dir)
254 })
255 }) else {
@@ -315,8 +344,18 @@ async fn download_known_file(repo: &ApiRepo, model_id: &str, filename: &str) ->
344 })
345 }
346
318 -fn build_api() -> anyhow::Result<Api> {
319 - let mut builder = ApiBuilder::from_env().with_progress(true);
347 +fn is_hf_hub_offline() -> bool {
348 + std::env::var("HF_HUB_OFFLINE").is_ok_and(|value| {
349 + matches!(
350 + value.to_ascii_uppercase().as_str(),
351 + "1" | "ON" | "YES" | "TRUE"
352 + )
353 + })
354 +}
355 +
356 +fn build_api(cache: &Cache) -> anyhow::Result<Api> {
357 + let mut builder =
358 + ApiBuilder::from_env().with_cache_dir(cache.path().clone()).with_progress(true);
359 if let Ok(token) = std::env::var(HF_TOKEN_ENV)
360 && !token.is_empty()
361 {
@@ -388,6 +427,7 @@ fn discover_chat_template_in_dir(dir: &std::path::Path) -> Option<PathBuf> {
427 #[cfg(test)]
428 mod tests {
429 use std::fs;
430 + use std::path::PathBuf;
431
432 use tempfile::tempdir;
433 use vllm_tokenizer::{TiktokenTokenizer, Tokenizer};
@@ -405,7 +445,7 @@ mod tests {
445 .expect("write tokenizer config");
446 fs::write(dir.path().join("config.json"), "{}").expect("write config");
447
408 - let files = ResolvedModelFiles::new(dir.path().to_str().expect("utf8 path"))
448 + let files = ResolvedModelFiles::new(dir.path().to_str().expect("utf8 path"), None)
449 .await
450 .expect("resolve local model files");
451
@@ -422,10 +462,90 @@ mod tests {
462 );
463 }
464
465 + #[test]
466 + fn cached_model_files_respect_branch_tag_and_commit_revisions() {
467 + let dir = tempdir().unwrap();
468 + let cache = hf_hub::Cache::new(dir.path().to_path_buf());
469 + let model = "test/model";
470 + let main_sha = "a".repeat(40);
471 + let pinned_sha = "b".repeat(40);
472 + for (revision, sha) in [("main", &main_sha), ("release/v1", &pinned_sha)] {
473 + let repo =
474 + hf_hub::Repo::with_revision(model.into(), hf_hub::RepoType::Model, revision.into());
475 + cache.repo(repo.clone()).create_ref(sha).unwrap();
476 + let snapshot = dir.path().join(repo.folder_name()).join("snapshots").join(sha);
477 + fs::create_dir_all(&snapshot).unwrap();
478 + fs::write(snapshot.join("tokenizer.json"), "{}").unwrap();
479 + fs::write(snapshot.join("config.json"), "{}").unwrap();
480 + }
481 + for (revision, expected_sha) in [
482 + ("main", Some(&main_sha)),
483 + ("release/v1", Some(&pinned_sha)),
484 + (pinned_sha.as_str(), Some(&pinned_sha)),
485 + ("missing", None),
486 + ] {
487 + let repo =
488 + hf_hub::Repo::with_revision(model.into(), hf_hub::RepoType::Model, revision.into());
489 + let files = super::resolve_cached_model_files(&cache, &repo).unwrap();
490 + match expected_sha {
491 + Some(sha) => {
492 + let snapshot = dir.path().join(repo.folder_name()).join("snapshots").join(sha);
493 + let files = files.unwrap();
494 + assert_eq!(files.tokenizer.path(), snapshot.join("tokenizer.json"));
495 + assert_eq!(files.config_path, Some(snapshot.join("config.json")));
496 + }
497 + None => assert!(files.is_none()),
498 + }
499 + }
500 + }
501 +
502 + #[tokio::test]
503 + async fn offline_resolution_uses_hub_cache_and_reports_cache_misses() {
504 + const CHILD: &str = "VLLM_TEST_HF_OFFLINE_CHILD";
505 + if std::env::var_os(CHILD).is_some() {
506 + let files = ResolvedModelFiles::new("test/model", Some("release")).await.unwrap();
507 + let cache = PathBuf::from(std::env::var_os("HF_HUB_CACHE").unwrap());
508 + assert_eq!(
509 + files.tokenizer.path(),
510 + cache.join("models--test--model/snapshots/commit/tokenizer.json")
511 + );
512 + let error = ResolvedModelFiles::new("test/model", Some("missing")).await.unwrap_err();
513 + let message = thiserror_ext::AsReport::as_report(&error).to_string();
514 + assert!(message.contains("HF_HUB_OFFLINE is enabled"), "{message}");
515 + assert!(message.contains("revision 'missing'"), "{message}");
516 + return;
517 + }
518 +
519 + // Isolate environment variables from other tests and Tokio worker threads.
520 + let dir = tempdir().unwrap();
521 + let cache = dir.path().join("hub-cache");
522 + let repo = cache.join("models--test--model");
523 + fs::create_dir_all(repo.join("refs")).unwrap();
524 + fs::create_dir_all(repo.join("snapshots/commit")).unwrap();
525 + fs::write(repo.join("refs/release"), "commit").unwrap();
526 + fs::write(repo.join("snapshots/commit/tokenizer.json"), "{}").unwrap();
527 + for value in ["1", "ON", "yes", "True"] {
528 + let output = std::process::Command::new(std::env::current_exe().unwrap())
529 + .args(["--exact", "backend::hf::model_files::tests::offline_resolution_uses_hub_cache_and_reports_cache_misses", "--nocapture"])
530 + .env(CHILD, "1")
531 + .env("HF_HOME", dir.path().join("hf-home"))
532 + .env("HF_HUB_CACHE", &cache)
533 + .env("HF_HUB_OFFLINE", value)
534 + .env("HF_ENDPOINT", "http://127.0.0.1:1")
535 + .output().unwrap();
536 + assert!(
537 + output.status.success(),
538 + "{}\n{}",
539 + String::from_utf8_lossy(&output.stdout),
540 + String::from_utf8_lossy(&output.stderr)
541 + );
542 + }
543 + }
544 +
545 #[tokio::test]
546 #[ignore = "too slow for CI and requires network access to Hugging Face"]
547 async fn tiktoken_real_kimi_k25_tokenizer_files_load_and_handle_special_tokens() {
428 - let files = ResolvedModelFiles::new("moonshotai/Kimi-K2.5")
548 + let files = ResolvedModelFiles::new("moonshotai/Kimi-K2.5", None)
549 .await
550 .expect("resolve real Kimi K2.5 model files");
551
rust/src/text/src/lower.rs
+2 -1
@@ -802,7 +802,8 @@ mod tests {
802 #[file_serial(hf_qwen3)]
803 async fn lower_text_request_uses_real_qwen_generation_defaults() {
804 let model_id = "Qwen/Qwen3-0.6B";
805 - let files = ResolvedModelFiles::new(model_id).await.expect("resolve qwen model files");
805 + let files =
806 + ResolvedModelFiles::new(model_id, None).await.expect("resolve qwen model files");
807 let backend = HfTextBackend::from_resolved_model_files(
808 files,
809 model_id.to_string(),