[Rust Frontend] Support `--generation-config vllm` (#53044)
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Bugen Zhao committed
Aug 21, 2026 at 10:31 UTC
f32b17b6d616605eb5ed498777f150242bd1bc30
14 files changed
+244
-41
rust/src/chat/src/backend/hf.rs
+6
-2
@@ -129,8 +129,11 @@ pub(super) async fn load_model_backends(
129
options: LoadModelBackendsOptions,
130
) -> Result<LoadedModelBackends> {
131
let files = ResolvedModelFiles::new(model_id).await?;
132
- let text_backend =
133
- HfTextBackend::from_resolved_model_files(files.clone(), model_id.to_string())?;
132
+ let text_backend = HfTextBackend::from_resolved_model_files(
133
+ files.clone(),
134
+ model_id.to_string(),
135
+ options.generation_config,
136
+ )?;
137
let tokenizer = text_backend.tokenizer();
138
let text_backend: DynTextBackend = Arc::new(text_backend);
139
@@ -227,6 +230,7 @@ mod tests {
230
resolved_files(config_json, tokenizer_config_json),
231
"test-model".to_string(),
232
LoadModelBackendsOptions {
233
+ generation_config: Default::default(),
234
renderer,
235
language_model_only: false,
236
chat_template_content_format: Default::default(),
rust/src/chat/src/backend/mod.rs
+3
-1
@@ -5,7 +5,7 @@ use std::collections::HashMap;
5
use std::sync::Arc;
6
7
use serde_json::Value;
8
-use vllm_text::{DynTextBackend, TextBackend};
8
+use vllm_text::{DynTextBackend, GenerationConfigMode, TextBackend};
9
10
use crate::error::Result;
11
use crate::multimodal::{MmLimitPerPrompt, MultimodalModelInfo};
@@ -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
+ /// Which generation-config sampling defaults to inherit.
65
+ pub generation_config: GenerationConfigMode,
66
/// Which chat renderer implementation to use.
67
pub renderer: RendererSelection,
68
/// Disable frontend-side multimodal preprocessing and render the model as
rust/src/chat/src/lib.rs
+1
@@ -44,6 +44,7 @@ pub use request::{
44
pub use stream::{ChatEventStream, ChatEventStreamTrait, CollectedAssistantMessage};
45
pub use vllm_engine_core_client::protocol::multimodal::MmFeatures;
46
pub use vllm_llm::FinishReason;
47
+pub use vllm_text::GenerationConfigMode;
48
49
mod backend;
50
mod error;
rust/src/cmd/src/cli.rs
+9
-1
@@ -22,8 +22,8 @@ use serde_json::Value;
22
use serde_with::{DefaultOnNull, OneOrMany, serde_as};
23
use thiserror_ext::AsReport as _;
24
use uuid::Uuid;
25
-use vllm_chat::ReasoningParserFactory;
25
use vllm_chat::multimodal::MmLimitPerPrompt;
26
+use vllm_chat::{GenerationConfigMode, ReasoningParserFactory};
27
use vllm_engine_core_client::TransportMode;
28
use vllm_managed_engine::ManagedEngineConfig;
29
use vllm_managed_engine::cli::{ManagedEngineArgs, repartition_managed_engine_args};
@@ -186,6 +186,12 @@ pub struct SharedRuntimeArgs {
186
/// public model ID.
187
pub model: String,
188
189
+ /// The source of generation-config sampling defaults. `"auto"` loads the
190
+ /// model's defaults, while `"vllm"` uses vLLM's neutral defaults.
191
+ #[arg(long, default_value_t)]
192
+ #[serde(default)]
193
+ pub generation_config: GenerationConfigMode,
194
+
195
/// Maximum time to wait for the expected engines to register on the
196
/// frontend transport.
197
#[arg(
@@ -482,6 +488,7 @@ impl SharedRuntimeArgs {
488
None => CoordinatorMode::None,
489
},
490
model: self.model,
491
+ generation_config: self.generation_config,
492
served_model_name: self.served_model_name,
493
listener_mode: HttpListenerMode::InheritedFd { fd: listen_fd },
494
tool_call_parser: self.tool_call_parser,
@@ -535,6 +542,7 @@ impl SharedRuntimeArgs {
542
},
543
coordinator_mode: CoordinatorMode::MaybeInProc,
544
model: self.model,
545
+ generation_config: self.generation_config,
546
served_model_name: self.served_model_name,
547
listener_mode,
548
tool_call_parser: self.tool_call_parser,
rust/src/cmd/src/cli/tests.rs
+30
-2
@@ -3,7 +3,9 @@
3
4
use expect_test::expect;
5
use vllm_engine_core_client::TransportMode;
6
-use vllm_server::{Config, HttpListenerMode, ParserSelection, RendererSelection};
6
+use vllm_server::{
7
+ Config, GenerationConfigMode, HttpListenerMode, ParserSelection, RendererSelection,
8
+};
9
10
use super::{BenchCommand, Cli, Command};
11
@@ -88,6 +90,7 @@ fn serve_args_forward_python_flags_with_separator() {
90
uds: None,
91
runtime: SharedRuntimeArgs {
92
model: "Qwen/Qwen3-0.6B",
93
+ generation_config: Auto,
94
engine_ready_timeout_secs: 600,
95
tool_call_parser: Auto,
96
reasoning_parser: Auto,
@@ -732,6 +735,24 @@ fn serve_args_reject_unsupported_flag_arg() {
735
"#]].assert_eq(&error.to_string());
736
}
737
738
+#[test]
739
+fn serve_args_reject_custom_generation_config_source() {
740
+ let error = Cli::try_parse_from([
741
+ "vllm-rs",
742
+ "serve",
743
+ "Qwen/Qwen3-0.6B",
744
+ "--generation-config",
745
+ "/tmp/custom-config",
746
+ ])
747
+ .unwrap_err();
748
+
749
+ expect![[r#"
750
+ error: invalid value '/tmp/custom-config' for '--generation-config <GENERATION_CONFIG>': generation config source `/tmp/custom-config` is not implemented yet (expected one of: auto, vllm)
751
+
752
+ For more information, try '--help'.
753
+ "#]].assert_eq(&error.to_string());
754
+}
755
+
756
#[test]
757
fn serve_args_reject_unsupported_no_flag_alias() {
758
let error = Cli::try_parse_from([
@@ -788,6 +809,7 @@ fn frontend_args_accept_json() {
809
data_parallel_size: None,
810
runtime: SharedRuntimeArgs {
811
model: "Qwen/Qwen3-0.6B",
812
+ generation_config: Auto,
813
engine_ready_timeout_secs: 600,
814
tool_call_parser: None,
815
reasoning_parser: None,
@@ -858,6 +880,7 @@ fn frontend_args_json_applies_defaults() {
880
panic!("expected frontend args");
881
};
882
assert_eq!(args.runtime.model, "Qwen/Qwen3-0.6B");
883
+ assert_eq!(args.runtime.generation_config, GenerationConfigMode::Auto);
884
assert_eq!(args.runtime.engine_ready_timeout_secs, 600);
885
assert_eq!(args.runtime.tool_call_parser, ParserSelection::None);
886
assert_eq!(args.runtime.reasoning_parser, ParserSelection::None);
@@ -901,7 +924,7 @@ fn frontend_args_json_accepts_supported_non_default_fields() {
924
"--output-address",
925
"ipc:///tmp/output.sock",
926
"--args-json",
904
- r#"{"model_tag":"Qwen/Qwen3-0.6B","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}"#,
927
+ 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}"#,
928
])
929
.unwrap();
930
@@ -909,6 +932,7 @@ fn frontend_args_json_accepts_supported_non_default_fields() {
932
panic!("expected frontend args");
933
};
934
assert_eq!(args.runtime.engine_ready_timeout_secs, 42);
935
+ assert_eq!(args.runtime.generation_config, GenerationConfigMode::Vllm);
936
assert_eq!(
937
args.runtime.tool_call_parser,
938
ParserSelection::Explicit("hermes".to_string())
@@ -1379,6 +1403,7 @@ fn serve_args_accept_handshake_aliases() {
1403
uds: None,
1404
runtime: SharedRuntimeArgs {
1405
model: "Qwen/Qwen3-0.6B",
1406
+ generation_config: Auto,
1407
engine_ready_timeout_secs: 600,
1408
tool_call_parser: Auto,
1409
reasoning_parser: Auto,
@@ -1523,6 +1548,7 @@ fn serve_frontend_config_uses_dp_address_as_advertised_host() {
1548
},
1549
coordinator_mode: MaybeInProc,
1550
model: "Qwen/Qwen3-0.6B",
1551
+ generation_config: Auto,
1552
served_model_name: [],
1553
listener_mode: BindTcp {
1554
host: "127.0.0.1",
@@ -1608,6 +1634,7 @@ fn serve_frontend_config_keeps_tcp_transport_for_non_local_only_topology() {
1634
},
1635
coordinator_mode: MaybeInProc,
1636
model: "Qwen/Qwen3-0.6B",
1637
+ generation_config: Auto,
1638
served_model_name: [],
1639
listener_mode: BindTcp {
1640
host: "127.0.0.1",
@@ -1715,6 +1742,7 @@ fn frontend_config_uses_external_coordinator_when_coordinator_address_is_present
1742
address: "tcp://127.0.0.1:7000",
1743
},
1744
model: "Qwen/Qwen3-0.6B",
1745
+ generation_config: Auto,
1746
served_model_name: [],
1747
listener_mode: InheritedFd {
1748
fd: 3,
rust/src/cmd/src/cli/unsupported.rs
+5
-8
@@ -240,15 +240,12 @@ pub struct EngineUnsupportedArgs {
240
#[arg(long)]
241
pub hf_overrides: Option<Unsupported>,
242
243
- /// The folder path to the generation config. Defaults to `"auto"`, the
244
- /// generation config will be loaded from model path. If set to `"vllm"`, no
245
- /// generation config is loaded, vLLM defaults will be used. If set to a
246
- /// folder path, the generation config will be loaded from the specified
247
- /// folder path. If `max_new_tokens` is specified in generation config,
248
- /// then it sets a server-wide limit on the number of output tokens for
249
- /// all requests.
243
+ /// Overrides or sets generation config. e.g. `{"temperature": 0.5}`. If
244
+ /// used with `--generation-config auto`, the override parameters will be
245
+ /// merged with the default config from the model. If used with
246
+ /// `--generation-config vllm`, only the override parameters are used.
247
#[arg(long)]
251
- pub generation_config: Option<Unsupported>,
248
+ pub override_generation_config: Option<Unsupported>,
249
250
/// IOProcessor plugin name to load at model startup
251
#[arg(long)]
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
+ generation_config: Default::default(),
64
served_model_name: vec![],
65
listener_mode: HttpListenerMode::BindTcp {
66
host: "127.0.0.1".to_string(),
rust/src/server/src/config.rs
+5
-1
@@ -11,7 +11,9 @@ use educe::Educe;
11
use serde::Serialize;
12
use serde_json::Value;
13
use vllm_chat::multimodal::MmLimitPerPrompt;
14
-use vllm_chat::{ChatTemplateContentFormatOption, ParserSelection, RendererSelection};
14
+use vllm_chat::{
15
+ ChatTemplateContentFormatOption, GenerationConfigMode, ParserSelection, RendererSelection,
16
+};
17
use vllm_engine_core_client::{CoordinatorMode as EngineCoreCoordinatorMode, TransportMode};
18
19
/// Default keep-alive idle timeout (seconds); also the head-read bound
@@ -165,6 +167,8 @@ pub struct Config {
167
pub coordinator_mode: CoordinatorMode,
168
/// Backend model identifier used for engine-core loading.
169
pub model: String,
170
+ /// Which generation-config sampling defaults to inherit.
171
+ pub generation_config: GenerationConfigMode,
172
/// Model name(s) exposed to clients via the OpenAI API. When non-empty,
173
/// the first entry is used as the primary ID in responses and all entries
174
/// are accepted in requests. When empty, falls back to `model`.
rust/src/server/src/lib.rs
+4
-1
@@ -45,7 +45,9 @@ use tonic_health::server::health_reporter;
45
use tower::ServiceExt as _;
46
use tracing::{info, trace, warn};
47
use vllm_chat::{ChatLlm, LoadModelBackendsOptions, load_model_backends};
48
-pub use vllm_chat::{ChatTemplateContentFormatOption, ParserSelection, RendererSelection};
48
+pub use vllm_chat::{
49
+ ChatTemplateContentFormatOption, GenerationConfigMode, ParserSelection, RendererSelection,
50
+};
51
use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig};
52
use vllm_llm::Llm;
53
use vllm_text::TextLlm;
@@ -96,6 +98,7 @@ async fn build_state(config: &Config) -> Result<Arc<AppState>> {
98
let loaded = load_model_backends(
99
&config.model,
100
LoadModelBackendsOptions {
101
+ generation_config: config.generation_config,
102
renderer: config.renderer,
103
language_model_only: config.language_model_only,
104
chat_template: config.chat_template.clone(),
rust/src/server/src/render.rs
+1
@@ -56,6 +56,7 @@ async fn build_state(config: &RenderConfig) -> Result<Arc<RenderState>> {
56
let loaded = load_model_backends(
57
&config.model,
58
LoadModelBackendsOptions {
59
+ generation_config: Default::default(),
60
renderer: config.renderer,
61
language_model_only: true,
62
chat_template: config.chat_template.clone(),
rust/src/text/src/backend/hf/mod.rs
+122
-19
@@ -18,7 +18,7 @@ pub use self::config::{
18
load_tokenizer_config,
19
};
20
pub use self::model_files::{ResolvedModelFiles, TokenizerSource};
21
-use crate::backend::{SamplingHints, TextBackend};
21
+use crate::backend::{GenerationConfigMode, SamplingHints, TextBackend};
22
use crate::error::Result;
23
24
fn load_tokenizer(tokenizer: &TokenizerSource) -> Result<DynTokenizer> {
@@ -41,6 +41,7 @@ pub struct HfTextBackend {
41
/// Generation-config for sampling defaults that may be inherited when the
42
/// user does not explicitly override them.
43
generation_config: GenerationConfig,
44
+ generation_config_mode: GenerationConfigMode,
45
/// Model vocabulary size from the selected text config.
46
model_vocab_size: usize,
47
/// Model config (`config.json`).
@@ -48,14 +49,12 @@ pub struct HfTextBackend {
49
}
50
51
impl HfTextBackend {
51
- /// Load the text backend with the given model id.
52
- pub async fn from_model(model_id: &str) -> Result<Self> {
53
- let files = ResolvedModelFiles::new(model_id).await?;
54
- Self::from_resolved_model_files(files, model_id.to_string())
55
- }
56
-
52
/// Load the text backend from resolved Hugging Face model files.
58
- pub fn from_resolved_model_files(files: ResolvedModelFiles, model_id: String) -> Result<Self> {
53
+ pub fn from_resolved_model_files(
54
+ files: ResolvedModelFiles,
55
+ model_id: String,
56
+ generation_config_mode: GenerationConfigMode,
57
+ ) -> Result<Self> {
58
let tokenizer_config = load_tokenizer_config(files.tokenizer_config_path.as_deref())?;
59
let tokenizer = load_tokenizer(&files.tokenizer)?;
60
let model_config = load_model_config(files.config_path.as_deref())?;
@@ -80,6 +79,7 @@ impl HfTextBackend {
79
primary_eos_token_id,
80
extra_eos_token_ids,
81
generation_config,
82
+ generation_config_mode,
83
model_vocab_size,
84
model_config,
85
})
@@ -146,16 +146,38 @@ impl TextBackend for HfTextBackend {
146
}
147
148
fn sampling_hints(&self) -> Result<SamplingHints> {
149
- Ok(SamplingHints {
150
- primary_eos_token_id: self.primary_eos_token_id,
151
- extra_eos_token_ids: self.extra_eos_token_ids.clone(),
152
- default_temperature: self.generation_config.temperature,
153
- default_top_p: self.generation_config.top_p,
154
- default_top_k: self.generation_config.top_k,
155
- default_min_p: self.generation_config.min_p,
156
- default_repetition_penalty: self.generation_config.repetition_penalty,
157
- default_max_tokens: self.generation_config.max_new_tokens,
158
- })
149
+ Ok(build_sampling_hints(
150
+ self.generation_config_mode,
151
+ &self.generation_config,
152
+ self.primary_eos_token_id,
153
+ self.extra_eos_token_ids.clone(),
154
+ ))
155
+ }
156
+}
157
+
158
+fn build_sampling_hints(
159
+ mode: GenerationConfigMode,
160
+ generation_config: &GenerationConfig,
161
+ primary_eos_token_id: Option<u32>,
162
+ extra_eos_token_ids: BTreeSet<u32>,
163
+) -> SamplingHints {
164
+ let sampling_config = match mode {
165
+ // Auto inherits sampling defaults from the model's generation config.
166
+ GenerationConfigMode::Auto => Some(generation_config),
167
+ // Vllm skips model-provided sampling defaults. `lower_sampling_params`
168
+ // applies vLLM fallback values; separately resolved EOS tokens remain.
169
+ GenerationConfigMode::Vllm => None,
170
+ };
171
+
172
+ SamplingHints {
173
+ primary_eos_token_id,
174
+ extra_eos_token_ids,
175
+ default_temperature: sampling_config.and_then(|config| config.temperature),
176
+ default_top_p: sampling_config.and_then(|config| config.top_p),
177
+ default_top_k: sampling_config.and_then(|config| config.top_k),
178
+ default_min_p: sampling_config.and_then(|config| config.min_p),
179
+ default_repetition_penalty: sampling_config.and_then(|config| config.repetition_penalty),
180
+ default_max_tokens: sampling_config.and_then(|config| config.max_new_tokens),
181
}
182
}
183
@@ -163,7 +185,10 @@ impl TextBackend for HfTextBackend {
185
mod tests {
186
use std::collections::BTreeSet;
187
166
- use super::{GenerationConfig, HfTokenizerConfig, ModelConfig, resolve_eos_token_ids};
188
+ use super::{
189
+ GenerationConfig, GenerationConfigMode, HfTokenizerConfig, ModelConfig,
190
+ build_sampling_hints, resolve_eos_token_ids,
191
+ };
192
use vllm_tokenizer::Tokenizer;
193
194
struct FakeTokenizer;
@@ -235,4 +260,82 @@ mod tests {
260
assert_eq!(primary, Some(2));
261
assert_eq!(extra, BTreeSet::from([200006, 200010]));
262
}
263
+
264
+ #[test]
265
+ fn vllm_generation_config_keeps_eos_and_uses_neutral_sampling_defaults() {
266
+ let generation_config: GenerationConfig = serde_json::from_str(
267
+ r#"{
268
+ "eos_token_id": [2, 3],
269
+ "temperature": 0.6,
270
+ "top_p": 0.95,
271
+ "top_k": 20,
272
+ "min_p": 0.1,
273
+ "repetition_penalty": 1.1,
274
+ "max_new_tokens": 4096
275
+ }"#,
276
+ )
277
+ .unwrap();
278
+ let extra_eos_token_ids = BTreeSet::from([3]);
279
+
280
+ let hints = (
281
+ build_sampling_hints(
282
+ GenerationConfigMode::Auto,
283
+ &generation_config,
284
+ Some(2),
285
+ extra_eos_token_ids.clone(),
286
+ ),
287
+ build_sampling_hints(
288
+ GenerationConfigMode::Vllm,
289
+ &generation_config,
290
+ Some(2),
291
+ extra_eos_token_ids,
292
+ ),
293
+ );
294
+
295
+ expect_test::expect![[r#"
296
+ (
297
+ SamplingHints {
298
+ primary_eos_token_id: Some(
299
+ 2,
300
+ ),
301
+ extra_eos_token_ids: {
302
+ 3,
303
+ },
304
+ default_temperature: Some(
305
+ 0.6,
306
+ ),
307
+ default_top_p: Some(
308
+ 0.95,
309
+ ),
310
+ default_top_k: Some(
311
+ 20,
312
+ ),
313
+ default_min_p: Some(
314
+ 0.1,
315
+ ),
316
+ default_repetition_penalty: Some(
317
+ 1.1,
318
+ ),
319
+ default_max_tokens: Some(
320
+ 4096,
321
+ ),
322
+ },
323
+ SamplingHints {
324
+ primary_eos_token_id: Some(
325
+ 2,
326
+ ),
327
+ extra_eos_token_ids: {
328
+ 3,
329
+ },
330
+ default_temperature: None,
331
+ default_top_p: None,
332
+ default_top_k: None,
333
+ default_min_p: None,
334
+ default_repetition_penalty: None,
335
+ default_max_tokens: None,
336
+ },
337
+ )
338
+ "#]]
339
+ .assert_debug_eq(&hints);
340
+ }
341
}
rust/src/text/src/backend/mod.rs
+44
@@ -3,12 +3,56 @@
3
4
pub mod hf;
5
6
+use std::fmt;
7
+use std::str::FromStr;
8
use std::sync::Arc;
9
10
+use serde_with::{DeserializeFromStr, SerializeDisplay};
11
use vllm_tokenizer::DynTokenizer;
12
13
use crate::error::Result;
14
15
+/// Select which sampling defaults to inherit from generation config.
16
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, DeserializeFromStr, SerializeDisplay)]
17
+pub enum GenerationConfigMode {
18
+ /// Inherit sampling defaults from the model's generation config.
19
+ #[default]
20
+ Auto,
21
+ /// Use vLLM's neutral sampling defaults.
22
+ Vllm,
23
+}
24
+
25
+impl GenerationConfigMode {
26
+ pub const AUTO_LITERAL: &str = "auto";
27
+ pub const VLLM_LITERAL: &str = "vllm";
28
+}
29
+
30
+impl FromStr for GenerationConfigMode {
31
+ type Err = String;
32
+
33
+ fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
34
+ if value.eq_ignore_ascii_case(Self::AUTO_LITERAL) {
35
+ Ok(Self::Auto)
36
+ } else if value.eq_ignore_ascii_case(Self::VLLM_LITERAL) {
37
+ Ok(Self::Vllm)
38
+ } else {
39
+ Err(format!(
40
+ "generation config source `{value}` is not implemented yet \
41
+ (expected one of: auto, vllm)"
42
+ ))
43
+ }
44
+ }
45
+}
46
+
47
+impl fmt::Display for GenerationConfigMode {
48
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49
+ match self {
50
+ Self::Auto => f.write_str(Self::AUTO_LITERAL),
51
+ Self::Vllm => f.write_str(Self::VLLM_LITERAL),
52
+ }
53
+ }
54
+}
55
+
56
/// Tokenizer/model-derived defaults used to enrich text-generation requests
57
/// before they are lowered into engine-core.
58
#[derive(Debug, Clone, Default, PartialEq)]
rust/src/text/src/lib.rs
+3
-1
@@ -9,7 +9,9 @@
9
10
use std::mem::take;
11
12
-pub use backend::{DynTextBackend, SamplingHints, SamplingLimits, TextBackend};
12
+pub use backend::{
13
+ DynTextBackend, GenerationConfigMode, SamplingHints, SamplingLimits, TextBackend,
14
+};
15
pub use error::{Error, LogprobsError, Result, SamplingParamsError, TokenIdsError};
16
use futures::Stream;
17
pub use lower::{
rust/src/text/src/lower.rs
+10
-5
@@ -322,8 +322,8 @@ mod tests {
322
use vllm_tokenizer::test_utils::TestTokenizer;
323
324
use super::*;
325
- use crate::backend::hf::HfTextBackend;
326
- use crate::backend::{SamplingHints, TextBackend as _};
325
+ use crate::backend::hf::{HfTextBackend, ResolvedModelFiles};
326
+ use crate::backend::{GenerationConfigMode, SamplingHints, TextBackend as _};
327
use crate::error::{LogprobsError, SamplingParamsError, TokenIdsError};
328
use crate::request::{Prompt, TextRequest};
329
@@ -785,9 +785,14 @@ mod tests {
785
#[tokio::test]
786
#[file_serial(hf_qwen3)]
787
async fn lower_text_request_uses_real_qwen_generation_defaults() {
788
- let backend = HfTextBackend::from_model("Qwen/Qwen3-0.6B")
789
- .await
790
- .expect("load qwen tokenizer and generation config");
788
+ let model_id = "Qwen/Qwen3-0.6B";
789
+ let files = ResolvedModelFiles::new(model_id).await.expect("resolve qwen model files");
790
+ let backend = HfTextBackend::from_resolved_model_files(
791
+ files,
792
+ model_id.to_string(),
793
+ GenerationConfigMode::Auto,
794
+ )
795
+ .expect("load qwen tokenizer and generation config");
796
let hints = backend.sampling_hints().expect("collect sampling hints");
797
798
expect_test::expect![[r#"