Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

603 lines
22 KiB
Python

"""Tests for vLLM-style CLI configuration arguments.
Verifies that vLLM-style CLI args are correctly parsed and mapped
to TokenSpeed's internal ServerArgs configuration.
"""
import os
import sys
# CI Registration (parsed via AST, runtime no-op)
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ci_system.ci_register import register_cuda_ci
register_cuda_ci(est_time=10, suite="runtime-1gpu")
import argparse
import contextlib
import io
import unittest
from types import SimpleNamespace
from unittest.mock import patch
from tokenspeed.runtime.utils.server_args import ServerArgs
class TestCLIConfigCompat(unittest.TestCase):
"""Test that vLLM-style CLI arguments map to TokenSpeed config."""
def _parse_args(self, argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser()
ServerArgs.add_cli_args(parser)
return parser.parse_args(argv)
def _from_cli_args_no_init(self, args: argparse.Namespace) -> ServerArgs:
with patch.object(ServerArgs, "__post_init__"):
return ServerArgs.from_cli_args(args)
def _parallelism_snapshot(self, argv: list[str]) -> tuple[int, ...]:
args = self._parse_args(argv)
sa = self._from_cli_args_no_init(args)
sa.resolve_basic_defaults()
sa.resolve_parallelism()
mapping = sa.mapping
return (
mapping.world_size,
mapping.attn.tp_size,
mapping.attn.cp_size,
mapping.attn.dp_size,
mapping.dense.tp_size,
mapping.dense.dp_size,
mapping.moe.tp_size,
mapping.moe.ep_size,
mapping.moe.dp_size,
)
# ---- Positional model arg ----
def test_positional_model_arg(self):
args = self._parse_args(["deepseek-ai/DeepSeek-V3"])
self.assertEqual(args.model_path, "deepseek-ai/DeepSeek-V3")
self.assertIsNone(args.model)
def test_model_flag(self):
args = self._parse_args(["--model", "deepseek-ai/DeepSeek-V3"])
self.assertIsNone(args.model_path)
self.assertEqual(args.model, "deepseek-ai/DeepSeek-V3")
def test_positional_model_resolved_in_from_cli_args(self):
args = self._parse_args(["deepseek-ai/DeepSeek-V3"])
sa = self._from_cli_args_no_init(args)
self.assertEqual(sa.model, "deepseek-ai/DeepSeek-V3")
def test_both_positional_and_model_raises(self):
args = self._parse_args(["deepseek-ai/DeepSeek-V3", "--model", "other/model"])
with self.assertRaises(ValueError):
self._from_cli_args_no_init(args)
def test_no_model_raises(self):
args = self._parse_args([])
with self.assertRaises(ValueError):
self._from_cli_args_no_init(args)
# ---- Tensor parallel size ----
def test_tensor_parallel_size_maps_to_attn_tp_size(self):
args = self._parse_args(
["--model", "test/model", "--tensor-parallel-size", "8"]
)
sa = self._from_cli_args_no_init(args)
self.assertEqual(sa.attn_tp_size, 8)
def test_tp_long_alias(self):
args = self._parse_args(["--model", "test/model", "--tp", "4"])
sa = self._from_cli_args_no_init(args)
self.assertEqual(sa.attn_tp_size, 4)
def test_tensor_parallel_aliases_match_explicit_attn_moe_tp(self):
explicit = self._parallelism_snapshot(
[
"--model",
"nvidia/Kimi-K2.5-NVFP4",
"--attn-tp-size",
"4",
"--moe-tp-size",
"4",
]
)
tensor_parallel_size = self._parallelism_snapshot(
[
"--model",
"nvidia/Kimi-K2.5-NVFP4",
"--tensor-parallel-size",
"4",
]
)
tp = self._parallelism_snapshot(
[
"--model",
"nvidia/Kimi-K2.5-NVFP4",
"--tp",
"4",
]
)
self.assertEqual(tensor_parallel_size, explicit)
self.assertEqual(tp, explicit)
def test_tensor_parallel_size_conflicts_with_attn_tp_size(self):
args = self._parse_args(
[
"--model",
"test/model",
"--tensor-parallel-size",
"8",
"--attn-tp-size",
"4",
]
)
with self.assertRaises(ValueError):
self._from_cli_args_no_init(args)
# ---- Enable expert parallel ----
def test_enable_expert_parallel_flag(self):
args = self._parse_args(["--model", "test/model", "--enable-expert-parallel"])
sa = self._from_cli_args_no_init(args)
self.assertTrue(sa.enable_expert_parallel)
def test_enable_expert_parallel_default_false(self):
args = self._parse_args(["--model", "test/model"])
sa = self._from_cli_args_no_init(args)
self.assertFalse(sa.enable_expert_parallel)
# ---- vLLM config names ----
def test_tokenizer_arg(self):
args = self._parse_args(
["--model", "test/model", "--tokenizer", "my/tokenizer"]
)
self.assertEqual(args.tokenizer, "my/tokenizer")
def test_max_model_len_arg(self):
args = self._parse_args(["--model", "test/model", "--max-model-len", "4096"])
self.assertEqual(args.max_model_len, 4096)
def test_gpu_memory_utilization_arg(self):
args = self._parse_args(
["--model", "test/model", "--gpu-memory-utilization", "0.9"]
)
self.assertEqual(args.gpu_memory_utilization, 0.9)
def test_seed_arg(self):
args = self._parse_args(["--model", "test/model", "--seed", "42"])
self.assertEqual(args.seed, 42)
def test_max_num_seqs_arg(self):
args = self._parse_args(["--model", "test/model", "--max-num-seqs", "256"])
self.assertEqual(args.max_num_seqs, 256)
def test_dp_sampling_backend_arg_removed(self):
with self.assertRaises(SystemExit):
self._parse_args(
["--model", "test/model", "--dp-sampling-backend", "onesided"]
)
def test_max_prefill_tokens_arg(self):
args = self._parse_args(
["--model", "test/model", "--max-prefill-tokens", "4096"]
)
self.assertEqual(args.max_prefill_tokens, 4096)
def test_chunked_prefill_size_arg(self):
args = self._parse_args(
["--model", "test/model", "--chunked-prefill-size", "2048"]
)
self.assertEqual(args.chunked_prefill_size, 2048)
def test_prefill_token_defaults(self):
args = self._parse_args(["--model", "test/model"])
self.assertEqual(args.max_prefill_tokens, 8192)
self.assertIsNone(args.chunked_prefill_size)
self.assertFalse(args.enable_mixed_batch)
sa = self._from_cli_args_no_init(args)
sa.mapping = SimpleNamespace(world_size=1)
platform = SimpleNamespace(is_amd=False, is_nvidia=False)
with patch(
"tokenspeed.runtime.utils.server_args.current_platform",
return_value=platform,
):
sa.resolve_memory_and_scheduling()
self.assertEqual(sa.max_prefill_tokens, 8192)
self.assertEqual(sa.chunked_prefill_size, 8192)
self.assertFalse(sa.enable_mixed_batch)
def test_mixed_batch_can_be_enabled(self):
args = self._parse_args(["--model", "test/model", "--enable-mixed-batch"])
self.assertTrue(args.enable_mixed_batch)
def test_distributed_timeout_seconds_arg(self):
args = self._parse_args(
["--model", "test/model", "--distributed-timeout-seconds", "600"]
)
self.assertEqual(args.distributed_timeout_seconds, 600)
def test_enforce_eager_arg(self):
args = self._parse_args(["--model", "test/model", "--enforce-eager"])
self.assertTrue(args.enforce_eager)
def test_cudagraph_capture_size_arg(self):
args = self._parse_args(
["--model", "test/model", "--max-cudagraph-capture-size", "32"]
)
self.assertEqual(args.max_cudagraph_capture_size, 32)
def test_cudagraph_capture_sizes_arg(self):
args = self._parse_args(
[
"--model",
"test/model",
"--cudagraph-capture-sizes",
"1",
"2",
"4",
]
)
self.assertEqual(args.cudagraph_capture_sizes, [1, 2, 4])
def test_block_size_arg(self):
args = self._parse_args(["--model", "test/model", "--block-size", "128"])
self.assertEqual(args.block_size, 128)
def test_moe_backend_arg(self):
args = self._parse_args(["--model", "test/model", "--moe-backend", "triton"])
self.assertEqual(args.moe_backend, "triton")
def test_sampling_backend_arg(self):
for backend in (
"greedy",
"flashinfer",
"flashinfer_full",
"triton",
"triton_full",
):
args = self._parse_args(
["--model", "test/model", "--sampling-backend", backend]
)
self.assertEqual(args.sampling_backend, backend)
def test_all2all_backend_arg(self):
args = self._parse_args(
["--model", "test/model", "--all2all-backend", "deepep"]
)
self.assertEqual(args.all2all_backend, "deepep")
def test_recipe_all2all_backend_alias_arg(self):
args = self._parse_args(
[
"--model",
"test/model",
"--all2all-backend",
"flashinfer_nvlink_one_sided",
]
)
self.assertEqual(args.all2all_backend, "flashinfer_nvlink_one_sided")
def test_recipe_moe_backend_alias_arg(self):
args = self._parse_args(
["--model", "test/model", "--moe-backend", "deep_gemm_mega_moe"]
)
self.assertEqual(args.moe_backend, "deep_gemm_mega_moe")
def test_kv_cache_dtype_fp8_alias_arg(self):
args = self._parse_args(["--model", "test/model", "--kv-cache-dtype", "fp8"])
sa = self._from_cli_args_no_init(args)
sa.resolve_basic_defaults()
self.assertEqual(sa.kv_cache_dtype, "fp8_e4m3")
def test_tokenizer_mode_deepseek_v4_arg(self):
args = self._parse_args(
["--model", "test/model", "--tokenizer-mode", "deepseek_v4"]
)
self.assertEqual(args.tokenizer_mode, "deepseek_v4")
def test_hf_overrides_arg(self):
args = self._parse_args(
["--model", "test/model", "--hf-overrides", '{"rope_scaling": null}']
)
self.assertEqual(args.hf_overrides, '{"rope_scaling": null}')
def test_enable_log_requests_arg(self):
args = self._parse_args(["--model", "test/model", "--enable-log-requests"])
self.assertTrue(args.enable_log_requests)
def test_no_enable_log_requests_arg(self):
args = self._parse_args(["--model", "test/model", "--no-enable-log-requests"])
self.assertFalse(args.enable_log_requests)
def test_no_trust_remote_code_arg(self):
args = self._parse_args(["--model", "test/model", "--no-trust-remote-code"])
self.assertFalse(args.trust_remote_code)
def test_enable_prefix_caching_arg(self):
args = self._parse_args(["--model", "test/model", "--enable-prefix-caching"])
self.assertTrue(args.enable_prefix_caching)
def test_no_enable_prefix_caching_arg(self):
args = self._parse_args(["--model", "test/model", "--no-enable-prefix-caching"])
self.assertFalse(args.enable_prefix_caching)
def test_kv_events_config_arg(self):
config = (
'{"publisher":"zmq","endpoint":"tcp://*:5557",'
'"topic":"kv-events","enable_kv_cache_events":true}'
)
args = self._parse_args(["--model", "test/model", "--kv-events-config", config])
sa = self._from_cli_args_no_init(args)
self.assertEqual(sa.kv_events_config, config)
def test_speculative_draft_quantization_defaults_to_unquant(self):
args = self._parse_args(["--model", "test/model", "--quantization", "nvfp4"])
self.assertEqual(args.speculative_draft_model_quantization, "unquant")
sa = self._from_cli_args_no_init(args)
sa.resolve_speculative_decoding()
self.assertIsNone(sa.speculative_draft_model_quantization)
def test_mxfp4_quantization_arg(self):
args = self._parse_args(["--model", "test/model", "--quantization", "mxfp4"])
self.assertEqual(args.quantization, "mxfp4")
def test_dotted_attention_config_args(self):
args = self._parse_args(
[
"--model",
"test/model",
"--attention_config.use_fp4_indexer_cache=True",
"--attention-config.use_trtllm_ragged_deepseek_prefill=True",
]
)
self.assertTrue(args.attention_use_fp4_indexer_cache)
self.assertTrue(args.use_trtllm_ragged_deepseek_prefill)
def test_vllm_recipe_speculative_config_arg(self):
args = self._parse_args(
[
"--model",
"test/model",
"--speculative-config",
'{"method": "mtp", "model": "draft/model", "num_speculative_tokens": 3}',
]
)
sa = self._from_cli_args_no_init(args)
sa.resolve_basic_defaults()
self.assertEqual(sa.speculative_algorithm, "MTP")
self.assertEqual(sa.speculative_draft_model_path, "draft/model")
self.assertEqual(sa.speculative_num_steps, 3)
self.assertEqual(sa.speculative_num_draft_tokens, 4)
def test_speculative_config_matches_explicit_eagle3_args(self):
draft_model = "lightseekorg/kimi-k2.5-eagle3-mla"
config_args = self._parse_args(
[
"--model",
"test/model",
"--speculative-config",
(
f'{{"model":"{draft_model}",'
'"method":"eagle3",'
'"num_speculative_tokens":1}'
),
]
)
explicit_args = self._parse_args(
[
"--model",
"test/model",
"--speculative-algorithm",
"EAGLE3",
"--speculative-draft-model-path",
draft_model,
"--speculative-num-steps",
"1",
]
)
config_server_args = self._from_cli_args_no_init(config_args)
explicit_server_args = self._from_cli_args_no_init(explicit_args)
config_server_args.resolve_basic_defaults()
explicit_server_args.resolve_basic_defaults()
self.assertEqual(
config_server_args.speculative_algorithm,
explicit_server_args.speculative_algorithm,
)
self.assertEqual(
config_server_args.speculative_draft_model_path,
explicit_server_args.speculative_draft_model_path,
)
self.assertEqual(
config_server_args.speculative_num_steps,
explicit_server_args.speculative_num_steps,
)
self.assertEqual(
config_server_args.speculative_num_draft_tokens,
explicit_server_args.speculative_num_draft_tokens,
)
def test_speculative_config_matches_explicit_mtp_args(self):
target_model = "nvidia/Qwen3.5-397B-A17B-NVFP4"
config_args = self._parse_args(
[
"--model",
target_model,
"--speculative-config",
'{"method":"mtp","num_speculative_tokens":3}',
]
)
explicit_args = self._parse_args(
[
"--model",
target_model,
"--speculative-algorithm",
"MTP",
"--speculative-num-steps",
"3",
]
)
config_server_args = self._from_cli_args_no_init(config_args)
explicit_server_args = self._from_cli_args_no_init(explicit_args)
config_server_args.resolve_basic_defaults()
explicit_server_args.resolve_basic_defaults()
config_server_args.resolve_speculative_decoding()
explicit_server_args.resolve_speculative_decoding()
self.assertEqual(
config_server_args.speculative_algorithm,
explicit_server_args.speculative_algorithm,
)
self.assertEqual(
config_server_args.speculative_draft_model_path,
explicit_server_args.speculative_draft_model_path,
)
self.assertEqual(
config_server_args.speculative_draft_model_path,
target_model,
)
self.assertTrue(explicit_server_args.draft_model_path_use_base)
self.assertEqual(
config_server_args.speculative_num_steps,
explicit_server_args.speculative_num_steps,
)
self.assertEqual(
config_server_args.speculative_num_draft_tokens,
explicit_server_args.speculative_num_draft_tokens,
)
def test_speculative_config_must_be_json_object(self):
args = self._parse_args(["--model", "test/model", "--speculative-config", "[]"])
sa = self._from_cli_args_no_init(args)
with self.assertRaisesRegex(
ValueError, "--speculative-config must be a JSON object"
):
sa.resolve_basic_defaults()
def test_speculative_defaults(self):
args = self._parse_args(["--model", "test/model"])
sa = self._from_cli_args_no_init(args)
sa.resolve_basic_defaults()
self.assertEqual(sa.speculative_num_steps, 3)
self.assertEqual(sa.speculative_eagle_topk, 1)
self.assertEqual(sa.speculative_num_draft_tokens, 4)
def test_speculative_draft_tokens_default_to_steps_plus_one(self):
args = self._parse_args(
["--model", "test/model", "--speculative-num-steps", "1"]
)
sa = self._from_cli_args_no_init(args)
sa.resolve_basic_defaults()
self.assertEqual(sa.speculative_num_steps, 1)
self.assertEqual(sa.speculative_num_draft_tokens, 2)
def test_speculative_eagle_topk_cli_rejects_non_1(self):
# Only chain spec (topk=1) is wired end-to-end; the CLI choices
# set is the gate, so non-1 values must fail at parse time.
with self.assertRaises(SystemExit):
self._parse_args(["--model", "test/model", "--speculative-eagle-topk", "4"])
def test_speculative_eagle_topk_runtime_rejects_non_1_when_spec_on(self):
# ServerArgs can be built programmatically (e.g. by smg_grpc_servicer),
# bypassing argparse — keep the resolve-time defensive check covered.
args = self._parse_args(
[
"--model",
"test/model",
"--speculative-algorithm",
"EAGLE3",
]
)
sa = self._from_cli_args_no_init(args)
sa.speculative_eagle_topk = 4
sa.resolve_basic_defaults()
with self.assertRaisesRegex(ValueError, "speculative_eagle_topk"):
sa.resolve_speculative_decoding()
def test_dp_sampling_is_opt_in(self):
args = self._parse_args(["--model", "test/model"])
sa = self._from_cli_args_no_init(args)
self.assertFalse(sa.dp_sampling)
self.assertIsNone(sa.dp_sampling_min_bs)
args = self._parse_args(["--model", "test/model", "--dp-sampling"])
sa = self._from_cli_args_no_init(args)
self.assertTrue(sa.dp_sampling)
def test_dp_sampling_min_bs_arg(self):
args = self._parse_args(["--model", "test/model", "--dp-sampling-min-bs", "16"])
sa = self._from_cli_args_no_init(args)
self.assertEqual(sa.dp_sampling_min_bs, 16)
# ---- Full server command example ----
def test_full_server_command(self):
"""Test a full server command example:
tokenspeed serve deepseek-ai/DeepSeek-V3.1 \\
--enable-expert-parallel \\
--tensor-parallel-size 8 \\
--served-model-name ds31
"""
args = self._parse_args(
[
"deepseek-ai/DeepSeek-V3.1",
"--enable-expert-parallel",
"--tensor-parallel-size",
"8",
"--served-model-name",
"ds31",
]
)
sa = self._from_cli_args_no_init(args)
self.assertEqual(sa.model, "deepseek-ai/DeepSeek-V3.1")
self.assertEqual(sa.attn_tp_size, 8)
self.assertTrue(sa.enable_expert_parallel)
self.assertEqual(sa.served_model_name, "ds31")
def test_data_parallel_size_arg(self):
args = self._parse_args(["--model", "test/model", "--data-parallel-size", "2"])
sa = self._from_cli_args_no_init(args)
self.assertEqual(sa.data_parallel_size, 2)
def test_help_uses_expected_metavars(self):
parser = argparse.ArgumentParser()
ServerArgs.add_cli_args(parser)
with contextlib.redirect_stdout(io.StringIO()) as stdout:
with self.assertRaises(SystemExit):
parser.parse_args(["--help"])
help_text = stdout.getvalue()
self.assertIn("--max-num-seqs MAX_NUM_SEQS", help_text)
self.assertIn("--max-prefill-tokens MAX_PREFILL_TOKENS", help_text)
self.assertIn("--chunked-prefill-size CHUNKED_PREFILL_SIZE", help_text)
self.assertIn("--gpu-memory-utilization GPU_MEMORY_UTILIZATION", help_text)
self.assertIn(
"--distributed-timeout-seconds DISTRIBUTED_TIMEOUT_SECONDS", help_text
)
self.assertIn("--all2all-backend ALL2ALL_BACKEND", help_text)
self.assertIn("--hf-overrides HF_OVERRIDES", help_text)
self.assertNotIn("MAX_RUNNING_REQUESTS", help_text)
self.assertNotIn("MEM_FRACTION_STATIC", help_text)
self.assertNotIn("DIST_TIMEOUT", help_text)
self.assertNotIn("MOE_A2A_BACKEND", help_text)
self.assertNotIn("JSON_MODEL_OVERRIDE_ARGS", help_text)
if __name__ == "__main__":
unittest.main()