forked from Karylab-cklius/vllm
c01618fdc8
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Signed-off-by: Bugen Zhao <i@bugenzhao.com>
1742 lines
49 KiB
Rust
1742 lines
49 KiB
Rust
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
use expect_test::expect;
|
|
use vllm_engine_core_client::TransportMode;
|
|
use vllm_server::{Config, HttpListenerMode, ParserSelection, RendererSelection};
|
|
|
|
use super::{BenchCommand, Cli, Command};
|
|
|
|
#[test]
|
|
fn bench_serve_args_parse_without_managed_engine_repartition() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"bench",
|
|
"serve",
|
|
"--backend",
|
|
"openai-chat",
|
|
"--request-rate",
|
|
"inf",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Bench(BenchCommand::Serve(args)) = cli.command else {
|
|
panic!("expected bench serve args");
|
|
};
|
|
assert_eq!(args.backend, vllm_bench::BackendKind::OpenaiChat);
|
|
assert!(args.request_rate.is_infinite());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_forward_python_flags_with_separator() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--python",
|
|
"../vllm/.venv/bin/python",
|
|
"--max-model-len",
|
|
"512",
|
|
"--",
|
|
"--dtype",
|
|
"float16",
|
|
])
|
|
.unwrap();
|
|
|
|
expect![[r#"
|
|
Cli {
|
|
command: Serve(
|
|
ServeArgs {
|
|
headless: false,
|
|
host: "127.0.0.1",
|
|
port: 8000,
|
|
uds: None,
|
|
runtime: SharedRuntimeArgs {
|
|
model: "Qwen/Qwen3-0.6B",
|
|
engine_ready_timeout_secs: 600,
|
|
tool_call_parser: Auto,
|
|
reasoning_parser: Auto,
|
|
renderer: Auto,
|
|
language_model_only: false,
|
|
max_model_len: Some(
|
|
512,
|
|
),
|
|
max_logprobs: None,
|
|
grpc_port: None,
|
|
shutdown_timeout: 0,
|
|
http_timeout_keep_alive: None,
|
|
chat_template: None,
|
|
default_chat_template_kwargs: None,
|
|
chat_template_content_format: Auto,
|
|
enable_log_requests: false,
|
|
enable_prompt_tokens_details: false,
|
|
enable_request_id_headers: false,
|
|
disable_log_stats: false,
|
|
served_model_name: [],
|
|
allowed_origins: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allowed_methods: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allowed_headers: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allow_credentials: false,
|
|
ssl_keyfile: None,
|
|
ssl_certfile: None,
|
|
ssl_ca_certs: None,
|
|
ssl_cert_reqs: 0,
|
|
ssl_ciphers: None,
|
|
profiler_config: None,
|
|
},
|
|
managed_engine: ManagedEngineArgs {
|
|
python: "../vllm/.venv/bin/python",
|
|
handshake_host: "127.0.0.1",
|
|
handshake_port: None,
|
|
data_parallel_size: 1,
|
|
data_parallel_size_local: None,
|
|
python_args: [
|
|
"--dtype",
|
|
"float16",
|
|
],
|
|
},
|
|
},
|
|
),
|
|
}
|
|
"#]]
|
|
.assert_debug_eq(&cli);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_auto_forward_python_flags_without_separator() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--python",
|
|
"python3",
|
|
"--quantization",
|
|
"awq",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"--quantization",
|
|
"awq",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_auto_forward_enable_lora_to_python() {
|
|
let cli =
|
|
Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--enable-lora"]).unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"--enable-lora",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_forward_shutdown_timeout_to_managed_engine() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--shutdown-timeout",
|
|
"60",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(args.runtime.shutdown_timeout, 60);
|
|
|
|
let config = args.to_managed_engine_config(5555);
|
|
expect![[r#"
|
|
[
|
|
"--reasoning-parser",
|
|
"qwen3",
|
|
"--shutdown-timeout",
|
|
"60",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&config.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_forward_disable_log_stats_to_managed_engine() {
|
|
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--disable-log-stats"])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert!(args.runtime.disable_log_stats);
|
|
|
|
let config = args.to_managed_engine_config(5555);
|
|
expect![[r#"
|
|
[
|
|
"--reasoning-parser",
|
|
"qwen3",
|
|
"--disable-log-stats",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&config.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_forward_profiler_config_to_managed_engine() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--profiler-config",
|
|
r#"{"profiler":"torch","torch_profiler_dir":"/tmp/profile"}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(args.runtime.profiler().as_deref(), Some("torch"));
|
|
|
|
let config = args.to_managed_engine_config(5555);
|
|
let profiler_flag_index = config
|
|
.python_args
|
|
.iter()
|
|
.position(|arg| arg == "--profiler-config")
|
|
.expect("profiler config flag");
|
|
let profiler_config: serde_json::Value =
|
|
serde_json::from_str(&config.python_args[profiler_flag_index + 1])
|
|
.expect("profiler config json");
|
|
assert_eq!(
|
|
profiler_config,
|
|
serde_json::json!({
|
|
"profiler": "torch",
|
|
"torch_profiler_dir": "/tmp/profile",
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_forward_max_logprobs_to_frontend_and_managed_engine() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--max-logprobs",
|
|
"-1",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(args.runtime.max_logprobs, Some(-1));
|
|
|
|
let frontend_config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
assert_eq!(frontend_config.max_logprobs, Some(-1));
|
|
|
|
let engine_config = args.to_managed_engine_config(5555);
|
|
expect![[r#"
|
|
[
|
|
"--max-logprobs",
|
|
"-1",
|
|
"--reasoning-parser",
|
|
"qwen3",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&engine_config.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_resolve_auto_reasoning_parser_for_managed_engine() {
|
|
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B"]).unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(args.runtime.reasoning_parser, ParserSelection::Auto);
|
|
|
|
let config = args.to_managed_engine_config(5555);
|
|
expect![[r#"
|
|
[
|
|
"--reasoning-parser",
|
|
"qwen3",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&config.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_forward_explicit_reasoning_parser_to_managed_engine() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Unknown/Model",
|
|
"--reasoning-parser",
|
|
"deepseek_r1",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
|
|
let config = args.to_managed_engine_config(5555);
|
|
expect![[r#"
|
|
[
|
|
"--reasoning-parser",
|
|
"deepseek_r1",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&config.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_do_not_forward_disabled_reasoning_parser_to_managed_engine() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--reasoning-parser",
|
|
"none",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
|
|
let config = args.to_managed_engine_config(5555);
|
|
assert!(config.python_args.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_forward_reasoning_parser_even_with_passthrough_reasoning_parser() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--",
|
|
"--reasoning-parser",
|
|
"deepseek_r1",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
|
|
let config = args.to_managed_engine_config(5555);
|
|
expect![[r#"
|
|
[
|
|
"--reasoning-parser",
|
|
"deepseek_r1",
|
|
"--reasoning-parser",
|
|
"qwen3",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&config.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_auto_forward_python_multi_char_alias_without_separator() {
|
|
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "-tp", "2"]).unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"--tensor-parallel-size",
|
|
"2",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_accept_explicit_deepseek_v32_renderer() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--tokenizer-mode",
|
|
"deepseek_v32",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(args.runtime.renderer, RendererSelection::DeepSeekV32);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_passes_enable_request_id_headers_into_config() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--enable-request-id-headers",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
assert!(config.api_server_options.enable_request_id_headers);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_passes_enable_prompt_tokens_details_into_config() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--enable-prompt-tokens-details",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
assert!(config.api_server_options.enable_prompt_tokens_details);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_passes_tls_into_config() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--ssl-certfile",
|
|
"/tmp/cert.pem",
|
|
"--ssl-keyfile",
|
|
"/tmp/key.pem",
|
|
"--ssl-ca-certs",
|
|
"/tmp/ca.pem",
|
|
"--ssl-cert-reqs",
|
|
"2",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
let tls = config.tls.expect("tls configured");
|
|
assert_eq!(tls.cert_file.as_deref(), Some("/tmp/cert.pem"));
|
|
assert_eq!(tls.key_file.as_deref(), Some("/tmp/key.pem"));
|
|
assert_eq!(tls.ca_certs.as_deref(), Some("/tmp/ca.pem"));
|
|
assert_eq!(tls.cert_reqs, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_without_ssl_flags_has_no_tls() {
|
|
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B"]).unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
assert!(config.tls.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_ssl_keyfile_without_certfile_fails_validation() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--ssl-keyfile",
|
|
"/tmp/key.pem",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
// TLS is requested (a key was given) but there is no certificate, so
|
|
// validation fails loud rather than silently serving plaintext.
|
|
assert_eq!(config.tls.as_ref().expect("tls requested").cert_file, None);
|
|
let err = config.validate().unwrap_err().to_string();
|
|
assert!(err.contains("--ssl-certfile is required"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn serve_mtls_without_ca_certs_fails_validation() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--ssl-certfile",
|
|
"/tmp/cert.pem",
|
|
"--ssl-cert-reqs",
|
|
"2",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
// Client-cert verification without a CA bundle has nothing to verify
|
|
// against, so it fails loud at startup.
|
|
let err = config.validate().unwrap_err().to_string();
|
|
assert!(err.contains("--ssl-ca-certs is required"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_passes_tls_into_config() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","ssl_certfile":"/tmp/cert.pem","ssl_keyfile":"/tmp/key.pem"}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
let config = args.into_config();
|
|
let tls = config.tls.expect("tls configured");
|
|
assert_eq!(tls.cert_file.as_deref(), Some("/tmp/cert.pem"));
|
|
assert_eq!(tls.key_file.as_deref(), Some("/tmp/key.pem"));
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_rejects_out_of_range_cert_reqs() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","ssl_certfile":"/tmp/cert.pem","ssl_cert_reqs":5}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
// The JSON path bypasses clap's range check, so validate() is the only guard.
|
|
let config = args.into_config();
|
|
let err = config.validate().unwrap_err().to_string();
|
|
assert!(err.contains("--ssl-cert-reqs"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_passes_enable_request_id_headers_into_config() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","enable_request_id_headers":true}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
let config = args.into_config();
|
|
assert!(config.api_server_options.enable_request_id_headers);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_passes_api_keys_into_config() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--api-key",
|
|
"secret-a",
|
|
"--api-key",
|
|
"secret-b",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
|
|
assert_eq!(config.api_keys, vec!["secret-a", "secret-b"]);
|
|
let debug = format!("{config:#?}");
|
|
assert!(debug.contains("api_keys: [<redacted>; 2]"));
|
|
assert!(!debug.contains("secret-a"));
|
|
assert!(!debug.contains("secret-b"));
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_accepts_api_key_string() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","api_key":"secret"}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
let config = args.into_config();
|
|
assert_eq!(config.api_keys, vec!["secret"]);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_accepts_api_key_list() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","api_key":["secret-a","secret-b"]}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
let config = args.into_config();
|
|
assert_eq!(config.api_keys, vec!["secret-a", "secret-b"]);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_reject_unknown_renderer_value() {
|
|
let error = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--tokenizer-mode",
|
|
"definitely_missing",
|
|
])
|
|
.unwrap_err();
|
|
|
|
expect![[r#"
|
|
error: invalid value 'definitely_missing' for '--tokenizer-mode <RENDERER>': unknown renderer `definitely_missing` (expected one of: auto, hf, deepseek_v32, deepseek_v4, harmony, inkling)
|
|
|
|
For more information, try '--help'.
|
|
"#]]
|
|
.assert_eq(&error.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_reject_unsupported_flag_arg() {
|
|
let error = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--root-path",
|
|
"/prefix",
|
|
])
|
|
.unwrap_err();
|
|
|
|
expect![[r#"
|
|
error: invalid value '/prefix' for '--root-path <ROOT_PATH>': argument is not implemented in Rust frontend yet
|
|
|
|
Remove this unsupported argument to continue.
|
|
|
|
Alternatively, if you intend to pass it only to the Python engine, put it after `--` (e.g., `-- <arg>`).
|
|
This may lead to unexpected behavior as the Rust frontend will completely ignore that argument.
|
|
|
|
For more information, try '--help'.
|
|
"#]].assert_eq(&error.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_reject_unsupported_no_flag_alias() {
|
|
let error = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--no-enable-log-deltas",
|
|
])
|
|
.unwrap_err();
|
|
|
|
expect![[r#"
|
|
error: invalid value 'true' for '--enable-log-deltas [<ENABLE_LOG_DELTAS>]': argument is not implemented in Rust frontend yet
|
|
|
|
Remove this unsupported argument to continue.
|
|
|
|
Alternatively, if you intend to pass it only to the Python engine, put it after `--` (e.g., `-- <arg>`).
|
|
This may lead to unexpected behavior as the Rust frontend will completely ignore that argument.
|
|
|
|
For more information, try '--help'.
|
|
"#]]
|
|
.assert_eq(&error.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_accept_json() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--coordinator-address",
|
|
"tcp://127.0.0.1:7000",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","engine_count":2}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
expect![[r#"
|
|
Cli {
|
|
command: Frontend(
|
|
FrontendArgs {
|
|
listen_fd: 3,
|
|
input_address: "ipc:///tmp/input.sock",
|
|
output_address: "ipc:///tmp/output.sock",
|
|
coordinator_address: Some(
|
|
"tcp://127.0.0.1:7000",
|
|
),
|
|
engine_start_index: 0,
|
|
engine_count: 1,
|
|
runtime: SharedRuntimeArgs {
|
|
model: "Qwen/Qwen3-0.6B",
|
|
engine_ready_timeout_secs: 600,
|
|
tool_call_parser: None,
|
|
reasoning_parser: None,
|
|
renderer: Auto,
|
|
language_model_only: false,
|
|
max_model_len: None,
|
|
max_logprobs: None,
|
|
grpc_port: None,
|
|
shutdown_timeout: 0,
|
|
http_timeout_keep_alive: None,
|
|
chat_template: None,
|
|
default_chat_template_kwargs: None,
|
|
chat_template_content_format: Auto,
|
|
enable_log_requests: false,
|
|
enable_prompt_tokens_details: false,
|
|
enable_request_id_headers: false,
|
|
disable_log_stats: false,
|
|
served_model_name: [],
|
|
allowed_origins: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allowed_methods: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allowed_headers: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allow_credentials: false,
|
|
ssl_keyfile: None,
|
|
ssl_certfile: None,
|
|
ssl_ca_certs: None,
|
|
ssl_cert_reqs: 0,
|
|
ssl_ciphers: None,
|
|
profiler_config: None,
|
|
},
|
|
},
|
|
),
|
|
}
|
|
"#]]
|
|
.assert_debug_eq(&cli);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_applies_defaults() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.model, "Qwen/Qwen3-0.6B");
|
|
assert_eq!(args.runtime.engine_ready_timeout_secs, 600);
|
|
assert_eq!(args.runtime.tool_call_parser, ParserSelection::None);
|
|
assert_eq!(args.runtime.reasoning_parser, ParserSelection::None);
|
|
assert_eq!(args.runtime.renderer, RendererSelection::Auto);
|
|
assert_eq!(args.runtime.max_model_len, None);
|
|
assert_eq!(args.runtime.max_logprobs, None);
|
|
assert_eq!(args.runtime.shutdown_timeout, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_accepts_supported_non_default_fields() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
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_model_len":8192,"max_logprobs":-1,"shutdown_timeout":3}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.engine_ready_timeout_secs, 42);
|
|
assert_eq!(
|
|
args.runtime.tool_call_parser,
|
|
ParserSelection::Explicit("hermes".to_string())
|
|
);
|
|
assert_eq!(
|
|
args.runtime.reasoning_parser,
|
|
ParserSelection::Explicit("qwen3_thinking".to_string())
|
|
);
|
|
assert_eq!(args.runtime.renderer, RendererSelection::DeepSeekV32);
|
|
assert!(args.runtime.language_model_only);
|
|
assert_eq!(args.runtime.max_model_len, Some(8192));
|
|
assert_eq!(args.runtime.max_logprobs, Some(-1));
|
|
assert_eq!(args.runtime.shutdown_timeout, 3);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_accept_none_reasoning_parser() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--reasoning-parser",
|
|
"none",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(args.runtime.reasoning_parser, ParserSelection::None);
|
|
assert_eq!(args.runtime.tool_call_parser, ParserSelection::Auto);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_ignores_unknown_fields() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","uds":"/tmp/vllm.sock","nested_unknown":{"x":1}}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.model, "Qwen/Qwen3-0.6B");
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_sets_prompt_tokens_details_flag() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","api_server_count":2,"enable_prompt_tokens_details":true}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.model, "Qwen/Qwen3-0.6B");
|
|
assert!(args.runtime.enable_prompt_tokens_details);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_parse_cors_flags() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--allowed-origins",
|
|
r#"["http://a.com","http://b.com"]"#,
|
|
"--allowed-methods",
|
|
r#"["GET","POST"]"#,
|
|
"--allow-credentials",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(serve) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(
|
|
serve.runtime.allowed_origins.0,
|
|
["http://a.com", "http://b.com"]
|
|
);
|
|
assert_eq!(serve.runtime.allowed_methods.0, ["GET", "POST"]);
|
|
assert!(serve.runtime.allow_credentials);
|
|
// Unspecified lists keep the permissive default.
|
|
assert_eq!(serve.runtime.allowed_headers.0, ["*"]);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_cors_defaults_are_permissive() {
|
|
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B"]).unwrap();
|
|
|
|
let Command::Serve(serve) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert_eq!(serve.runtime.allowed_origins.0, ["*"]);
|
|
assert_eq!(serve.runtime.allowed_methods.0, ["*"]);
|
|
assert_eq!(serve.runtime.allowed_headers.0, ["*"]);
|
|
assert!(!serve.runtime.allow_credentials);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_parses_cors_fields() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","allowed_origins":["http://a.com"],"allow_credentials":true}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.allowed_origins.0, ["http://a.com"]);
|
|
assert!(args.runtime.allow_credentials);
|
|
// Unspecified lists fall back to the permissive default via serde.
|
|
assert_eq!(args.runtime.allowed_methods.0, ["*"]);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_rejects_unsupported_fields() {
|
|
let error = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","root_path":"/prefix"}"#,
|
|
])
|
|
.unwrap_err();
|
|
|
|
expect![[r#"
|
|
error: invalid value '{"model_tag":"Qwen/Qwen3-0.6B","root_path":"/prefix"}' for '--args-json <JSON>':
|
|
The following arguments are not implemented in Rust frontend yet:
|
|
- root_path
|
|
|
|
Remove these arguments to continue.
|
|
|
|
For more information, try '--help'.
|
|
"#]].assert_eq(&error.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_aggregates_multiple_unsupported_fields() {
|
|
let error = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","response_role":"assistant","root_path":"/prefix"}"#,
|
|
])
|
|
.unwrap_err();
|
|
|
|
let actual = error.to_string().replace(": \n", ":\n");
|
|
expect![[r#"
|
|
error: invalid value '{"model_tag":"Qwen/Qwen3-0.6B","response_role":"assistant","root_path":"/prefix"}' for '--args-json <JSON>':
|
|
The following arguments are not implemented in Rust frontend yet:
|
|
- response_role
|
|
- root_path
|
|
|
|
Remove these arguments to continue.
|
|
|
|
For more information, try '--help'.
|
|
"#]].assert_eq(&actual);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_rejects_malformed_json() {
|
|
let error = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B""#,
|
|
])
|
|
.unwrap_err();
|
|
|
|
expect![[r#"
|
|
error: invalid value '{"model_tag":"Qwen/Qwen3-0.6B"' for '--args-json <JSON>': invalid JSON arguments: EOF while parsing an object at line 1 column 30
|
|
|
|
For more information, try '--help'.
|
|
"#]].assert_eq(&error.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_reject_flags_before_model() {
|
|
let error = Cli::try_parse_from(["vllm-rs", "serve", "--python", "python3", "Qwen/Qwen3-0.6B"])
|
|
.unwrap_err();
|
|
|
|
expect![[r#"
|
|
error: the model must appear immediately after the command
|
|
|
|
Usage: vllm-rs serve <MODEL> [OPTIONS] [-- <PYTHON_ARGS>...]
|
|
|
|
For more information, try '--help'.
|
|
"#]]
|
|
.assert_eq(&error.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_accept_headless_mode() {
|
|
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--headless"]).unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert!(args.headless);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_keep_python_passthrough_flags_after_separator() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--python",
|
|
"python3",
|
|
"--",
|
|
"--tensor-parallel-size",
|
|
"2",
|
|
"--dtype",
|
|
"float16",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"--tensor-parallel-size",
|
|
"2",
|
|
"--dtype",
|
|
"float16",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_keep_python_multi_char_alias_after_separator() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--python",
|
|
"python3",
|
|
"--",
|
|
"-tp",
|
|
"2",
|
|
"--dtype",
|
|
"float16",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"-tp",
|
|
"2",
|
|
"--dtype",
|
|
"float16",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_keep_frontend_arg_after_separator() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--",
|
|
"--uds",
|
|
"/tmp/vllm.sock",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"--uds",
|
|
"/tmp/vllm.sock",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_keep_python_multi_char_engine_aliases_after_separator() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--",
|
|
"-dpr",
|
|
"1",
|
|
"-dpl",
|
|
"2",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"-dpr",
|
|
"1",
|
|
"-dpl",
|
|
"2",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_auto_forward_unknown_flags_without_separator() {
|
|
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--foo", "bar"]).unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"--foo",
|
|
"bar",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_auto_forward_negative_value_without_separator() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--num-gpu-blocks-override",
|
|
"-1",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
expect![[r#"
|
|
[
|
|
"--num-gpu-blocks-override",
|
|
"-1",
|
|
]
|
|
"#]]
|
|
.assert_debug_eq(&args.managed_engine.python_args);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_accept_handshake_aliases() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--python",
|
|
"python3",
|
|
"--handshake-host",
|
|
"10.99.48.128",
|
|
"--handshake-port",
|
|
"13345",
|
|
"--data-parallel-size",
|
|
"4",
|
|
])
|
|
.unwrap();
|
|
|
|
expect![[r#"
|
|
Cli {
|
|
command: Serve(
|
|
ServeArgs {
|
|
headless: false,
|
|
host: "127.0.0.1",
|
|
port: 8000,
|
|
uds: None,
|
|
runtime: SharedRuntimeArgs {
|
|
model: "Qwen/Qwen3-0.6B",
|
|
engine_ready_timeout_secs: 600,
|
|
tool_call_parser: Auto,
|
|
reasoning_parser: Auto,
|
|
renderer: Auto,
|
|
language_model_only: false,
|
|
max_model_len: None,
|
|
max_logprobs: None,
|
|
grpc_port: None,
|
|
shutdown_timeout: 0,
|
|
http_timeout_keep_alive: None,
|
|
chat_template: None,
|
|
default_chat_template_kwargs: None,
|
|
chat_template_content_format: Auto,
|
|
enable_log_requests: false,
|
|
enable_prompt_tokens_details: false,
|
|
enable_request_id_headers: false,
|
|
disable_log_stats: false,
|
|
served_model_name: [],
|
|
allowed_origins: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allowed_methods: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allowed_headers: JsonStringList(
|
|
[
|
|
"*",
|
|
],
|
|
),
|
|
allow_credentials: false,
|
|
ssl_keyfile: None,
|
|
ssl_certfile: None,
|
|
ssl_ca_certs: None,
|
|
ssl_cert_reqs: 0,
|
|
ssl_ciphers: None,
|
|
profiler_config: None,
|
|
},
|
|
managed_engine: ManagedEngineArgs {
|
|
python: "python3",
|
|
handshake_host: "10.99.48.128",
|
|
handshake_port: Some(
|
|
13345,
|
|
),
|
|
data_parallel_size: 4,
|
|
data_parallel_size_local: None,
|
|
python_args: [],
|
|
},
|
|
},
|
|
),
|
|
}
|
|
"#]]
|
|
.assert_debug_eq(&cli);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_args_accept_data_parallel_primary_flags() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--data-parallel-address",
|
|
"10.99.48.128",
|
|
"--data-parallel-rpc-port",
|
|
"13345",
|
|
"--data-parallel-size",
|
|
"4",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
assert!(!args.headless);
|
|
assert_eq!(args.managed_engine.handshake_host, "10.99.48.128");
|
|
assert_eq!(args.managed_engine.handshake_port, Some(13345));
|
|
assert_eq!(args.managed_engine.data_parallel_size, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_frontend_config_uses_dp_address_as_advertised_host() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--handshake-host",
|
|
"10.99.48.128",
|
|
"--data-parallel-size",
|
|
"4",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://10.99.48.128:29550".to_string());
|
|
|
|
let TransportMode::HandshakeOwner {
|
|
handshake_address,
|
|
advertised_host,
|
|
engine_count,
|
|
ready_timeout,
|
|
local_input_address,
|
|
local_output_address,
|
|
} = &config.transport_mode
|
|
else {
|
|
panic!("expected handshake-owned transport");
|
|
};
|
|
|
|
assert_eq!(handshake_address, "tcp://10.99.48.128:29550");
|
|
assert_eq!(advertised_host, "10.99.48.128");
|
|
assert_eq!(*engine_count, 4);
|
|
assert!(
|
|
local_input_address
|
|
.as_deref()
|
|
.is_some_and(|address| address.starts_with("ipc://"))
|
|
);
|
|
assert!(
|
|
local_output_address
|
|
.as_deref()
|
|
.is_some_and(|address| address.starts_with("ipc://"))
|
|
);
|
|
assert_ne!(local_input_address, local_output_address);
|
|
|
|
expect![[r#"
|
|
Config {
|
|
transport_mode: HandshakeOwner {
|
|
handshake_address: "tcp://10.99.48.128:29550",
|
|
advertised_host: "10.99.48.128",
|
|
engine_count: 4,
|
|
ready_timeout: 600s,
|
|
local_input_address: Some(
|
|
"<ipc input>",
|
|
),
|
|
local_output_address: Some(
|
|
"<ipc output>",
|
|
),
|
|
},
|
|
coordinator_mode: MaybeInProc,
|
|
model: "Qwen/Qwen3-0.6B",
|
|
served_model_name: [],
|
|
listener_mode: BindTcp {
|
|
host: "127.0.0.1",
|
|
port: 8000,
|
|
},
|
|
tool_call_parser: Auto,
|
|
reasoning_parser: Auto,
|
|
renderer: Auto,
|
|
language_model_only: false,
|
|
chat_template: None,
|
|
default_chat_template_kwargs: None,
|
|
chat_template_content_format: Auto,
|
|
max_logprobs: None,
|
|
api_server_options: ApiServerOptions {
|
|
enable_log_requests: false,
|
|
enable_prompt_tokens_details: false,
|
|
enable_request_id_headers: false,
|
|
},
|
|
cors: CorsConfig {
|
|
allow_origins: [
|
|
"*",
|
|
],
|
|
allow_methods: [
|
|
"*",
|
|
],
|
|
allow_headers: [
|
|
"*",
|
|
],
|
|
allow_credentials: false,
|
|
},
|
|
tls: None,
|
|
api_keys: [],
|
|
disable_log_stats: false,
|
|
grpc_port: None,
|
|
shutdown_timeout: 0ns,
|
|
keep_alive_timeout: 5s,
|
|
profiler: None,
|
|
}
|
|
"#]]
|
|
.assert_debug_eq(&Config {
|
|
transport_mode: TransportMode::HandshakeOwner {
|
|
handshake_address: handshake_address.clone(),
|
|
advertised_host: advertised_host.clone(),
|
|
engine_count: *engine_count,
|
|
ready_timeout: *ready_timeout,
|
|
local_input_address: Some("<ipc input>".to_string()),
|
|
local_output_address: Some("<ipc output>".to_string()),
|
|
},
|
|
..config.clone()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn serve_frontend_config_keeps_tcp_transport_for_non_local_only_topology() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--data-parallel-address",
|
|
"10.99.48.128",
|
|
"--data-parallel-size",
|
|
"4",
|
|
"--data-parallel-size-local",
|
|
"2",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://10.99.48.128:29550".to_string());
|
|
|
|
expect![[r#"
|
|
Config {
|
|
transport_mode: HandshakeOwner {
|
|
handshake_address: "tcp://10.99.48.128:29550",
|
|
advertised_host: "10.99.48.128",
|
|
engine_count: 4,
|
|
ready_timeout: 600s,
|
|
local_input_address: None,
|
|
local_output_address: None,
|
|
},
|
|
coordinator_mode: MaybeInProc,
|
|
model: "Qwen/Qwen3-0.6B",
|
|
served_model_name: [],
|
|
listener_mode: BindTcp {
|
|
host: "127.0.0.1",
|
|
port: 8000,
|
|
},
|
|
tool_call_parser: Auto,
|
|
reasoning_parser: Auto,
|
|
renderer: Auto,
|
|
language_model_only: false,
|
|
chat_template: None,
|
|
default_chat_template_kwargs: None,
|
|
chat_template_content_format: Auto,
|
|
max_logprobs: None,
|
|
api_server_options: ApiServerOptions {
|
|
enable_log_requests: false,
|
|
enable_prompt_tokens_details: false,
|
|
enable_request_id_headers: false,
|
|
},
|
|
cors: CorsConfig {
|
|
allow_origins: [
|
|
"*",
|
|
],
|
|
allow_methods: [
|
|
"*",
|
|
],
|
|
allow_headers: [
|
|
"*",
|
|
],
|
|
allow_credentials: false,
|
|
},
|
|
tls: None,
|
|
api_keys: [],
|
|
disable_log_stats: false,
|
|
grpc_port: None,
|
|
shutdown_timeout: 0ns,
|
|
keep_alive_timeout: 5s,
|
|
profiler: None,
|
|
}
|
|
"#]]
|
|
.assert_debug_eq(&config);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_reject_legacy_handshake_flags() {
|
|
let error = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
|
|
"--handshake-address",
|
|
"tcp://127.0.0.1:62100",
|
|
])
|
|
.unwrap_err();
|
|
|
|
assert!(error.to_string().contains("--handshake-address"));
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_config_uses_external_coordinator_when_coordinator_address_is_present() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--coordinator-address",
|
|
"tcp://127.0.0.1:7000",
|
|
"--engine-start-index",
|
|
"3",
|
|
"--engine-count",
|
|
"1",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
let config = args.into_config();
|
|
|
|
expect![[r#"
|
|
Config {
|
|
transport_mode: Bootstrapped {
|
|
input_address: "ipc:///tmp/input.sock",
|
|
output_address: "ipc:///tmp/output.sock",
|
|
engine_start_index: 3,
|
|
engine_count: 1,
|
|
ready_timeout: 600s,
|
|
},
|
|
coordinator_mode: External {
|
|
address: "tcp://127.0.0.1:7000",
|
|
},
|
|
model: "Qwen/Qwen3-0.6B",
|
|
served_model_name: [],
|
|
listener_mode: InheritedFd {
|
|
fd: 3,
|
|
},
|
|
tool_call_parser: None,
|
|
reasoning_parser: None,
|
|
renderer: Auto,
|
|
language_model_only: false,
|
|
chat_template: None,
|
|
default_chat_template_kwargs: None,
|
|
chat_template_content_format: Auto,
|
|
max_logprobs: None,
|
|
api_server_options: ApiServerOptions {
|
|
enable_log_requests: false,
|
|
enable_prompt_tokens_details: false,
|
|
enable_request_id_headers: false,
|
|
},
|
|
cors: CorsConfig {
|
|
allow_origins: [
|
|
"*",
|
|
],
|
|
allow_methods: [
|
|
"*",
|
|
],
|
|
allow_headers: [
|
|
"*",
|
|
],
|
|
allow_credentials: false,
|
|
},
|
|
tls: None,
|
|
api_keys: [],
|
|
disable_log_stats: false,
|
|
grpc_port: None,
|
|
shutdown_timeout: 0ns,
|
|
keep_alive_timeout: 5s,
|
|
profiler: None,
|
|
}
|
|
"#]]
|
|
.assert_debug_eq(&config);
|
|
}
|
|
|
|
#[test]
|
|
fn serve_frontend_config_uses_unix_listener_when_uds_is_present() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"serve",
|
|
"Qwen/Qwen3-0.6B",
|
|
"--uds",
|
|
"/tmp/vllm.sock",
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Serve(args) = cli.command else {
|
|
panic!("expected serve args");
|
|
};
|
|
let config = args.to_frontend_config("tcp://127.0.0.1:29550".to_string());
|
|
|
|
assert_eq!(
|
|
config.listener_mode,
|
|
HttpListenerMode::BindUnix {
|
|
path: "/tmp/vllm.sock".to_string(),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_enables_profiling_when_profiler_config_set() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","profiler_config":{"profiler":"torch","torch_profiler_dir":"/tmp/profile"}}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.profiler().as_deref(), Some("torch"));
|
|
let config = args.into_config();
|
|
assert_eq!(config.profiler.as_deref(), Some("torch"));
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_disables_profiling_when_profiler_config_absent() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.profiler(), None);
|
|
let config = args.into_config();
|
|
assert_eq!(config.profiler, None);
|
|
}
|
|
|
|
#[test]
|
|
fn frontend_args_json_disables_profiling_when_profiler_type_is_null() {
|
|
let cli = Cli::try_parse_from([
|
|
"vllm-rs",
|
|
"frontend",
|
|
"--listen-fd",
|
|
"3",
|
|
"--input-address",
|
|
"ipc:///tmp/input.sock",
|
|
"--output-address",
|
|
"ipc:///tmp/output.sock",
|
|
"--args-json",
|
|
r#"{"model_tag":"Qwen/Qwen3-0.6B","profiler_config":{"profiler":null}}"#,
|
|
])
|
|
.unwrap();
|
|
|
|
let Command::Frontend(args) = cli.command else {
|
|
panic!("expected frontend args");
|
|
};
|
|
assert_eq!(args.runtime.profiler(), None);
|
|
let config = args.into_config();
|
|
assert_eq!(config.profiler, None);
|
|
}
|