[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(),