From 5bef6adfa612f513e024d2953ac361f17b6f74d8 Mon Sep 17 00:00:00 2001 From: Yuan Tong <13075180+tongyuantongyu@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:20:08 +0800 Subject: [PATCH 1/2] Extract CPU only unittests - trunk Signed-off-by: Yuan Tong <13075180+tongyuantongyu@users.noreply.github.com> --- jenkins/L0_Test.groovy | 2 +- tensorrt_llm/llmapi/llm_args.py | 10 ---------- tests/README.md | 14 +++++++++----- tests/integration/defs/test_unittests.py | 11 ++++++++--- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/jenkins/L0_Test.groovy b/jenkins/L0_Test.groovy index 397fe4620399..ca96b6ffb557 100644 --- a/jenkins/L0_Test.groovy +++ b/jenkins/L0_Test.groovy @@ -1393,7 +1393,7 @@ def getPytestBaseCommandLine( if (stageName.contains("-Ray-")) { testCmdLine += ["--run-ray"] } - def unittestMarkExpr = (stageName.startsWith("CPU-")) ? "cpu_only and not disabled" : "not cpu_only" + def unittestMarkExpr = (stageName.startsWith("CPU-")) ? "cpu_only" : "not cpu_only" testCmdLine += ["--unittest-markexpr='${unittestMarkExpr}'"] if (ENABLE_UPLOAD_TEST_RESULTS) { testCmdLine += ["-o console_output_style=progress-even-when-capture-no"] diff --git a/tensorrt_llm/llmapi/llm_args.py b/tensorrt_llm/llmapi/llm_args.py index f1f382dc8c0d..f200fda9bd64 100644 --- a/tensorrt_llm/llmapi/llm_args.py +++ b/tensorrt_llm/llmapi/llm_args.py @@ -4679,16 +4679,6 @@ def from_yaml(cls, yaml_path: Union[str, Path]): raise ValueError("Configuration file root must be a mapping.") return cls(**config_dict) - @field_validator("dtype") - @classmethod - def validate_dtype(cls, v, info): - if torch.cuda.get_device_properties(0).major < 8: - if v == 'auto': - v = 'float16' - if v == 'bfloat16': - raise RuntimeError("Pre SM 80 GPUs do not support bfloat16") - return v - @field_validator("gpus_per_node", mode='before') @classmethod def validate_gpus_per_node(cls, v, info): diff --git a/tests/README.md b/tests/README.md index 70f980f543f7..db194c36d3b6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -123,11 +123,13 @@ In file `jenkins/L0_Test.groovy`, the variables `x86TestConfigs`, `SBSATestConfi Currently the yml files are manually maintained, which requires developer to update them when new test cases are added. -### How to choose GPU type +### How to choose whether a GPU is needed and which GPU type -The CI resource of each GPU type is different. Usually you should choose the cheapest GPU that fulfills test requirements. In most cases, an integration test case should only run on one GPU type, unless it's very important or has different behaviours on different GPUs. +While TensorRT LLM is GPU-centric, the specific feature covered by a unit test may not need to interact with a GPU on its own. For unit tests that do not intrinsically require a GPU, prefer the CPU-only stages to avoid reserving unnecessary GPU resources. The CPU-only option applies only to unit tests. -The priority is A10 > A30 > L40s > A100 > H100 > B200. +When a test requires a GPU, choose the cheapest GPU that fulfills its requirements. In most cases, an integration test case should run on only one GPU type, unless it is especially important or behaves differently on different GPUs. + +The priority is A10 > A30 > L40S > A100 > H100 > B200. ## 2. Add an integration test @@ -141,6 +143,8 @@ Once a new integration test case is added, the yml files must be updated to cont A unit test are used to test a standalone feature or building block, and only runs partial workflow. +Mark a unit test that does not require a GPU to run with `@pytest.mark.cpu_only` or file-level `pytestmark = pytest.mark.cpu_only`. Only these tests run in the CPU-only stages. You can define CPU and GPU cases in the same test file. + For legacy and case management reason, the CI doesn't run unit tests directly. It uses a bridge to map multiple unit test cases into one integration test case, and manages these bridged cases. The bridge is implemented in `integration/defs/test_unittests.py` and `pytest_generate_tests` function in `tests/integration/defs/conftest.py`. @@ -161,9 +165,9 @@ pytest unittest/an_existing_file.py -m "part0 and gpu2" # run some cases in a fi ``` 2. Check existing bridge cases and make sure your cases are not covered by an existing one. -For example, you may want to add `pytest unittest/an_existing_file.py -k "some_keyword or another_keyword"`, but there is already `pytest unittest/an_existing_file.py -k "not thrid_keyword"` which covers your filter. +For example, you may want to add `pytest unittest/an_existing_file.py -k "some_keyword or another_keyword"`, but there is already `pytest unittest/an_existing_file.py -k "not third_keyword"` which covers your filter. -3. Choose a suitable GPU and add a line of your cases. For example, adding `unittest/an_existing_file.py -k "some_keyword or another_keyword"` to `tests/integration/test_lists/test-db/l0_a10.yml`. +3. Choose a suitable CI stage and add a line to the corresponding test list config. For example, adding `unittest/an_existing_file.py -k "some_keyword or another_keyword"` to `tests/integration/test_lists/test-db/l0_a10.yml`. ## 4. Run a CI stage locally diff --git a/tests/integration/defs/test_unittests.py b/tests/integration/defs/test_unittests.py index badcde1f44b3..471a6abe821a 100644 --- a/tests/integration/defs/test_unittests.py +++ b/tests/integration/defs/test_unittests.py @@ -131,6 +131,14 @@ def test_unittests_v2(llm_root, llm_venv, case: str, output_dir, request): import shlex arg_list = shlex.split(case) + if unittest_markexpr: + if "-m" in arg_list: + markexpr_index = arg_list.index("-m") + 1 + case_markexpr = arg_list[markexpr_index] + arg_list[markexpr_index] = ( + f"({case_markexpr}) and ({unittest_markexpr})") + else: + arg_list += ["-m", unittest_markexpr] case_fn = re.sub(r'[/\s"\']+', '-', case) if len(case_fn) > 80: case_fn = case_fn[:80] @@ -171,9 +179,6 @@ def test_unittests_v2(llm_root, llm_venv, case: str, output_dir, request): if run_ray: command += ["--run-ray"] - if unittest_markexpr: - command += ["-m", unittest_markexpr] - s3_secret_key = None s3_upload_path = request.config.getoption("--s3-upload-path", default=None) if s3_upload_path: From 7409d5932fb16d7aee21e5442908cac405d0abda Mon Sep 17 00:00:00 2001 From: Yuan Tong <13075180+tongyuantongyu@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:20:08 +0800 Subject: [PATCH 2/2] Extract CPU only unittests - runtime Signed-off-by: Yuan Tong <13075180+tongyuantongyu@users.noreply.github.com> --- jenkins/L0_Test.groovy | 4 +- tensorrt_llm/llmapi/llm_args.py | 3 +- .../integration/test_lists/test-db/l0_a10.yml | 22 ----- .../test_lists/test-db/l0_a100.yml | 1 - .../test_lists/test-db/l0_b200.yml | 2 - .../integration/test_lists/test-db/l0_cpu.yml | 46 ++++++++++ .../test_lists/test-db/l0_cpu_arm.yml | 19 ----- .../test_lists/test-db/l0_cpu_x86.yml | 20 ----- .../test_lists/test-db/l0_dgx_h100.yml | 2 - .../test_lists/test-db/l0_gh200.yml | 1 - .../test_lists/test-db/l0_h100.yml | 3 - .../_torch/distributed/test_cp_broadcast.py | 2 + .../_torch/distributed/test_safe_mpi_comm.py | 2 + .../_torch/executor/test_adp_router.py | 2 + .../executor/test_async_transfer_manager.py | 4 + .../executor/test_executor_request_queue.py | 2 + .../executor/test_iter_stats_populate.py | 4 + .../_torch/executor/test_model_loader_gms.py | 3 + .../_torch/executor/test_model_loader_mx.py | 19 ++++- .../test_multimodal_embedding_lengths.py | 8 ++ .../executor/test_per_layer_head_dim.py | 1 + .../_torch/executor/test_py_executor.py | 2 + ...utor_creator_flash_mla_tokens_per_block.py | 4 + ...y_executor_creator_mla_cache_reuse_sync.py | 2 + .../_torch/executor/test_py_scheduler.py | 2 + .../_torch/executor/test_request_utils.py | 2 + .../_torch/executor/test_resource_manager.py | 2 + .../_torch/executor/test_router_dealer_ipc.py | 2 + .../test_scheduler_serializable_output.py | 4 + .../_torch/executor/test_waiting_queue.py | 2 + tests/unittest/_torch/lora/test_lora.py | 2 + tests/unittest/_torch/lora/test_moe_layout.py | 3 + .../_torch/lora/test_moe_lora_extract.py | 3 + .../_torch/lora/test_moe_lora_model_path.py | 3 + .../_torch/lora/test_moe_lora_validator.py | 2 + .../_torch/modules/dwdp/test_dwdp_manager.py | 4 + .../_torch/modules/dwdp/test_dwdp_mapping.py | 11 +++ .../modules/dwdp/test_dwdp_peer_ranges.py | 4 + .../single_gpu/test_cache_transceiver_comm.py | 3 + .../_torch/speculative/hw_agnostic/test_sa.py | 2 + .../speculative/hw_agnostic/test_spec_gate.py | 3 + .../test_torch_rejection_sampling.py | 3 + tests/unittest/executor/test_base_worker.py | 1 + .../test_event_loop_error_broadcast.py | 3 - .../executor/test_fatal_error_health_check.py | 3 + tests/unittest/executor/test_ipc.py | 2 + .../executor/test_multi_frontend_routing.py | 2 - .../test_sleep_collective_rpc_guards.py | 3 + .../executor/test_stats_serializer.py | 2 + .../inputs/test_async_media_loading.py | 3 + .../inputs/test_chat_template_dispatch.py | 2 + tests/unittest/inputs/test_content_format.py | 5 ++ tests/unittest/inputs/test_multimodal.py | 2 + tests/unittest/inputs/test_url_validation.py | 19 ++++- .../inputs/test_video_data_hashing.py | 3 + tests/unittest/inputs/test_video_decode.py | 2 + tests/unittest/llmapi/apps/test_chat_utils.py | 2 + .../test_chat_utils_validator_iterator.py | 2 + .../apps/test_harmony_channel_validation.py | 13 ++- .../llmapi/apps/test_harmony_parsing.py | 14 +++- tests/unittest/llmapi/apps/test_media_io.py | 2 + ...est_openai_protocol_mm_processor_kwargs.py | 4 + .../unittest/llmapi/apps/test_tool_parsers.py | 2 + .../llmapi/test_additional_model_outputs.py | 2 + tests/unittest/llmapi/test_config_database.py | 3 + tests/unittest/llmapi/test_executor.py | 3 + .../unittest/llmapi/test_features_contract.py | 3 + tests/unittest/llmapi/test_gc_utils.py | 4 + tests/unittest/llmapi/test_gms_args.py | 3 + tests/unittest/llmapi/test_grpc.py | 8 +- .../llmapi/test_kv_cache_dtype_override.py | 4 +- tests/unittest/llmapi/test_llm_args.py | 84 +++++++++++++++++-- tests/unittest/llmapi/test_llm_quant.py | 22 +++++ tests/unittest/llmapi/test_llm_telemetry.py | 2 + tests/unittest/llmapi/test_llm_utils.py | 3 + tests/unittest/llmapi/test_mpi_session.py | 11 ++- tests/unittest/llmapi/test_mx_args.py | 3 + .../unittest/llmapi/test_reasoning_parser.py | 2 + .../unittest/llmapi/test_request_priority.py | 3 + tests/unittest/llmapi/test_sampling_params.py | 2 + tests/unittest/llmapi/test_serialization.py | 3 + .../llmapi/test_tokenizer_multinode.py | 2 + tests/unittest/llmapi/test_utils.py | 4 + 83 files changed, 403 insertions(+), 94 deletions(-) create mode 100644 tests/integration/test_lists/test-db/l0_cpu.yml delete mode 100644 tests/integration/test_lists/test-db/l0_cpu_arm.yml delete mode 100644 tests/integration/test_lists/test-db/l0_cpu_x86.yml diff --git a/jenkins/L0_Test.groovy b/jenkins/L0_Test.groovy index ca96b6ffb557..daa0a1049efa 100644 --- a/jenkins/L0_Test.groovy +++ b/jenkins/L0_Test.groovy @@ -5075,7 +5075,7 @@ def launchTestJobs(pipeline, testFilter) // may break the mapping functionality. x86TestConfigs = [ - "CPU-Generic-x86-1": ["cpu", "l0_cpu_x86", 1, 1], + "CPU-Generic-x86-1": ["cpu", "l0_cpu", 1, 1], "DGX_H100-4_GPUs-CPP-1": ["dgx-h100-x4", "l0_dgx_h100", 1, 1, 4], "A10-PyTorch-1": ["a10", "l0_a10", 1, 3], "A10-PyTorch-2": ["a10", "l0_a10", 2, 3], @@ -5257,7 +5257,7 @@ def launchTestJobs(pipeline, testFilter) // SBSA machines from the Blossom machine pool SBSATestConfigs = [ - "CPU-Generic-arm-1": ["cpu", "l0_cpu_arm", 1, 1], + "CPU-Generic-arm-1": ["cpu", "l0_cpu", 1, 1], "GH200-PyTorch-Post-Merge-1": ["gh200", "l0_gh200", 1, 1], // DGX Spark is also named as GB10 Grace Blackwell Superchip. "GB10-PyTorch-1": ["gb10x", "l0_gb10", 1, 1], diff --git a/tensorrt_llm/llmapi/llm_args.py b/tensorrt_llm/llmapi/llm_args.py index f200fda9bd64..a614f13b6528 100644 --- a/tensorrt_llm/llmapi/llm_args.py +++ b/tensorrt_llm/llmapi/llm_args.py @@ -4368,8 +4368,7 @@ class BaseLlmArgs(StrictBaseModel): "(default), it is read from the HF config.json ('dtype', or the " "deprecated 'torch_dtype'); for composite/VLM configs it falls " "back to the nested text_config.dtype. Defaults to bfloat16 if " - "none is found, and is overridden to float16 on GPUs with compute " - "capability < 8.0 (pre-Ampere).", + "none is found.", telemetry=TelemetryField.categorical("auto", "float16", "bfloat16", "float32")) diff --git a/tests/integration/test_lists/test-db/l0_a10.yml b/tests/integration/test_lists/test-db/l0_a10.yml index aca92f01364c..4fb53f01ab54 100644 --- a/tests/integration/test_lists/test-db/l0_a10.yml +++ b/tests/integration/test_lists/test-db/l0_a10.yml @@ -34,8 +34,6 @@ l0_a10: - unittest/_torch/modeling/test_multimodal_encoder_mixin.py - unittest/_torch/sampler/test_trtllm_sampler.py - unittest/_torch/sampler/test_token_ban.py - - unittest/_torch/executor/test_async_transfer_manager.py - - unittest/_torch/executor/test_scheduler_serializable_output.py - unittest/_torch/executor/test_kv_cache_estimation.py - unittest/_torch/executor/test_kv_cache_budget_split.py - unittest/_torch/executor/test_kv_pool_rebalance.py @@ -44,21 +42,13 @@ l0_a10: - unittest/_torch/executor/test_kv_cache_v2_capacity_only.py - unittest/_torch/executor/test_error_classification.py - unittest/_torch/modules/dwdp/test_dwdp_fixup_moe_backends.py - - unittest/_torch/modules/dwdp/test_dwdp_manager.py - - unittest/_torch/modules/dwdp/test_dwdp_mapping.py - - unittest/_torch/modules/dwdp/test_dwdp_peer_ranges.py - unittest/_torch/modules/moe/test_communication_factory.py # NOTE: this is a CPU-only test, but we do not have a dedicated job for this (and therefore no # test list either). - unittest/_torch/models/checkpoints - unittest/_torch/models/test_qwen3_next_moe_quant.py - unittest/_torch/weight_sharing - - unittest/inputs/test_chat_template_dispatch.py - - unittest/inputs/test_content_format.py - - unittest/inputs/test_url_validation.py - - unittest/inputs/test_multimodal.py - unittest/inputs/test_multimodal_input_processor.py - - unittest/inputs/test_video_decode.py - unittest/others/test_cache_transceiver_precheck_config.py - unittest/others/test_cache_transceiver_precheck_run.py - unittest/others/test_convert_utils.py @@ -145,26 +135,14 @@ l0_a10: - unittest/_torch/visual_gen/test_qwen_image_infer.py - unittest/_torch/visual_gen/test_qwen_image_pipeline.py # llmapi - - unittest/llmapi/test_llm_utils.py - - unittest/llmapi/test_gc_utils.py - - unittest/llmapi/test_reasoning_parser.py - - unittest/llmapi/test_serialization.py - - unittest/llmapi/test_utils.py - unittest/llmapi/test_rlhf_utils.py - unittest/llmapi/test_llm_args.py - - unittest/llmapi/test_kv_cache_dtype_override.py - unittest/llmapi/test_additional_model_outputs.py -m "gpu1" - - unittest/llmapi/test_request_priority.py # executor - - unittest/executor/test_ipc.py - - unittest/executor/test_fatal_error_health_check.py - unittest/executor/test_postprocessor_hook.py - unittest/executor/test_proxy_postproc_terminate.py - unittest/executor/test_proxy_fast_death.py # trtllm-serve CPU-only - - unittest/llmapi/apps/test_chat_utils.py - - unittest/llmapi/apps/test_tool_parsers.py - - unittest/llmapi/apps/test_harmony_channel_validation.py - unittest/llmapi/apps/test_encode_batcher.py - unittest/llmapi/test_embedding_arch_routing.py # usage telemetry diff --git a/tests/integration/test_lists/test-db/l0_a100.yml b/tests/integration/test_lists/test-db/l0_a100.yml index fe9ec737f01c..66f7bcfa2a3f 100644 --- a/tests/integration/test_lists/test-db/l0_a100.yml +++ b/tests/integration/test_lists/test-db/l0_a100.yml @@ -56,7 +56,6 @@ l0_a100: tests: - unittest/llmapi/test_llm.py -m "part0" - unittest/llmapi/test_llm.py -m "not part0" TIMEOUT (90) - - unittest/llmapi/test_executor.py - condition: ranges: system_gpu_count: diff --git a/tests/integration/test_lists/test-db/l0_b200.yml b/tests/integration/test_lists/test-db/l0_b200.yml index bcacc3e90bbc..1bb0243f12ca 100644 --- a/tests/integration/test_lists/test-db/l0_b200.yml +++ b/tests/integration/test_lists/test-db/l0_b200.yml @@ -181,7 +181,6 @@ l0_b200: - kv_cache/test_kv_cache_v2_scheduler.py::TestKVCacheV2LoRA::test_lora_chunked_prefill - kv_cache/test_kv_cache_v2_scheduler.py::TestKVCacheV2LoRA::test_lora_eviction # ------------- KV Cache Iteration Stats --------------- - - unittest/executor/test_stats_serializer.py - unittest/metrics/test_collector.py - kv_cache/test_kv_cache_iteration_stats.py::TestKvCacheIterationStats::test_cold_start - kv_cache/test_kv_cache_iteration_stats.py::TestKvCacheIterationStats::test_partial_block_reuse @@ -269,7 +268,6 @@ l0_b200: stage: post_merge backend: pytorch tests: - - unittest/llmapi/test_llm_quant.py # 3.5 mins on B200 - unittest/disaggregated/test_openai_server_info.py - examples/visual_gen/test_visual_gen.py::test_cosmos3_t2i_4step_example TIMEOUT (30) - examples/visual_gen/test_visual_gen.py::test_cosmos3_i2v_4step_example TIMEOUT (45) diff --git a/tests/integration/test_lists/test-db/l0_cpu.yml b/tests/integration/test_lists/test-db/l0_cpu.yml new file mode 100644 index 000000000000..3bb62ffd3b89 --- /dev/null +++ b/tests/integration/test_lists/test-db/l0_cpu.yml @@ -0,0 +1,46 @@ +version: 0.0.1 +l0_cpu: +- condition: + ranges: + system_gpu_count: + gte: 0 + lte: 0 + wildcards: + linux_distribution_name: ubuntu* + terms: + stage: pre_merge + backend: generic + orchestrator: mpi + tests: + - unittest/_torch/distributed + - unittest/_torch/executor + - unittest/_torch/lora + - unittest/_torch/modules + - unittest/_torch/ray_orchestrator/single_gpu/test_cache_transceiver_comm.py + - unittest/_torch/speculative/hw_agnostic + - unittest/executor/test_base_worker.py ISOLATION + - unittest/executor/test_fatal_error_health_check.py + - unittest/executor/test_ipc.py + - unittest/executor/test_rpc.py + - unittest/executor/test_multi_frontend_routing.py + - unittest/executor/test_event_loop_error_broadcast.py + - unittest/executor/test_stats_serializer.py + - unittest/inputs + - unittest/llmapi/apps/test_chat_utils.py + - unittest/llmapi/apps/test_harmony_channel_validation.py + - unittest/llmapi/apps/test_tool_parsers.py + - unittest/llmapi/test_bench_async.py + - unittest/llmapi/test_additional_model_outputs.py -m "gpu1" + - unittest/llmapi/test_executor.py + - unittest/llmapi/test_gc_utils.py + - unittest/llmapi/test_kv_cache_dtype_override.py + - unittest/llmapi/test_llm_args.py + - unittest/llmapi/test_llm_quant.py + - unittest/llmapi/test_llm_telemetry.py + - unittest/llmapi/test_llm_utils.py + - unittest/llmapi/test_mpi_session.py ISOLATION + - unittest/llmapi/test_reasoning_parser.py + - unittest/llmapi/test_request_priority.py + - unittest/llmapi/test_serialization.py + - unittest/llmapi/test_utils.py + - unittest/others/test_http_utils_fail_fast.py diff --git a/tests/integration/test_lists/test-db/l0_cpu_arm.yml b/tests/integration/test_lists/test-db/l0_cpu_arm.yml deleted file mode 100644 index 9e21ccf8b5fc..000000000000 --- a/tests/integration/test_lists/test-db/l0_cpu_arm.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: 0.0.1 -l0_cpu_arm: -- condition: - ranges: - system_gpu_count: - gte: 0 - lte: 0 - wildcards: - linux_distribution_name: ubuntu* - cpu: aarch64 - terms: - stage: pre_merge - backend: generic - orchestrator: mpi - tests: - - unittest/executor/test_rpc.py - - unittest/executor/test_event_loop_error_broadcast.py - - unittest/others/test_http_utils_fail_fast.py - - unittest/llmapi/test_bench_async.py diff --git a/tests/integration/test_lists/test-db/l0_cpu_x86.yml b/tests/integration/test_lists/test-db/l0_cpu_x86.yml deleted file mode 100644 index 76d344b77adb..000000000000 --- a/tests/integration/test_lists/test-db/l0_cpu_x86.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: 0.0.1 -l0_cpu_x86: -- condition: - ranges: - system_gpu_count: - gte: 0 - lte: 0 - wildcards: - linux_distribution_name: ubuntu* - cpu: x86_64 - terms: - stage: pre_merge - backend: generic - orchestrator: mpi - tests: - - unittest/executor/test_rpc.py - - unittest/others/test_http_utils_fail_fast.py - - unittest/executor/test_multi_frontend_routing.py - - unittest/executor/test_event_loop_error_broadcast.py - - unittest/llmapi/test_bench_async.py diff --git a/tests/integration/test_lists/test-db/l0_dgx_h100.yml b/tests/integration/test_lists/test-db/l0_dgx_h100.yml index 3cda7d22ca40..ff1642f5e1e4 100644 --- a/tests/integration/test_lists/test-db/l0_dgx_h100.yml +++ b/tests/integration/test_lists/test-db/l0_dgx_h100.yml @@ -18,7 +18,6 @@ l0_dgx_h100: - unittest/llmapi/test_llm_multi_gpu_pytorch.py -m "gpu2" - unittest/llmapi/test_additional_model_outputs.py -m "gpu2" - unittest/_torch/multi_gpu -m "not post_merge" TIMEOUT (90) - - unittest/_torch/distributed - unittest/_torch/modeling/test_modeling_pixtral.py::test_tensor_parallelism - kv_cache/test_final_single_token_context_cuda_graph.py::test_final_token_reuse_cuda_graph_tp2[v1] - kv_cache/test_final_single_token_context_cuda_graph.py::test_final_token_reuse_cuda_graph_tp2[v2] @@ -63,7 +62,6 @@ l0_dgx_h100: - unittest/llmapi/apps/test_disagg_serving_perf_metrics.py - disaggregated/test_disaggregated.py::test_disaggregated_cancel_large_context_requests[DeepSeek-V3-Lite-bf16] # llmapi - - unittest/llmapi/test_mpi_session.py::test_llmapi_launch_multiple_tasks - accuracy/test_disaggregated_serving.py::TestLlama3_1_8BInstruct::test_gen_only_spec_dec - condition: ranges: diff --git a/tests/integration/test_lists/test-db/l0_gh200.yml b/tests/integration/test_lists/test-db/l0_gh200.yml index 6cf8033c2d6b..f9fa3873cd89 100644 --- a/tests/integration/test_lists/test-db/l0_gh200.yml +++ b/tests/integration/test_lists/test-db/l0_gh200.yml @@ -15,7 +15,6 @@ l0_gh200: backend: pytorch tests: - unittest/bindings - - unittest/llmapi/test_llm_quant.py - llmapi/test_llm_examples.py::test_llmapi_quickstart_atexit - examples/visual_gen/test_visual_gen.py::test_visual_gen_quickstart - examples/visual_gen/test_visual_gen.py::test_visual_gen_api_walkthrough diff --git a/tests/integration/test_lists/test-db/l0_h100.yml b/tests/integration/test_lists/test-db/l0_h100.yml index ac3903686689..9c7d152e3fc2 100644 --- a/tests/integration/test_lists/test-db/l0_h100.yml +++ b/tests/integration/test_lists/test-db/l0_h100.yml @@ -268,7 +268,6 @@ l0_h100: - disaggregated/test_disaggregated_single_gpu.py::test_disaggregated_logprobs[False-TinyLlama-1.1B-Chat-v1.0] - disaggregated/test_disaggregated_single_gpu.py::test_disaggregated_logprobs[True-TinyLlama-1.1B-Chat-v1.0] - unittest/_torch/executor/test_overlap_scheduler.py - - unittest/_torch/ray_orchestrator/single_gpu/test_cache_transceiver_comm.py - unittest/_torch/ray_orchestrator/single_gpu/test_llm_sleep.py - unittest/_torch/ray_orchestrator/single_gpu/test_llm_update_weights.py -m "part0" - unittest/_torch/ray_orchestrator/single_gpu/test_llm_update_weights.py -m "part1" @@ -310,7 +309,6 @@ l0_h100: stage: pre_merge backend: pytorch tests: - - unittest/llmapi/test_llm_quant.py # 5.5 mins on H100 - examples/visual_gen/test_visual_gen.py::test_visual_gen_quickstart - examples/visual_gen/test_visual_gen.py::test_visual_gen_api_walkthrough - condition: @@ -364,7 +362,6 @@ l0_h100: - unittest/bindings # 8 mins on H100 - unittest/kv_cache_manager_v2_tests # 4 min # ------------- KV Cache Iteration Stats --------------- - - unittest/executor/test_stats_serializer.py - unittest/metrics/test_collector.py - kv_cache/test_kv_cache_iteration_stats.py::TestKvCacheIterationStats::test_cold_start - kv_cache/test_kv_cache_iteration_stats.py::TestKvCacheIterationStats::test_partial_block_reuse diff --git a/tests/unittest/_torch/distributed/test_cp_broadcast.py b/tests/unittest/_torch/distributed/test_cp_broadcast.py index d5ed278726e7..265738efe225 100644 --- a/tests/unittest/_torch/distributed/test_cp_broadcast.py +++ b/tests/unittest/_torch/distributed/test_cp_broadcast.py @@ -16,6 +16,8 @@ from tensorrt_llm._torch.distributed import MPIDist from tensorrt_llm.mapping import Mapping +pytestmark = pytest.mark.cpu_only + def get_mpi_info(): """Get MPI rank and world size, returns (0, 1) if MPI is not available.""" diff --git a/tests/unittest/_torch/distributed/test_safe_mpi_comm.py b/tests/unittest/_torch/distributed/test_safe_mpi_comm.py index c7d0aac293bb..233ba977de4d 100644 --- a/tests/unittest/_torch/distributed/test_safe_mpi_comm.py +++ b/tests/unittest/_torch/distributed/test_safe_mpi_comm.py @@ -34,6 +34,8 @@ from tensorrt_llm._torch.distributed import communicator from tensorrt_llm.bindings import BuildInfo +pytestmark = pytest.mark.cpu_only + def get_mpi_info(): """Get MPI rank and world size, returns (0, 1) if MPI is not available.""" diff --git a/tests/unittest/_torch/executor/test_adp_router.py b/tests/unittest/_torch/executor/test_adp_router.py index f59a3c3b233a..2768766ffb28 100644 --- a/tests/unittest/_torch/executor/test_adp_router.py +++ b/tests/unittest/_torch/executor/test_adp_router.py @@ -26,6 +26,8 @@ from tensorrt_llm.conversation_params import ConversationParams from tensorrt_llm.scheduling_params import SchedulingParams +pytestmark = pytest.mark.cpu_only + class _MockRequest(MagicMock): """Mock executor Request whose ``num_input_tokens`` mirrors the real binding diff --git a/tests/unittest/_torch/executor/test_async_transfer_manager.py b/tests/unittest/_torch/executor/test_async_transfer_manager.py index 1f2f9013d903..bed0dcc3f8f0 100644 --- a/tests/unittest/_torch/executor/test_async_transfer_manager.py +++ b/tests/unittest/_torch/executor/test_async_transfer_manager.py @@ -15,10 +15,14 @@ from unittest.mock import MagicMock +import pytest + from tensorrt_llm._torch.pyexecutor.py_executor import AsyncTransferManager from tensorrt_llm._torch.pyexecutor.resource_manager import ResourceManagerType from tensorrt_llm.bindings import LlmRequestState +pytestmark = pytest.mark.cpu_only + def create_mock_request(request_id: int): """Create a mock LlmRequest with the given request ID.""" diff --git a/tests/unittest/_torch/executor/test_executor_request_queue.py b/tests/unittest/_torch/executor/test_executor_request_queue.py index 6874d578f6fb..6befb8907c7f 100644 --- a/tests/unittest/_torch/executor/test_executor_request_queue.py +++ b/tests/unittest/_torch/executor/test_executor_request_queue.py @@ -18,6 +18,8 @@ from tensorrt_llm._torch.pyexecutor.executor_request_queue import ( SHUTDOWN_REQUEST_ID, ExecutorRequestQueue, RequestQueueItem) +pytestmark = pytest.mark.cpu_only + @pytest.fixture def mock_dist(): diff --git a/tests/unittest/_torch/executor/test_iter_stats_populate.py b/tests/unittest/_torch/executor/test_iter_stats_populate.py index 1aad42de9499..9f0087da354f 100644 --- a/tests/unittest/_torch/executor/test_iter_stats_populate.py +++ b/tests/unittest/_torch/executor/test_iter_stats_populate.py @@ -41,6 +41,8 @@ import types from unittest.mock import MagicMock, patch +import pytest + from tensorrt_llm._torch.pyexecutor.adp_iter_stats import ( _ITERATION_STATS_OPTIONAL_FIELDS, _ITERATION_STATS_SCALAR_FIELDS, @@ -49,6 +51,8 @@ from tensorrt_llm._torch.pyexecutor.scheduler.adp_router import RankIterStatsPayload, RankState from tensorrt_llm.bindings.executor import InflightBatchingStats, IterationStats, SpecDecodingStats +pytestmark = pytest.mark.cpu_only + class _StubRequest: """Stub LlmRequest exposing only the accessors ``_update_iter_stats`` reads. diff --git a/tests/unittest/_torch/executor/test_model_loader_gms.py b/tests/unittest/_torch/executor/test_model_loader_gms.py index 5b20f4f81653..3d073d95bf90 100644 --- a/tests/unittest/_torch/executor/test_model_loader_gms.py +++ b/tests/unittest/_torch/executor/test_model_loader_gms.py @@ -26,6 +26,9 @@ ) from tensorrt_llm.llmapi.llm_args import LoadFormat +pytestmark = pytest.mark.cpu_only + + _SOURCE_IDENTITY = model_loader_mod.SourceIdentity( format_version=SOURCE_IDENTITY_FORMAT_VERSION, artifact_identity=ArtifactIdentity( diff --git a/tests/unittest/_torch/executor/test_model_loader_mx.py b/tests/unittest/_torch/executor/test_model_loader_mx.py index 907d754cfae5..25686a15063e 100644 --- a/tests/unittest/_torch/executor/test_model_loader_mx.py +++ b/tests/unittest/_torch/executor/test_model_loader_mx.py @@ -272,6 +272,7 @@ def _build_source_identity(_cls, *_args, **kwargs): return loader +@pytest.mark.cpu_only def test_construct_checkpoint_loader_passes_mx_config(): mx_config = SimpleNamespace( server_url="http://mx:8001", @@ -292,6 +293,7 @@ def test_construct_checkpoint_loader_passes_mx_config(): assert checkpoint_loader.model_name == "Qwen/Qwen2.5-7B-Instruct" +@pytest.mark.cpu_only def test_public_support_table_matches_qualified_profile_registry() -> None: profiles = ModelLoader._POST_TRANSFORM_PROFILE_REGISTRY.profiles documentation = (Path(__file__).parents[4] / "docs/source/features/model-express.md").read_text( @@ -320,6 +322,7 @@ def test_public_support_table_matches_qualified_profile_registry() -> None: assert any(row.startswith(expected_row_prefix) for row in table_rows) +@pytest.mark.cpu_only def test_mx_success_initializes_mapper_skips_weight_mapping_and_reload_works( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -365,6 +368,7 @@ def test_mx_success_initializes_mapper_skips_weight_mapping_and_reload_works( assert events == ["post_load_weights", "load_weights"] +@pytest.mark.cpu_only def test_reload_partial_loading_preserves_weights_transformed_flags(monkeypatch): events = [] loader = _make_loader(monkeypatch, events=events) @@ -382,6 +386,7 @@ def test_reload_partial_loading_preserves_weights_transformed_flags(monkeypatch) assert events == ["load_weights"] +@pytest.mark.cpu_only def test_mx_partial_fallback_merges_returned_weights( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -447,6 +452,7 @@ def _load_weights(self, *_args: object, **_kwargs: object) -> dict[str, object]: return {} +@pytest.mark.cpu_only def test_mx_post_transform_receiver_uses_staged_path_when_qualified( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -502,6 +508,7 @@ def test_default_profile_qualifies_real_tiny_llama_lifecycle( assert_post_transform_lifecycle_equivalent(case) +@pytest.mark.cpu_only def test_separate_draft_model_is_not_qualified_by_target_only_profile( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -522,6 +529,7 @@ def test_separate_draft_model_is_not_qualified_by_target_only_profile( assert decision.unsupported_features == frozenset({PostTransformFeature.SEPARATE_DRAFT_MODEL}) +@pytest.mark.cpu_only def test_one_engine_speculative_mode_is_not_qualified_by_target_only_profile( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -542,12 +550,12 @@ def test_one_engine_speculative_mode_is_not_qualified_by_target_only_profile( assert decision.unsupported_features == frozenset() +@pytest.mark.cpu_only def test_speculative_mode_name_is_canonical_and_fails_closed( monkeypatch: pytest.MonkeyPatch, ) -> None: warning = MagicMock() monkeypatch.setattr(model_loader_mod.logger, "warning", warning) - assert ModelLoader._speculative_mode_name(None) is None warning.assert_not_called() assert ( @@ -568,6 +576,7 @@ def test_speculative_mode_name_is_canonical_and_fails_closed( ) +@pytest.mark.cpu_only def test_mx_post_transform_receiver_falls_back_for_unqualified_model( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -623,6 +632,7 @@ def test_load_qualifies_with_preconstruction_identity_after_model_normalization( assert loader._source_identity.transform_abi_id == LLAMA_POST_TRANSFORM_LAYOUT_ABI_V1 +@pytest.mark.cpu_only def test_mx_rejects_post_transform_preload_after_failed_qualification( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -640,6 +650,7 @@ def test_mx_rejects_post_transform_preload_after_failed_qualification( checkpoint_loader.post_load_publish.assert_not_called() +@pytest.mark.cpu_only def test_mx_fallback_runs_standard_weight_mapping( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -755,6 +766,7 @@ def __init__(self, events): self.removed_child = _HookRecorder("removed_child", events, removed=True) +@pytest.mark.cpu_only def test_staged_hook_setup_aliases_walks_skip_removed_modules(): events = [] model = _HookModel(events) @@ -768,6 +780,7 @@ def test_staged_hook_setup_aliases_walks_skip_removed_modules(): ] +@pytest.mark.cpu_only def test_staged_hook_walks_skip_removed_and_transformed_modules(): events = [] model = _HookModel(events) @@ -788,6 +801,7 @@ def test_staged_hook_walks_skip_removed_and_transformed_modules(): ] +@pytest.mark.cpu_only def test_reset_weights_transformed_only_resets_existing_flags(): events = [] model = _HookModel(events) @@ -802,6 +816,7 @@ def test_reset_weights_transformed_only_resets_existing_flags(): assert not hasattr(model.removed_child, "_weights_transformed") +@pytest.mark.cpu_only def test_mark_weights_transformed_only_sets_existing_flags(): events = [] model = _HookModel(events) @@ -816,6 +831,7 @@ def test_mark_weights_transformed_only_sets_existing_flags(): assert not hasattr(model.removed_child, "_weights_transformed") +@pytest.mark.cpu_only def test_linear_transform_weights_is_idempotent(): linear = Linear( 1, @@ -841,6 +857,7 @@ def test_linear_transform_weights_is_idempotent(): assert linear._weights_transformed is True +@pytest.mark.cpu_only def test_mla_transform_weights_is_idempotent(monkeypatch): monkeypatch.setattr(mla_mod, "get_sm_version", lambda: 120) quant_mode = SimpleNamespace(has_fp8_block_scales=lambda: True) diff --git a/tests/unittest/_torch/executor/test_multimodal_embedding_lengths.py b/tests/unittest/_torch/executor/test_multimodal_embedding_lengths.py index e4c445acef53..1898562674b7 100644 --- a/tests/unittest/_torch/executor/test_multimodal_embedding_lengths.py +++ b/tests/unittest/_torch/executor/test_multimodal_embedding_lengths.py @@ -43,6 +43,7 @@ ), ], ) +@pytest.mark.cpu_only def test_multimodal_embedding_lengths_returns_top_level_metadata(req, expected): """Getter reads top-level lengths and ignores layout metadata.""" assert get_multimodal_embedding_lengths(req) == expected @@ -103,6 +104,7 @@ def test_multimodal_embedding_lengths_returns_top_level_metadata(req, expected): ), ], ) +@pytest.mark.cpu_only def test_multimodal_embedding_lengths_rejects_invalid_metadata(req, exception, match): """Bad length metadata is rejected by the getter.""" with pytest.raises(exception, match=match): @@ -132,6 +134,7 @@ def set_finished_reason(self, reason, beam): self.finished_reason = (reason, beam) +@pytest.mark.cpu_only def test_mm_encoder_sampler_aligns_mixed_batch_by_request_index(): """Sparse MM encoder outputs attach to the original request index.""" text_request = _FakeRequest() @@ -165,6 +168,7 @@ def test_mm_encoder_sampler_aligns_mixed_batch_by_request_index(): assert mm_request.py_result.mrope_position == ("mm-pos", "mm-delta") +@pytest.mark.cpu_only def test_py_result_mm_embedding_handles_use_shared_tensor_handles(): """MM encoder result handles should preserve the producer tensor device.""" result = PyResult(prompt_len=1, max_new_tokens=1) @@ -202,6 +206,7 @@ def num_context_requests(self): return len(self.context_requests) +@pytest.mark.cpu_only def test_mm_encoder_sampler_builds_typed_result_from_model_outputs(): """Sampler converts raw model-output dicts into typed MM results.""" sampler = EarlyStopWithMMResult() @@ -228,6 +233,7 @@ def test_mm_encoder_sampler_builds_typed_result_from_model_outputs(): } +@pytest.mark.cpu_only def test_mm_encoder_sampler_rejects_typed_result_batch_mismatch(): """MM embedding arrays must stay length-aligned with request indices.""" sampler = EarlyStopWithMMResult() @@ -244,6 +250,7 @@ def test_mm_encoder_sampler_rejects_typed_result_batch_mismatch(): ) +@pytest.mark.cpu_only def test_mm_encoder_sampler_rejects_invalid_request_index(): """MM encoder output cannot target a request outside the scheduled batch.""" sampler = EarlyStopWithMMResult() @@ -260,6 +267,7 @@ def test_mm_encoder_sampler_rejects_invalid_request_index(): ) +@pytest.mark.cpu_only def test_multimodal_result_rejects_embedding_shape_mismatch(): """Per-item lengths must sum to the attached embedding rows.""" with pytest.raises(ValueError, match="shape mismatch"): diff --git a/tests/unittest/_torch/executor/test_per_layer_head_dim.py b/tests/unittest/_torch/executor/test_per_layer_head_dim.py index 022c812a653e..ba39d7520220 100644 --- a/tests/unittest/_torch/executor/test_per_layer_head_dim.py +++ b/tests/unittest/_torch/executor/test_per_layer_head_dim.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. + import gc import unittest diff --git a/tests/unittest/_torch/executor/test_py_executor.py b/tests/unittest/_torch/executor/test_py_executor.py index 50f33d321c2f..c9c05b39137f 100644 --- a/tests/unittest/_torch/executor/test_py_executor.py +++ b/tests/unittest/_torch/executor/test_py_executor.py @@ -34,6 +34,8 @@ SerializableSchedulerOutput, ) +pytestmark = pytest.mark.cpu_only + class MockPyExecutor: """A mock PyExecutor class for testing request handling logic. diff --git a/tests/unittest/_torch/executor/test_py_executor_creator_flash_mla_tokens_per_block.py b/tests/unittest/_torch/executor/test_py_executor_creator_flash_mla_tokens_per_block.py index e25d216f9985..4084bd903639 100644 --- a/tests/unittest/_torch/executor/test_py_executor_creator_flash_mla_tokens_per_block.py +++ b/tests/unittest/_torch/executor/test_py_executor_creator_flash_mla_tokens_per_block.py @@ -34,8 +34,12 @@ import inspect import re +import pytest + from tensorrt_llm._torch.pyexecutor import py_executor_creator +pytestmark = pytest.mark.cpu_only + def _get_create_py_executor_source() -> str: return inspect.getsource(py_executor_creator.create_py_executor) diff --git a/tests/unittest/_torch/executor/test_py_executor_creator_mla_cache_reuse_sync.py b/tests/unittest/_torch/executor/test_py_executor_creator_mla_cache_reuse_sync.py index d878a50c3c24..214544293ff9 100644 --- a/tests/unittest/_torch/executor/test_py_executor_creator_mla_cache_reuse_sync.py +++ b/tests/unittest/_torch/executor/test_py_executor_creator_mla_cache_reuse_sync.py @@ -26,6 +26,8 @@ from tensorrt_llm.llmapi.llm_args import CacheTransceiverConfig, ContextChunkingPolicy from tensorrt_llm.quantization import QuantAlgo +pytestmark = pytest.mark.cpu_only + class _DummyCalibrator: """Mock calibrator for testing that bypasses actual calibration logic.""" diff --git a/tests/unittest/_torch/executor/test_py_scheduler.py b/tests/unittest/_torch/executor/test_py_scheduler.py index 81a813a43796..c2c9fe61b4a5 100644 --- a/tests/unittest/_torch/executor/test_py_scheduler.py +++ b/tests/unittest/_torch/executor/test_py_scheduler.py @@ -41,6 +41,8 @@ ) from tensorrt_llm.llmapi.llm_args import CapacitySchedulerPolicy +pytestmark = pytest.mark.cpu_only + @dataclass class MockPrefixReuseSummary: diff --git a/tests/unittest/_torch/executor/test_request_utils.py b/tests/unittest/_torch/executor/test_request_utils.py index f2d6d6bc605b..0c098fda90a7 100644 --- a/tests/unittest/_torch/executor/test_request_utils.py +++ b/tests/unittest/_torch/executor/test_request_utils.py @@ -30,6 +30,8 @@ from tensorrt_llm.conversation_params import ConversationParams from tensorrt_llm.mapping import CpType +pytestmark = pytest.mark.cpu_only + @pytest.fixture def attention_dp_config(): diff --git a/tests/unittest/_torch/executor/test_resource_manager.py b/tests/unittest/_torch/executor/test_resource_manager.py index b4888797eb42..9a230973a85a 100644 --- a/tests/unittest/_torch/executor/test_resource_manager.py +++ b/tests/unittest/_torch/executor/test_resource_manager.py @@ -10,6 +10,7 @@ from unittest.mock import MagicMock, patch import numpy as np +import pytest import torch import tensorrt_llm @@ -1047,6 +1048,7 @@ def test_peft_cache_manager_with_execution_stream(self): self.assertTrue(peft_cache_manager.impl.enabled) +@pytest.mark.cpu_only class TestKVCacheManagerConfigForwarding(unittest.TestCase): def test_secondary_offload_min_priority_forwarded_to_cpp_manager(self): diff --git a/tests/unittest/_torch/executor/test_router_dealer_ipc.py b/tests/unittest/_torch/executor/test_router_dealer_ipc.py index a6895d487730..28f17fb72175 100644 --- a/tests/unittest/_torch/executor/test_router_dealer_ipc.py +++ b/tests/unittest/_torch/executor/test_router_dealer_ipc.py @@ -8,6 +8,8 @@ from tensorrt_llm.executor.ipc import ZeroMqQueue +pytestmark = pytest.mark.cpu_only + @contextmanager def router_dealer_pair(use_hmac_encryption=True, diff --git a/tests/unittest/_torch/executor/test_scheduler_serializable_output.py b/tests/unittest/_torch/executor/test_scheduler_serializable_output.py index 5f0763869487..904123518dce 100644 --- a/tests/unittest/_torch/executor/test_scheduler_serializable_output.py +++ b/tests/unittest/_torch/executor/test_scheduler_serializable_output.py @@ -1,8 +1,12 @@ import pickle +import pytest + from tensorrt_llm._torch.pyexecutor.llm_request import LlmRequest, SamplingConfig from tensorrt_llm._torch.pyexecutor.scheduler import ScheduledRequests, SerializableSchedulerOutput +pytestmark = pytest.mark.cpu_only + def _make_request(request_id: int) -> LlmRequest: return LlmRequest( diff --git a/tests/unittest/_torch/executor/test_waiting_queue.py b/tests/unittest/_torch/executor/test_waiting_queue.py index b1f33f235ac2..27bcb5eb9187 100644 --- a/tests/unittest/_torch/executor/test_waiting_queue.py +++ b/tests/unittest/_torch/executor/test_waiting_queue.py @@ -21,6 +21,8 @@ from tensorrt_llm.executor.request import DEFAULT_REQUEST_PRIORITY from tensorrt_llm.llmapi.llm_args import WaitingQueuePolicy +pytestmark = pytest.mark.cpu_only + def create_mock_request_item(request_id: int) -> RequestQueueItem: """Create a mock RequestQueueItem for testing.""" diff --git a/tests/unittest/_torch/lora/test_lora.py b/tests/unittest/_torch/lora/test_lora.py index 394da82b4d18..420ff8cd8a9c 100644 --- a/tests/unittest/_torch/lora/test_lora.py +++ b/tests/unittest/_torch/lora/test_lora.py @@ -1,3 +1,4 @@ +import pytest import torch from tensorrt_llm._torch.peft.lora.adapter_slot_manager import AdapterSlotManager @@ -27,6 +28,7 @@ def test_cuda_graph_lora_params_handle_missing_peft_table(): assert layer_params.h_b_prime_ptrs[:, 1].tolist() == [0, 0] +@pytest.mark.cpu_only def test_adapter_slot_manager_handles_missing_peft_cache_manager(): manager = AdapterSlotManager(max_num_adapters=2) manager.slot2task[0] = 123 diff --git a/tests/unittest/_torch/lora/test_moe_layout.py b/tests/unittest/_torch/lora/test_moe_layout.py index d81c9305507f..a98cc008df52 100644 --- a/tests/unittest/_torch/lora/test_moe_layout.py +++ b/tests/unittest/_torch/lora/test_moe_layout.py @@ -6,6 +6,7 @@ end-to-end multi-LoRA tests will use. """ +import pytest import torch from tensorrt_llm._torch.peft.lora.moe_layout import ( @@ -14,6 +15,8 @@ reference_moe_lora_delta, ) +pytestmark = pytest.mark.cpu_only + def test_module_list_complete(): # Sanity check: the canonical module names match the validator's set. diff --git a/tests/unittest/_torch/lora/test_moe_lora_extract.py b/tests/unittest/_torch/lora/test_moe_lora_extract.py index f0f4c67924b5..26abb8bb9f90 100644 --- a/tests/unittest/_torch/lora/test_moe_lora_extract.py +++ b/tests/unittest/_torch/lora/test_moe_lora_extract.py @@ -17,6 +17,9 @@ import pytest import torch +pytestmark = pytest.mark.cpu_only + + # These imports are pure-Python; skip cleanly if the package layout changes. fused_moe_cutlass = pytest.importorskip("tensorrt_llm._torch.modules.fused_moe.fused_moe_cutlass") lora_layer = pytest.importorskip("tensorrt_llm._torch.peft.lora.layer") diff --git a/tests/unittest/_torch/lora/test_moe_lora_model_path.py b/tests/unittest/_torch/lora/test_moe_lora_model_path.py index e69abd2c278f..1900cc48118a 100644 --- a/tests/unittest/_torch/lora/test_moe_lora_model_path.py +++ b/tests/unittest/_torch/lora/test_moe_lora_model_path.py @@ -29,6 +29,9 @@ from tensorrt_llm._torch.modules.fused_moe.moe_scheduler import ExternalCommMoEScheduler from tensorrt_llm._torch.peft.lora.layer import LoraModuleType +pytestmark = pytest.mark.cpu_only + + # A unique sentinel so the assertions can verify object identity rather than # mere truthiness; any drop/replace along the chain fails the identity check. _LORA_PARAMS_SENTINEL = {"num_seqs": 1, "_marker": object()} diff --git a/tests/unittest/_torch/lora/test_moe_lora_validator.py b/tests/unittest/_torch/lora/test_moe_lora_validator.py index 872be2f9c4b9..993f65a67aa8 100644 --- a/tests/unittest/_torch/lora/test_moe_lora_validator.py +++ b/tests/unittest/_torch/lora/test_moe_lora_validator.py @@ -11,6 +11,8 @@ ) from tensorrt_llm.quantization.mode import QuantMode +pytestmark = pytest.mark.cpu_only + class _FakeLoraConfig: """Minimal stand-in for `LoraConfig` for tests that don't need pydantic.""" diff --git a/tests/unittest/_torch/modules/dwdp/test_dwdp_manager.py b/tests/unittest/_torch/modules/dwdp/test_dwdp_manager.py index 5bbab2dd59d3..bb4141cc17fb 100644 --- a/tests/unittest/_torch/modules/dwdp/test_dwdp_manager.py +++ b/tests/unittest/_torch/modules/dwdp/test_dwdp_manager.py @@ -26,6 +26,8 @@ import unittest from unittest.mock import MagicMock, patch +import pytest + from tensorrt_llm._torch.distributed import MPIDist from tensorrt_llm._torch.pyexecutor.dwdp import ( DwdpManager, @@ -35,6 +37,8 @@ from tensorrt_llm.llmapi.llm_args import DwdpConfig from tensorrt_llm.mapping import Mapping +pytestmark = pytest.mark.cpu_only + def _make_config(dwdp_size: int = 2) -> DwdpConfig: return DwdpConfig( diff --git a/tests/unittest/_torch/modules/dwdp/test_dwdp_mapping.py b/tests/unittest/_torch/modules/dwdp/test_dwdp_mapping.py index ee1ec2094133..f7ba7942f80b 100644 --- a/tests/unittest/_torch/modules/dwdp/test_dwdp_mapping.py +++ b/tests/unittest/_torch/modules/dwdp/test_dwdp_mapping.py @@ -27,9 +27,20 @@ """ import unittest +from unittest.mock import patch + +import pytest from tensorrt_llm.mapping import Mapping +pytestmark = pytest.mark.cpu_only + + +@pytest.fixture(autouse=True) +def _force_mpi_topology_mapping(): + with patch("tensorrt_llm.mapping.mpi_disabled", return_value=False): + yield + class TestMappingDwdp(unittest.TestCase): # ------------------------------------------------------------------ diff --git a/tests/unittest/_torch/modules/dwdp/test_dwdp_peer_ranges.py b/tests/unittest/_torch/modules/dwdp/test_dwdp_peer_ranges.py index 4bd77cc6b678..411b68c471d0 100644 --- a/tests/unittest/_torch/modules/dwdp/test_dwdp_peer_ranges.py +++ b/tests/unittest/_torch/modules/dwdp/test_dwdp_peer_ranges.py @@ -33,8 +33,12 @@ import unittest +import pytest + from tensorrt_llm._torch.modules.dwdp.specs import compute_peer_ranges, lookup_owner +pytestmark = pytest.mark.cpu_only + class TestComputePeerRanges(unittest.TestCase): def test_uniform_dwdp4_matches_floor_div(self): diff --git a/tests/unittest/_torch/ray_orchestrator/single_gpu/test_cache_transceiver_comm.py b/tests/unittest/_torch/ray_orchestrator/single_gpu/test_cache_transceiver_comm.py index d06bee9bd94f..c62ae50553f0 100644 --- a/tests/unittest/_torch/ray_orchestrator/single_gpu/test_cache_transceiver_comm.py +++ b/tests/unittest/_torch/ray_orchestrator/single_gpu/test_cache_transceiver_comm.py @@ -2,12 +2,15 @@ import os import unittest +import pytest import torch import torch.distributed as dist import torch.multiprocessing as mp from tensorrt_llm._utils import get_free_port, torch_pybind11_abi +pytestmark = pytest.mark.cpu_only + class TestCacheTransceiverComm(unittest.TestCase): diff --git a/tests/unittest/_torch/speculative/hw_agnostic/test_sa.py b/tests/unittest/_torch/speculative/hw_agnostic/test_sa.py index 1486f49d0a72..bd3f54683d5c 100644 --- a/tests/unittest/_torch/speculative/hw_agnostic/test_sa.py +++ b/tests/unittest/_torch/speculative/hw_agnostic/test_sa.py @@ -214,6 +214,7 @@ def test_llama_sa( torch.cuda.synchronize() +@pytest.mark.cpu_only @pytest.mark.parametrize("max_matching_ngram_size", [2, 4, -1]) def test_sa_config_validation(max_matching_ngram_size: int): """Test SADecodingConfig validation.""" @@ -225,6 +226,7 @@ def test_sa_config_validation(max_matching_ngram_size: int): assert config.max_matching_ngram_size == max_matching_ngram_size +@pytest.mark.cpu_only def test_sa_config_invalid_zero(): """Test that max_matching_ngram_size=0 raises error for SA.""" with pytest.raises(ValueError, match="max_matching_ngram_size must be"): diff --git a/tests/unittest/_torch/speculative/hw_agnostic/test_spec_gate.py b/tests/unittest/_torch/speculative/hw_agnostic/test_spec_gate.py index cb0001e0b718..f32b4d5d3872 100644 --- a/tests/unittest/_torch/speculative/hw_agnostic/test_spec_gate.py +++ b/tests/unittest/_torch/speculative/hw_agnostic/test_spec_gate.py @@ -144,6 +144,7 @@ def mock_record_acceptance_rate(self, acceptance_rate, sample_id=None): llm_spec.shutdown() +@pytest.mark.cpu_only def test_returns_none_until_window_and_enabled_when_above_threshold(): gate = SpeculationGate(window=3, threshold=0.5) @@ -161,6 +162,7 @@ def test_returns_none_until_window_and_enabled_when_above_threshold(): assert gate.disabled is False +@pytest.mark.cpu_only def test_disables_when_avg_below_threshold_and_stays_disabled(): gate = SpeculationGate(window=3, threshold=0.3) @@ -182,6 +184,7 @@ def test_disables_when_avg_below_threshold_and_stays_disabled(): assert gate.disabled is True +@pytest.mark.cpu_only def test_rolling_window_and_disable_on_drop(): gate = SpeculationGate(window=3, threshold=0.7) diff --git a/tests/unittest/_torch/speculative/hw_agnostic/test_torch_rejection_sampling.py b/tests/unittest/_torch/speculative/hw_agnostic/test_torch_rejection_sampling.py index 7258a939dac2..cccd1b89ad09 100644 --- a/tests/unittest/_torch/speculative/hw_agnostic/test_torch_rejection_sampling.py +++ b/tests/unittest/_torch/speculative/hw_agnostic/test_torch_rejection_sampling.py @@ -2,11 +2,14 @@ from typing import cast import numpy as np +import pytest import torch from scipy.stats import entropy from tensorrt_llm._torch.pyexecutor.sampler import get_rejected_indices, sample_rejected +pytestmark = pytest.mark.cpu_only + def test_get_rejected_indices(): vocab_size = 500 diff --git a/tests/unittest/executor/test_base_worker.py b/tests/unittest/executor/test_base_worker.py index 7bee69ef87f9..8b7ed5de9f6b 100644 --- a/tests/unittest/executor/test_base_worker.py +++ b/tests/unittest/executor/test_base_worker.py @@ -24,6 +24,7 @@ model_path = llm_models_root() / default_model_name +@pytest.mark.cpu_only def test_enqueue_request_wraps_lora_load_error(): class LoraManager: diff --git a/tests/unittest/executor/test_event_loop_error_broadcast.py b/tests/unittest/executor/test_event_loop_error_broadcast.py index df8db031cd9e..f5b72d22e3f6 100644 --- a/tests/unittest/executor/test_event_loop_error_broadcast.py +++ b/tests/unittest/executor/test_event_loop_error_broadcast.py @@ -18,9 +18,6 @@ from tensorrt_llm.executor.base_worker import AwaitResponseHelper from tensorrt_llm.executor.utils import ErrorResponse -# CI's CPU stages select tests with ``-m "cpu_only and not disabled"``; without -# this marker the whole file is deselected and pytest exits 5 (no tests ran), -# which the runner reports as a failure. These are pure stub-based unit tests. pytestmark = pytest.mark.cpu_only diff --git a/tests/unittest/executor/test_fatal_error_health_check.py b/tests/unittest/executor/test_fatal_error_health_check.py index 63d31c10ad23..3f8f7396a6ff 100644 --- a/tests/unittest/executor/test_fatal_error_health_check.py +++ b/tests/unittest/executor/test_fatal_error_health_check.py @@ -41,6 +41,9 @@ import pytest +pytestmark = pytest.mark.cpu_only + + logger = logging.getLogger(__name__) _mod_path = ( diff --git a/tests/unittest/executor/test_ipc.py b/tests/unittest/executor/test_ipc.py index d618c3f07c7f..d21dd03bea3a 100644 --- a/tests/unittest/executor/test_ipc.py +++ b/tests/unittest/executor/test_ipc.py @@ -7,6 +7,8 @@ from tensorrt_llm.executor.ipc import ZeroMqQueue +pytestmark = pytest.mark.cpu_only + class TestIpcBasics: """Test basic synchronous IPC operations.""" diff --git a/tests/unittest/executor/test_multi_frontend_routing.py b/tests/unittest/executor/test_multi_frontend_routing.py index d34aa62d8dde..44d55cf81408 100644 --- a/tests/unittest/executor/test_multi_frontend_routing.py +++ b/tests/unittest/executor/test_multi_frontend_routing.py @@ -35,8 +35,6 @@ namespace_client_id, ) -# The CI CPU stages collect with -m "cpu_only and not disabled" and skip -# files that don't mention pytest.mark.cpu_only (see unittest/conftest.py). pytestmark = pytest.mark.cpu_only diff --git a/tests/unittest/executor/test_sleep_collective_rpc_guards.py b/tests/unittest/executor/test_sleep_collective_rpc_guards.py index 08b5aca0ed62..4576f88ede6c 100644 --- a/tests/unittest/executor/test_sleep_collective_rpc_guards.py +++ b/tests/unittest/executor/test_sleep_collective_rpc_guards.py @@ -23,6 +23,9 @@ import pytest +pytestmark = pytest.mark.cpu_only + + # Sentinel used as the default sleep_config value in _make_worker so that # callers who omit sleep_config get a truthy non-None object (simulating a # configured SleepConfig), while callers who pass sleep_config=None test the diff --git a/tests/unittest/executor/test_stats_serializer.py b/tests/unittest/executor/test_stats_serializer.py index b051947966ae..df38f3232421 100644 --- a/tests/unittest/executor/test_stats_serializer.py +++ b/tests/unittest/executor/test_stats_serializer.py @@ -29,6 +29,8 @@ ) from tensorrt_llm.executor.base_worker import BaseWorker +pytestmark = pytest.mark.cpu_only + def _make_mock_iteration_stats(kv_cache_stats_json=None): """Create a mock IterationStats object with to_json_str().""" diff --git a/tests/unittest/inputs/test_async_media_loading.py b/tests/unittest/inputs/test_async_media_loading.py index ea3ef84c70aa..c97c9cc04128 100644 --- a/tests/unittest/inputs/test_async_media_loading.py +++ b/tests/unittest/inputs/test_async_media_loading.py @@ -27,6 +27,9 @@ from tensorrt_llm.inputs.media_io import _get_aiohttp_session from tensorrt_llm.inputs.utils import MultimodalDataTracker, async_load_audio, async_load_image +pytestmark = pytest.mark.cpu_only + + # ────────────────────────────────────────────────────────────── # Helpers # ────────────────────────────────────────────────────────────── diff --git a/tests/unittest/inputs/test_chat_template_dispatch.py b/tests/unittest/inputs/test_chat_template_dispatch.py index 694b0c6f2163..9bb730b45655 100644 --- a/tests/unittest/inputs/test_chat_template_dispatch.py +++ b/tests/unittest/inputs/test_chat_template_dispatch.py @@ -21,6 +21,8 @@ interleave_mm_placeholders, ) +pytestmark = pytest.mark.cpu_only + @pytest.fixture(autouse=True, scope="module") def _register_test_models(): diff --git a/tests/unittest/inputs/test_content_format.py b/tests/unittest/inputs/test_content_format.py index fa2a2a23c8c1..b48844f7715c 100644 --- a/tests/unittest/inputs/test_content_format.py +++ b/tests/unittest/inputs/test_content_format.py @@ -2,8 +2,13 @@ # Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. """Tests for content format detection via Jinja AST analysis.""" +import pytest + from tensorrt_llm.inputs.content_format import ContentFormat, detect_content_format +pytestmark = pytest.mark.cpu_only + + # A template that iterates over message['content'] and checks content['type'] # (OpenAI-style multimodal template) OPENAI_TEMPLATE = """\ diff --git a/tests/unittest/inputs/test_multimodal.py b/tests/unittest/inputs/test_multimodal.py index d7d36e19fff4..b9026e52d44d 100644 --- a/tests/unittest/inputs/test_multimodal.py +++ b/tests/unittest/inputs/test_multimodal.py @@ -19,6 +19,8 @@ maybe_compute_mm_embed_cumsum, ) +pytestmark = pytest.mark.cpu_only + def test_maybe_compute_mm_embed_cumsum_populates_py_multimodal_data(): """Producer writes a flat int64 cumsum tensor at py_multimodal_data[multimodal_embed_mask_cumsum].""" diff --git a/tests/unittest/inputs/test_url_validation.py b/tests/unittest/inputs/test_url_validation.py index 758ab79b5918..d5d6ff5b9d35 100644 --- a/tests/unittest/inputs/test_url_validation.py +++ b/tests/unittest/inputs/test_url_validation.py @@ -1,3 +1,17 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Unit tests for SSRF-prevention URL validation helpers in inputs/media_io.py. Tests cover _validate_url(), _safe_request_get(), and _safe_aiohttp_get() @@ -17,6 +31,9 @@ _validate_url, ) +pytestmark = pytest.mark.cpu_only + + # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -246,7 +263,7 @@ def test_rejects_private_url_before_request(self): class TestSafeAiohttpGet: def _run(self, coro): - return asyncio.get_event_loop().run_until_complete(coro) + return asyncio.run(coro) @patch("tensorrt_llm.inputs.media_io.socket.getaddrinfo", return_value=PUBLIC_DNS) def test_reads_response_chunks_to_eof(self, _): diff --git a/tests/unittest/inputs/test_video_data_hashing.py b/tests/unittest/inputs/test_video_data_hashing.py index 2ee0aba55d7f..2dc2591fe599 100644 --- a/tests/unittest/inputs/test_video_data_hashing.py +++ b/tests/unittest/inputs/test_video_data_hashing.py @@ -9,12 +9,15 @@ Plus sampling-metadata and audio contributions to cache identity. """ +import pytest import torch from blake3 import blake3 from tensorrt_llm.inputs import multimodal_data as md from tensorrt_llm.inputs.multimodal_data import AudioData, VideoData +pytestmark = pytest.mark.cpu_only + def _hex(video: VideoData) -> str: h = blake3() diff --git a/tests/unittest/inputs/test_video_decode.py b/tests/unittest/inputs/test_video_decode.py index fc900f53f380..d2d113645c7a 100644 --- a/tests/unittest/inputs/test_video_decode.py +++ b/tests/unittest/inputs/test_video_decode.py @@ -16,6 +16,8 @@ from tensorrt_llm.inputs.media_io import _load_video_by_cv2 # noqa: E402 +pytestmark = pytest.mark.cpu_only + @pytest.fixture(scope="module") def sample_video_path(tmp_path_factory: pytest.TempPathFactory) -> str: diff --git a/tests/unittest/llmapi/apps/test_chat_utils.py b/tests/unittest/llmapi/apps/test_chat_utils.py index bed19c4575c9..12b51422784b 100644 --- a/tests/unittest/llmapi/apps/test_chat_utils.py +++ b/tests/unittest/llmapi/apps/test_chat_utils.py @@ -16,6 +16,8 @@ parse_chat_messages_coroutines, ) +pytestmark = pytest.mark.cpu_only + @pytest.fixture def mock_mm_data_tracker(): diff --git a/tests/unittest/llmapi/apps/test_chat_utils_validator_iterator.py b/tests/unittest/llmapi/apps/test_chat_utils_validator_iterator.py index 1e248df5197f..72310ce48db6 100644 --- a/tests/unittest/llmapi/apps/test_chat_utils_validator_iterator.py +++ b/tests/unittest/llmapi/apps/test_chat_utils_validator_iterator.py @@ -23,6 +23,8 @@ from tensorrt_llm.serve.chat_utils import parse_chat_message_content, parse_chat_messages_coroutines +pytestmark = pytest.mark.cpu_only + class SingleUseIterator: """Mimics Pydantic v2 ValidatorIterator: yields items once, then empty.""" diff --git a/tests/unittest/llmapi/apps/test_harmony_channel_validation.py b/tests/unittest/llmapi/apps/test_harmony_channel_validation.py index bb16cb4bc974..d693ba74b184 100644 --- a/tests/unittest/llmapi/apps/test_harmony_channel_validation.py +++ b/tests/unittest/llmapi/apps/test_harmony_channel_validation.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os from unittest.mock import patch import pytest +from utils.llm_data import llm_datasets_root # Try to import the private function for direct testing try: @@ -28,6 +30,15 @@ # Always import the public API from tensorrt_llm.serve.harmony_adapter import HarmonyAdapter +pytestmark = pytest.mark.cpu_only + + +@pytest.fixture(autouse=True) +def _tiktoken_local_cache(monkeypatch): + cache_dir = os.path.join(llm_datasets_root(), "tiktoken_vocab") + monkeypatch.setenv("TIKTOKEN_RS_CACHE_DIR", cache_dir) + monkeypatch.setenv("TIKTOKEN_ENCODINGS_BASE", cache_dir) + @pytest.mark.skipif( _check_channel_valid is None, diff --git a/tests/unittest/llmapi/apps/test_harmony_parsing.py b/tests/unittest/llmapi/apps/test_harmony_parsing.py index 8238bc3643f8..8689454a9e14 100644 --- a/tests/unittest/llmapi/apps/test_harmony_parsing.py +++ b/tests/unittest/llmapi/apps/test_harmony_parsing.py @@ -23,9 +23,11 @@ """ import json +import os from unittest.mock import Mock, patch import pytest +from utils.llm_data import llm_datasets_root try: from tensorrt_llm.serve.harmony_adapter import ( @@ -47,7 +49,17 @@ except (ImportError, ModuleNotFoundError): _harmony_available = False -pytestmark = pytest.mark.skipif(not _harmony_available, reason="harmony_adapter not importable") +pytestmark = [ + pytest.mark.cpu_only, + pytest.mark.skipif(not _harmony_available, reason="harmony_adapter not importable"), +] + + +@pytest.fixture(autouse=True) +def _tiktoken_local_cache(monkeypatch): + cache_dir = os.path.join(llm_datasets_root(), "tiktoken_vocab") + monkeypatch.setenv("TIKTOKEN_RS_CACHE_DIR", cache_dir) + monkeypatch.setenv("TIKTOKEN_ENCODINGS_BASE", cache_dir) # --------------------------------------------------------------------------- diff --git a/tests/unittest/llmapi/apps/test_media_io.py b/tests/unittest/llmapi/apps/test_media_io.py index 92521971f3b1..d139ea2cd4a6 100644 --- a/tests/unittest/llmapi/apps/test_media_io.py +++ b/tests/unittest/llmapi/apps/test_media_io.py @@ -8,6 +8,8 @@ from tensorrt_llm.inputs.media_io import AudioMediaIO, BaseMediaIO, ImageMediaIO, VideoMediaIO from tensorrt_llm.serve.chat_utils import parse_chat_message_content_part +pytestmark = pytest.mark.cpu_only + class CustomError(Exception): pass diff --git a/tests/unittest/llmapi/apps/test_openai_protocol_mm_processor_kwargs.py b/tests/unittest/llmapi/apps/test_openai_protocol_mm_processor_kwargs.py index 559050b5c363..5712469942ea 100644 --- a/tests/unittest/llmapi/apps/test_openai_protocol_mm_processor_kwargs.py +++ b/tests/unittest/llmapi/apps/test_openai_protocol_mm_processor_kwargs.py @@ -20,8 +20,12 @@ dispatch, so the request schema is the authoritative contract. """ +import pytest + from tensorrt_llm.serve.openai_protocol import ChatCompletionRequest +pytestmark = pytest.mark.cpu_only + def _base_request(**extra): return { diff --git a/tests/unittest/llmapi/apps/test_tool_parsers.py b/tests/unittest/llmapi/apps/test_tool_parsers.py index a4ef1beeb69b..f551ed534d93 100644 --- a/tests/unittest/llmapi/apps/test_tool_parsers.py +++ b/tests/unittest/llmapi/apps/test_tool_parsers.py @@ -46,6 +46,8 @@ _parse_gemma4_value, ) +pytestmark = pytest.mark.cpu_only + # Test fixtures for common tools @pytest.fixture diff --git a/tests/unittest/llmapi/test_additional_model_outputs.py b/tests/unittest/llmapi/test_additional_model_outputs.py index c0e51c95e8ac..d83ad7f9a9df 100644 --- a/tests/unittest/llmapi/test_additional_model_outputs.py +++ b/tests/unittest/llmapi/test_additional_model_outputs.py @@ -135,6 +135,7 @@ def load(self, checkpoint_dir: str, **kwargs) -> ModelConfig: return ModelConfig(pretrained_config=DummyConfig()) +@pytest.mark.cpu_only @pytest.mark.gpu1 def test_additional_model_outputs_sampling_params(): """Test that additional_model_outputs can be configured in SamplingParams.""" @@ -153,6 +154,7 @@ def test_additional_model_outputs_sampling_params(): assert sampling_params.additional_model_outputs[1] == "generation_output" +@pytest.mark.cpu_only @pytest.mark.gpu1 def test_additional_model_outputs_no_outputs(): """Test that no additional outputs are returned when not requested.""" diff --git a/tests/unittest/llmapi/test_config_database.py b/tests/unittest/llmapi/test_config_database.py index 009982a14f3c..3c15db7e410d 100644 --- a/tests/unittest/llmapi/test_config_database.py +++ b/tests/unittest/llmapi/test_config_database.py @@ -37,6 +37,9 @@ validate_torch_llm_args_config, ) +pytestmark = pytest.mark.cpu_only + + CONFIG_ROOT = Path(__file__).parents[3] / "examples" / "configs" REPO_ROOT = CONFIG_ROOT.parent.parent CURATED_DIR = CONFIG_ROOT / "curated" diff --git a/tests/unittest/llmapi/test_executor.py b/tests/unittest/llmapi/test_executor.py index dce923c6211c..5aaa3433794a 100644 --- a/tests/unittest/llmapi/test_executor.py +++ b/tests/unittest/llmapi/test_executor.py @@ -24,6 +24,9 @@ # isort: off from utils.llm_data import llm_models_root + +pytestmark = pytest.mark.cpu_only + # isort: on WORLD_SIZE = mpi_world_size() diff --git a/tests/unittest/llmapi/test_features_contract.py b/tests/unittest/llmapi/test_features_contract.py index 4323de3460ee..55d519e23c68 100644 --- a/tests/unittest/llmapi/test_features_contract.py +++ b/tests/unittest/llmapi/test_features_contract.py @@ -25,6 +25,9 @@ from tensorrt_llm.llmapi import llm_args from tensorrt_llm.usage import usage_lib +pytestmark = pytest.mark.cpu_only + + _STABILITY_DIR = Path(__file__).resolve().parents[1] / "api_stability" _COMMITTED_YAML = _STABILITY_DIR / "references_committed" / "llm.yaml" _REFERENCE_YAML = _STABILITY_DIR / "references" / "llm.yaml" diff --git a/tests/unittest/llmapi/test_gc_utils.py b/tests/unittest/llmapi/test_gc_utils.py index f75483f08d7b..d15f5dc6c5cb 100644 --- a/tests/unittest/llmapi/test_gc_utils.py +++ b/tests/unittest/llmapi/test_gc_utils.py @@ -3,9 +3,13 @@ import sys import unittest +import pytest + sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..") from gc_utils import assert_resource_freed +pytestmark = pytest.mark.cpu_only + # A global list to simulate a leak LEAKY_HOLD = [] diff --git a/tests/unittest/llmapi/test_gms_args.py b/tests/unittest/llmapi/test_gms_args.py index cc51955d270e..d2f3e162b12e 100644 --- a/tests/unittest/llmapi/test_gms_args.py +++ b/tests/unittest/llmapi/test_gms_args.py @@ -14,6 +14,9 @@ from tensorrt_llm.llmapi.llm_args import LoadFormat, TorchLlmArgs +pytestmark = pytest.mark.cpu_only + + _LOGGER_PATH = "tensorrt_llm.llmapi.llm_args.logger" _DUMMY_MODEL = "/tmp/test-gms-args-nonexistent" diff --git a/tests/unittest/llmapi/test_grpc.py b/tests/unittest/llmapi/test_grpc.py index f66bb0af519d..cf69a99a0514 100644 --- a/tests/unittest/llmapi/test_grpc.py +++ b/tests/unittest/llmapi/test_grpc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,6 +46,7 @@ pytestmark = pytest.mark.threadleak(enabled=False) +@pytest.mark.cpu_only class TestSamplingParamsConversion: """Tests for proto to SamplingParams conversion.""" @@ -167,6 +168,7 @@ def test_guided_decoding_regex(self): assert params.guided_decoding.regex is not None +@pytest.mark.cpu_only class TestLoraRequestConversion: """Tests for proto to LoRARequest conversion.""" @@ -185,6 +187,7 @@ def test_none_lora_config(self): assert request is None +@pytest.mark.cpu_only class TestDisaggregatedParamsConversion: """Tests for proto to DisaggregatedParams conversion.""" @@ -245,6 +248,7 @@ def test_none_params(self): assert params is None +@pytest.mark.cpu_only class TestProtoMessages: """Tests for proto message structure.""" @@ -369,6 +373,7 @@ def test_abort_messages(self): # ============================================================================ +@pytest.mark.cpu_only class TestComprehensiveSamplingParamsConversion: """Comprehensive test covering all proto fields for SamplingParams conversion. @@ -581,6 +586,7 @@ def test_guided_decoding_all_types(self): # ============================================================================ +@pytest.mark.cpu_only class TestGenerateValidation: """Test that invalid gRPC requests return INVALID_ARGUMENT status. diff --git a/tests/unittest/llmapi/test_kv_cache_dtype_override.py b/tests/unittest/llmapi/test_kv_cache_dtype_override.py index b5fed337c769..8049d034d568 100644 --- a/tests/unittest/llmapi/test_kv_cache_dtype_override.py +++ b/tests/unittest/llmapi/test_kv_cache_dtype_override.py @@ -8,6 +8,8 @@ from tensorrt_llm.llmapi.llm_utils import ModelLoader from tensorrt_llm.models.modeling_utils import QuantAlgo, QuantConfig +pytestmark = pytest.mark.cpu_only + def _write_hf_quant_config(model_dir, kv_cache_quant_algo: str = "FP8"): with open(model_dir / "hf_quant_config.json", "w") as f: @@ -44,7 +46,7 @@ def _compressed_tensors_nvfp4_config(**overrides): def test_get_llm_args_plumbs_kv_cache_dtype(): - llm_args, _ = get_llm_args(model="dummy", kv_cache_dtype="nvfp4") + llm_args, _ = get_llm_args(model="dummy", kv_cache_dtype="nvfp4", gpus_per_node=1) assert llm_args["kv_cache_config"].dtype == "nvfp4" diff --git a/tests/unittest/llmapi/test_llm_args.py b/tests/unittest/llmapi/test_llm_args.py index c5bdfb6a6a9e..a4e278efc2d2 100644 --- a/tests/unittest/llmapi/test_llm_args.py +++ b/tests/unittest/llmapi/test_llm_args.py @@ -8,6 +8,7 @@ from enum import Enum from pathlib import Path from typing import Annotated, Any, ClassVar, Literal, get_args, get_origin +from unittest.mock import patch import pydantic_core import pytest @@ -15,7 +16,6 @@ import yaml from pydantic import BaseModel, TypeAdapter, ValidationError from utils.llm_data import llm_models_root -from utils.util import force_ampere import tensorrt_llm.bindings.executor as tle import tensorrt_llm.llmapi as public_llmapi @@ -68,6 +68,7 @@ from .test_llm import llama_model_path +@pytest.mark.cpu_only def test_LookaheadDecodingConfig(): # from constructor config = LookaheadDecodingConfig(max_window_size=4, @@ -95,6 +96,7 @@ def test_LookaheadDecodingConfig(): assert pybind_config.max_verification_set_size == 4 +@pytest.mark.cpu_only def test_MTPDecodingConfig_default_draft_len_is_not_user_set(): config = MTPDecodingConfig() @@ -110,6 +112,7 @@ def test_MTPDecodingConfig_default_draft_len_is_not_user_set(): assert "max_draft_len" in explicit_config.model_fields_set +@pytest.mark.cpu_only def test_rejection_sampling_allows_attention_dp(monkeypatch): """ADP (incl. ADP+LM-head-TP) supports rejection sampling. @@ -141,6 +144,7 @@ def test_rejection_sampling_allows_attention_dp(monkeypatch): assert args.speculative_config.use_rejection_sampling is True +@pytest.mark.cpu_only def test_rejection_sampling_still_gated_on_context_parallel(): """Context parallelism remains an unsupported rejection combination. @@ -157,6 +161,7 @@ def test_rejection_sampling_still_gated_on_context_parallel(): speculative_config=spec_cfg) +@pytest.mark.cpu_only class TestYaml: def _yaml_to_dict(self, yaml_content: str) -> dict: @@ -238,6 +243,7 @@ def test_llm_args_with_model_kwargs(self, llm_args_cls): assert llm_args.model_kwargs['num_hidden_layers'] == 2 +@pytest.mark.cpu_only @pytest.mark.parametrize("llm_args_cls", [TorchLlmArgs]) class TestEncoderRuntimeSizes: """Cover encoder runtime size fields and fallback to LLM limits. @@ -295,6 +301,7 @@ def test_rejects_non_positive(self, llm_args_cls, field_name, llm_args_cls(model=llama_model_path, **{field_name: invalid_value}) +@pytest.mark.cpu_only def test_decoding_type_eagle3_parses_to_eagle3_decoding_config(): adapter = TypeAdapter(SpeculativeConfig) spec_cfg = adapter.validate_python( @@ -304,6 +311,7 @@ def test_decoding_type_eagle3_parses_to_eagle3_decoding_config(): assert isinstance(spec_cfg, Eagle3DecodingConfig) +@pytest.mark.cpu_only def test_decoding_type_eagle_warns_on_pytorch_backend(monkeypatch): warnings_seen: list[str] = [] @@ -323,6 +331,7 @@ def _capture_warning(msg, *args, **kwargs): for m in warnings_seen) +@pytest.mark.cpu_only def test_dspark_block_size_resolved_from_checkpoint(tmp_path): (tmp_path / "config.json").write_text('{"dspark_block_size": 5}') spec_cfg = DSparkDecodingConfig(max_draft_len=5, @@ -337,6 +346,7 @@ def test_dspark_block_size_resolved_from_checkpoint(tmp_path): assert args.speculative_config.block_size == 5 +@pytest.mark.cpu_only def test_dspark_block_size_must_match_max_draft_len(tmp_path): (tmp_path / "config.json").write_text('{"dspark_block_size": 4}') spec_cfg = DSparkDecodingConfig(max_draft_len=5, @@ -350,6 +360,7 @@ def test_dspark_block_size_must_match_max_draft_len(tmp_path): ) +@pytest.mark.cpu_only def test_dspark_target_layer_ids_resolved_from_checkpoint(tmp_path): # When the user leaves target_layer_ids unset, the checkpoint's ordered # dspark_target_layer_ids must be adopted verbatim. @@ -368,6 +379,7 @@ def test_dspark_target_layer_ids_resolved_from_checkpoint(tmp_path): assert args.speculative_config.target_layer_ids == [3, 1, 2] +@pytest.mark.cpu_only def test_dspark_target_layer_ids_matching_override_accepted(tmp_path): # An explicit override that matches the checkpoint list exactly is fine. (tmp_path / "config.json").write_text( @@ -385,6 +397,7 @@ def test_dspark_target_layer_ids_matching_override_accepted(tmp_path): assert args.speculative_config.target_layer_ids == [1, 2, 3] +@pytest.mark.cpu_only def test_dspark_target_layer_ids_mismatched_count_rejected(tmp_path): # A different number of layers would mismatch main_proj.in_features. (tmp_path / "config.json").write_text( @@ -401,6 +414,7 @@ def test_dspark_target_layer_ids_mismatched_count_rejected(tmp_path): ) +@pytest.mark.cpu_only def test_dspark_target_layer_ids_same_count_different_layers_rejected(tmp_path): # Same count but different layers: shapes line up, but the draft would see # hidden states it was not trained on, so this must be rejected too. @@ -418,6 +432,7 @@ def test_dspark_target_layer_ids_same_count_different_layers_rejected(tmp_path): ) +@pytest.mark.cpu_only def test_dspark_target_layer_ids_order_mismatch_rejected(tmp_path): # Same set but different order: projection columns are order-dependent, so a # reordered override must be rejected rather than silently accepted. @@ -435,6 +450,7 @@ def test_dspark_target_layer_ids_order_mismatch_rejected(tmp_path): ) +@pytest.mark.cpu_only def test_dspark_requires_speculative_model(): # The DSpark draft weights live in the checkpoint's mtp.* namespace, so an # unset speculative_model must fail fast at config validation instead of @@ -450,6 +466,7 @@ def test_dspark_requires_speculative_model(): ) +@pytest.mark.cpu_only def test_dspark_requires_positive_max_draft_len(tmp_path): (tmp_path / "config.json").write_text('{"dspark_block_size": 5}') spec_cfg = DSparkDecodingConfig(speculative_model=str(tmp_path)) @@ -462,6 +479,7 @@ def test_dspark_requires_positive_max_draft_len(tmp_path): ) +@pytest.mark.cpu_only def test_post_processor_hook_rejected_with_skip_tokenizer_init(): """post_processor_hook + skip_tokenizer_init must fail fast. @@ -476,6 +494,7 @@ def test_post_processor_hook_rejected_with_skip_tokenizer_init(): TorchLlmArgs(model="/tmp/dummy_model", skip_tokenizer_init=True) +@pytest.mark.cpu_only class TestModelDefaults: """Test suite for model-specific default overrides functionality.""" @@ -660,6 +679,7 @@ def get_model_defaults(cls, llm_args): assert "enable_block_reuse" in error_str or "max_tokens" in error_str +@pytest.mark.cpu_only def test_KvCacheConfig_declaration(): assert KvCacheConfig().mamba_state_cache_interval is None assert KvCacheConfig().mamba_state_config.periodic_snapshot_interval == 0 @@ -745,6 +765,7 @@ def test_KvCacheConfig_declaration(): KvCacheConfig(block_reuse_policy="invalid") +@pytest.mark.cpu_only def test_MambaStateConfig_defaults_use_independent_lists(): first = MambaStateConfig() second = MambaStateConfig() @@ -758,11 +779,13 @@ def test_MambaStateConfig_defaults_use_independent_lists(): assert public_llmapi.MambaStateConfig is MambaStateConfig +@pytest.mark.cpu_only def test_MambaStateConfig_rejects_unknown_fields(): with pytest.raises(ValidationError, match="extra_forbidden"): MambaStateConfig(unknown_snapshot_policy=1) +@pytest.mark.cpu_only @pytest.mark.parametrize( ("field", "value"), [ @@ -778,6 +801,7 @@ def test_MambaStateConfig_rejects_invalid_snapshot_offsets(field, value): MambaStateConfig(**{field: value}) +@pytest.mark.cpu_only @pytest.mark.parametrize( ("field", "offsets"), [ @@ -802,6 +826,7 @@ def test_KvCacheConfig_requires_v2_for_additional_snapshot_offsets( assert getattr(config.mamba_state_config, field) == offsets +@pytest.mark.cpu_only def test_KvCacheConfig_migrates_deprecated_mamba_interval(monkeypatch): warnings_seen = [] monkeypatch.setattr(llm_args_mod.logger, "warning", @@ -816,6 +841,7 @@ def test_KvCacheConfig_migrates_deprecated_mamba_interval(monkeypatch): assert "mamba_state_cache_interval" not in config.model_dump() +@pytest.mark.cpu_only def test_KvCacheConfig_warns_when_disabling_periodic_conversation_snapshots( monkeypatch): warnings_seen = [] @@ -842,6 +868,7 @@ def test_KvCacheConfig_warns_when_disabling_periodic_conversation_snapshots( assert warnings_seen == [] +@pytest.mark.cpu_only def test_update_llm_args_with_empty_options_file(tmp_path): yaml_path = tmp_path / "empty.yaml" yaml_path.write_text("", encoding="utf-8") @@ -851,6 +878,7 @@ def test_update_llm_args_with_empty_options_file(tmp_path): str(yaml_path)) == llm_args +@pytest.mark.cpu_only def test_config_file_merge_migrates_legacy_mamba_interval_without_mutating_input( ): yaml_dict = { @@ -873,6 +901,7 @@ def test_config_file_merge_migrates_legacy_mamba_interval_without_mutating_input assert yaml_dict["kv_cache_config"]["mamba_state_cache_interval"] == 64 +@pytest.mark.cpu_only def test_config_file_merge_rejects_legacy_and_new_mamba_intervals(): with pytest.raises(ValueError, match="Cannot set both"): update_llm_args_with_extra_dict( @@ -888,6 +917,7 @@ def test_config_file_merge_rejects_legacy_and_new_mamba_intervals(): ) +@pytest.mark.cpu_only def test_KvCacheConfig_disk_cache_validation(tmp_path): config = KvCacheConfig(disk_cache_size=2048, disk_cache_path=str(tmp_path)) @@ -899,6 +929,7 @@ def test_KvCacheConfig_disk_cache_validation(tmp_path): assert "disk_cache_path" in str(exc_info.value) +@pytest.mark.cpu_only class TestMultimodalEncoderCudaGraphConfig: def test_minimal_required_fields(self): @@ -941,6 +972,7 @@ def test_rejects_buckets_with_too_few_tokens(self): MultimodalEncoderCudaGraphConfig(buckets=[(1, 2)]) +@pytest.mark.cpu_only class TestMultimodalConfig: def test_default_encoder_cuda_graph_is_none(self): @@ -1068,23 +1100,27 @@ def test_encoder_cache_and_side_stream_max_ahead_can_be_combined(self): "avg_seq_len": 0 }, ]) +@pytest.mark.cpu_only def test_KvCacheConfig_pool_ratio_avg_seq_len_validation(kwargs): with pytest.raises(ValidationError): KvCacheConfig(**kwargs) +@pytest.mark.cpu_only def test_CapacitySchedulerPolicy(): val = CapacitySchedulerPolicy.MAX_UTILIZATION assert PybindMirror.maybe_to_pybind( val) == tle.CapacitySchedulerPolicy.MAX_UTILIZATION +@pytest.mark.cpu_only def test_ContextChunkingPolicy(): val = ContextChunkingPolicy.EQUAL_PROGRESS assert PybindMirror.maybe_to_pybind( val) == tle.ContextChunkingPolicy.EQUAL_PROGRESS +@pytest.mark.cpu_only def test_SleepConfig_restore_modes_normalized_from_dict(): sleep_config = SleepConfig( restore_modes={ @@ -1100,6 +1136,7 @@ def test_SleepConfig_restore_modes_normalized_from_dict(): RestoreMode) +@pytest.mark.cpu_only def test_SleepConfig_restore_modes_normalized_from_defaultdict(): sleep_config = SleepConfig(restore_modes=defaultdict( lambda: RestoreMode.CPU, { @@ -1115,7 +1152,7 @@ def test_SleepConfig_restore_modes_normalized_from_defaultdict(): ExecutorMemoryType.SAMPLER] == RestoreMode.CPU -@force_ampere +@pytest.mark.cpu_only def test_SleepConfig_is_picklable(): """SleepConfig with default construction must survive a pickle round-trip. @@ -1131,7 +1168,7 @@ def test_SleepConfig_is_picklable(): assert rt.restore_modes == cfg_default.restore_modes -@force_ampere +@pytest.mark.cpu_only def test_SleepConfig_pickle_custom_restore_modes_roundtrip(): """SleepConfig with explicit per-key overrides must survive a pickle round-trip.""" import pickle @@ -1148,7 +1185,7 @@ def test_SleepConfig_pickle_custom_restore_modes_roundtrip(): ExecutorMemoryType.MODEL_WEIGHTS_MAIN] == RestoreMode.CPU -@force_ampere +@pytest.mark.cpu_only def test_SleepConfig_pickle_defaultfactory_survives_roundtrip(): """The defaultdict default_factory must remain functional after pickle. @@ -1167,6 +1204,7 @@ def test_SleepConfig_pickle_defaultfactory_survives_roundtrip(): missing_key] +@pytest.mark.cpu_only def test_DynamicBatchConfig_declaration(): config = DynamicBatchConfig(enable_batch_size_tuning=True, enable_max_num_tokens_tuning=True, @@ -1179,12 +1217,12 @@ def test_DynamicBatchConfig_declaration(): assert pybind_config.dynamic_batch_moving_average_window == 10 +@pytest.mark.cpu_only def test_SchedulerConfig_declaration() -> None: default_config = SchedulerConfig() default_pybind_config = PybindMirror.maybe_to_pybind(default_config) assert default_config.enable_prefix_aware_scheduling is True assert default_pybind_config.enable_prefix_aware_scheduling is True - config = SchedulerConfig( capacity_scheduler_policy=CapacitySchedulerPolicy.MAX_UTILIZATION, context_chunking_policy=ContextChunkingPolicy.EQUAL_PROGRESS, @@ -1202,6 +1240,7 @@ def test_SchedulerConfig_declaration() -> None: assert pybind_config.enable_prefix_aware_scheduling is False +@pytest.mark.cpu_only def test_PeftCacheConfig_declaration(): config = PeftCacheConfig(num_host_module_layer=1, num_device_module_layer=1, @@ -1231,6 +1270,7 @@ def test_PeftCacheConfig_declaration(): assert pybind_config.lora_prefetch_dir == "." +@pytest.mark.cpu_only def test_PeftCacheConfig_from_pybind(): pybind_config = tle.PeftCacheConfig(num_host_module_layer=1, num_device_module_layer=1, @@ -1260,6 +1300,7 @@ def test_PeftCacheConfig_from_pybind(): assert config.lora_prefetch_dir == "." +@pytest.mark.cpu_only def test_PeftCacheConfig_from_pybind_gets_python_only_default_values_when_none( ): pybind_config = tle.PeftCacheConfig(num_host_module_layer=1, @@ -1292,6 +1333,7 @@ def test_PeftCacheConfig_from_pybind_gets_python_only_default_values_when_none( assert config.lora_prefetch_dir == "." +@pytest.mark.cpu_only class TestTelemetryConfigPrecedence: """Telemetry-config precedence in the merge helper. @@ -1423,6 +1465,7 @@ def test_yaml_null_telemetry_config_preserves_default(self, yaml_value): assert tc.disabled is False +@pytest.mark.cpu_only class TestExplicitCliKeysPrecedence: """`explicit_cli_keys` makes the CLI side win over YAML on conflicts.""" @@ -1535,6 +1578,7 @@ def test_enable_block_reuse_explicit_wins_over_yaml(self): assert merged["kv_cache_config"].enable_block_reuse is False +@pytest.mark.cpu_only class TestEvalTranslationMap: """eval's _CLICK_TO_LLM_ARG via the shared helper.""" @@ -1579,6 +1623,7 @@ def test_meta_flags_excluded(self): assert self._collect({"extra_llm_api_options", "config"}) == set() +@pytest.mark.cpu_only class TestBenchTranslationMap: """`collect_explicit_cli_keys` in bench.benchmark rewrites Click param names.""" @@ -1630,6 +1675,7 @@ def test_meta_flags_excluded(self): assert self._collect({"extra_llm_api_options", "config"}) == set() +@pytest.mark.cpu_only class TestDisaggLauncherKwargsPreservation: """Regression tests for `_build_llm_args_from_disagg_server_cfg`. @@ -1681,6 +1727,7 @@ def test_default_valued_named_params_survive(self): assert final.get("tensor_parallel_size") == 1 +@pytest.mark.cpu_only class TestTorchLlmArgsCudaGraphSettings: def test_cuda_graph_batch_sizes_case_0(self): @@ -1783,6 +1830,7 @@ def test_generate_cuda_graph_batch_sizes_padding_edge_cases( assert max_batch_size in batch_sizes +@pytest.mark.cpu_only class TestPiecewiseCudaGraphCaptureDefaults: """Piecewise CUDA graph capture-set defaults and reachable-ceiling filter. @@ -2069,6 +2117,7 @@ def test_runtime_sizes(self): assert max_seq_len == 128 assert max_batch_size == 8 + @pytest.mark.cpu_only def test_dynamic_setattr(self): with pytest.raises(pydantic_core._pydantic_core.ValidationError): args = TorchLlmArgs(model=llama_model_path, invalid_arg=1) @@ -2077,6 +2126,7 @@ def test_dynamic_setattr(self): args = TorchLlmArgs(model=llama_model_path) args.invalid_arg = 1 + @pytest.mark.cpu_only def test_speculative_model_alias(self): spec_config = EagleDecodingConfig( max_draft_len=3, @@ -2088,6 +2138,7 @@ def test_speculative_model_alias(self): speculative_config=spec_config) assert args.speculative_model == "/path/to/model" + @pytest.mark.cpu_only @print_traceback_on_error def test_model_kwargs_with_num_hidden_layers(self): config_no_kwargs = ModelConfig.from_pretrained( @@ -2099,6 +2150,7 @@ def test_model_kwargs_with_num_hidden_layers(self): assert config_with_kwargs.num_hidden_layers == 2 +@pytest.mark.cpu_only class TestStrictBaseModelArbitraryArgs: """Test that StrictBaseModel prevents arbitrary arguments from being accepted.""" @@ -2353,8 +2405,14 @@ class TestConfig(StrictBaseModel): assert "extra_field" in str(exc_info.value) +@pytest.mark.cpu_only class TestServeDefaults: + @pytest.fixture(autouse=True) + def _patch_device_count(self): + with patch("tensorrt_llm.commands.serve.device_count", return_value=1): + yield + def test_serve_get_llm_args_preserves_model_defaults(self): # No explicit CLI flags: only required params and serve-side defaults # reach the constructor; everything else is left for YAML / model @@ -2362,6 +2420,7 @@ def test_serve_get_llm_args_preserves_model_defaults(self): llm_args, _ = get_llm_args( model=llama_model_path, backend="pytorch", + gpus_per_node=1, ) assert "model" in llm_args @@ -2377,6 +2436,7 @@ def test_serve_get_llm_args_preserves_model_defaults(self): llm_args_with_values, _ = get_llm_args( model=llama_model_path, backend="pytorch", + gpus_per_node=1, max_batch_size=128, tensor_parallel_size=4, explicit_cli_keys={"max_batch_size", "tensor_parallel_size"}, @@ -2386,7 +2446,9 @@ def test_serve_get_llm_args_preserves_model_defaults(self): def test_serve_filters_default_values(self): # All defaults, no explicit CLI flags. - llm_args, _ = get_llm_args(model=llama_model_path, backend="pytorch") + llm_args, _ = get_llm_args(model=llama_model_path, + backend="pytorch", + gpus_per_node=1) assert "model" in llm_args assert "backend" in llm_args @@ -2399,6 +2461,7 @@ def test_serve_filters_default_values(self): llm_args, _ = get_llm_args( model=llama_model_path, backend="pytorch", + gpus_per_node=1, max_batch_size=128, tensor_parallel_size=4, explicit_cli_keys={"max_batch_size", "tensor_parallel_size"}, @@ -2422,7 +2485,8 @@ def test_serve_backend_specific_configs(self): # PyTorch backend: build_config / scheduler_config stay None and are # filtered out. llm_args_pytorch, _ = get_llm_args(model=llama_model_path, - backend="pytorch") + backend="pytorch", + gpus_per_node=1) assert "build_config" not in llm_args_pytorch assert "scheduler_config" not in llm_args_pytorch @@ -2431,6 +2495,7 @@ def test_serve_explicit_cli_default_value_wins_over_yaml(self): llm_args, _ = get_llm_args( model=llama_model_path, backend="pytorch", + gpus_per_node=1, tensor_parallel_size=1, explicit_cli_keys={"tensor_parallel_size"}, ) @@ -2605,6 +2670,7 @@ def test_empty_nested_config_preserves_defaults(self): assert modified_args.kv_cache_config.free_gpu_memory_fraction == 0.75 +@pytest.mark.cpu_only def test_executor_config_consistency(): """Verify that BaseLlmArgs exposes all ExecutorConfig options.""" # max_beam_width is not included since vague behavior due to lacking the support for dynamic beam width during @@ -2686,6 +2752,7 @@ def _get_qualified_name(cls: type) -> str: return f"{cls.__module__}.{cls.__qualname__}" +@pytest.mark.cpu_only class TestPydanticBestPractices: """Ensure that the user-facing LlmArgs and its subfields follow Pydantic best practices. """ @@ -2947,6 +3014,7 @@ def test_no_custom_init_methods(self): ) +@pytest.mark.cpu_only def test_kv_cache_compression_config_dispatches_by_algorithm(): from tensorrt_llm.llmapi.llm_args import \ TriAttentionKvCacheCompressionConfig @@ -2976,6 +3044,7 @@ def test_kv_cache_compression_config_dispatches_by_algorithm(): assert "changes_physical_kv_length" not in config.model_dump() +@pytest.mark.cpu_only class TestSkipSoftmaxAttentionConfig: """Test LLM Skip Softmax Attention config behavior.""" @@ -3294,6 +3363,7 @@ def test_ckpt_sparse_attention_config_can_be_passed_directly(self): 100.0 * math.exp(5.0 * 0.5)) +@pytest.mark.cpu_only class TestDeepSeekV4SparseAttentionConfig: def test_zero_compress_ratios_are_normalized(self): diff --git a/tests/unittest/llmapi/test_llm_quant.py b/tests/unittest/llmapi/test_llm_quant.py index 2fc3cd86218c..141ea98ab644 100644 --- a/tests/unittest/llmapi/test_llm_quant.py +++ b/tests/unittest/llmapi/test_llm_quant.py @@ -8,6 +8,7 @@ from tensorrt_llm.llmapi.llm_utils import QuantAlgo +@pytest.mark.cpu_only def test_quant_cfg_from_quant_cfg_json(): """ Test loading MIXED_PRECISION config from quant_cfg.json with per-layer quantization. @@ -71,6 +72,7 @@ def test_quant_cfg_from_quant_cfg_json(): assert awq_layer.pre_quant_scale is True +@pytest.mark.cpu_only def test_quant_cfg_top_level_overlay(): """quant_cfg.json's top-level group_size/exclude_modules override hf_quant_config.json.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -111,6 +113,7 @@ def test_quant_cfg_top_level_overlay(): assert quant_config.exclude_modules == ["lm_head", "model.embed_tokens"] +@pytest.mark.cpu_only def test_quant_cfg_from_hf_quant_config(): """Test fallback to hf_quant_config.json when quant_cfg.json is missing.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -156,6 +159,7 @@ def _write_hf_quant_config(model_dir: Path, content: dict) -> Path: return path +@pytest.mark.cpu_only def test_quant_cfg_fp8_legacy_shape(): """Plain FP8 modelopt 0.x checkpoint: legacy 'quantization' wrapper.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -180,6 +184,7 @@ def test_quant_cfg_fp8_legacy_shape(): assert layer_quant_config is None +@pytest.mark.cpu_only def test_quant_cfg_flat_shape_with_ignore_rename(): """Modelopt 1.x flat shape: ``ignore`` is renamed to ``exclude_modules``.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -200,6 +205,7 @@ def test_quant_cfg_flat_shape_with_ignore_rename(): assert quant_config.exclude_modules == ["lm_head", "model.embed_tokens"] +@pytest.mark.cpu_only def test_quant_cfg_flat_shape_kv_cache_scheme_dict(): """Flat shape with compressed-tensors-style kv_cache_scheme dict (FP8).""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -224,6 +230,7 @@ def test_quant_cfg_flat_shape_kv_cache_scheme_dict(): assert quant_config.kv_cache_quant_algo == QuantAlgo.FP8 +@pytest.mark.cpu_only def test_quant_cfg_flat_shape_kv_cache_scheme_string_nvfp4(): """Flat shape with bare-string kv_cache_scheme fallback (NVFP4).""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -245,6 +252,7 @@ def test_quant_cfg_flat_shape_kv_cache_scheme_string_nvfp4(): assert quant_config.kv_cache_quant_algo == QuantAlgo.NVFP4 +@pytest.mark.cpu_only def test_quant_cfg_fp8_pb_wo_alias_canonicalized(): """Legacy ``fp8_pb_wo`` alias is canonicalized to FP8_BLOCK_SCALES.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -265,6 +273,7 @@ def test_quant_cfg_fp8_pb_wo_alias_canonicalized(): assert quant_config.group_size == 128 +@pytest.mark.cpu_only def test_quant_cfg_fp8_block_scales_trtllm_default_excludes(): """TRTLLM moe_backend + FP8_BLOCK_SCALES + no excludes → defaults applied.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -285,6 +294,7 @@ def test_quant_cfg_fp8_block_scales_trtllm_default_excludes(): ] +@pytest.mark.cpu_only def test_quant_cfg_explicit_empty_excludes_preserved(): """Explicit ``exclude_modules: []`` is preserved (no defaults applied).""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -305,6 +315,7 @@ def test_quant_cfg_explicit_empty_excludes_preserved(): assert quant_config.exclude_modules == [] +@pytest.mark.cpu_only def test_quant_cfg_mixed_precision_kv_cache_conflict_raises(): """quant_cfg.json kv_cache_quant_algo conflicting with hf_quant_config.json raises.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -335,6 +346,7 @@ def test_quant_cfg_mixed_precision_kv_cache_conflict_raises(): model_dir, None) +@pytest.mark.cpu_only def test_quant_cfg_awq_extra_fields_preserved_via_load_hf_quant_config(): """AWQ extras (``has_zero_point``, ``pre_quant_scale``) flow through ``load_hf_quant_config``.""" inline_modelopt_awq = { @@ -381,6 +393,7 @@ def test_quant_cfg_awq_extra_fields_preserved_via_load_hf_quant_config(): ("not a dict", False), (None, False), ]) +@pytest.mark.cpu_only def test_is_modelopt_quant_config(config, expected): """Producer name or quant_method prefix must signal modelopt.""" from tensorrt_llm.quantization.modelopt_config import \ @@ -418,6 +431,7 @@ def test_is_modelopt_quant_config(config, expected): }, None), (123, None), ]) +@pytest.mark.cpu_only def test_kv_cache_scheme_to_algo(scheme, expected): """``_kv_cache_scheme_to_algo`` covers string + dict + None inputs.""" from tensorrt_llm.quantization.modelopt_config import \ @@ -439,6 +453,7 @@ def test_kv_cache_scheme_to_algo(scheme, expected): "quantization": "not a dict", }, "'quantization' must be a dict"), ]) +@pytest.mark.cpu_only def test_read_modelopt_quant_config_invalid_raises(raw, match): """Non-dict / non-modelopt / malformed configs raise ValueError.""" from tensorrt_llm.quantization.modelopt_config import \ @@ -447,6 +462,7 @@ def test_read_modelopt_quant_config_invalid_raises(raw, match): read_modelopt_quant_config(raw) +@pytest.mark.cpu_only def test_quant_cfg_quant_algo_fields_are_enum_typed(): """Top-level and per-layer ``quant_algo``/``kv_cache_quant_algo`` are QuantAlgo enums.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -480,6 +496,7 @@ def test_quant_cfg_quant_algo_fields_are_enum_typed(): assert layer.kv_cache_quant_algo is QuantAlgo.FP8 +@pytest.mark.cpu_only @pytest.mark.parametrize("scheme", ["INT8", {"type": "int", "num_bits": 8}]) def test_quant_cfg_flat_shape_kv_cache_scheme_int8(scheme): """Flat shape: INT8 ``kv_cache_scheme`` honored via both string and dict forms.""" @@ -497,6 +514,7 @@ def test_quant_cfg_flat_shape_kv_cache_scheme_int8(scheme): assert quant_config.kv_cache_quant_algo is QuantAlgo.INT8 +@pytest.mark.cpu_only def test_quant_cfg_awq_extras_default_when_absent(): """When AWQ extras are absent from the JSON, ``QuantConfig`` defaults are preserved.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -517,6 +535,7 @@ def test_quant_cfg_awq_extras_default_when_absent(): assert quant_config.pre_quant_scale is False +@pytest.mark.cpu_only def test_load_hf_quant_config_fp8_block_scales_deepseek_v3(): """DeepSeek V3 ``quant_method=fp8`` with weight_block_size=(128,128).""" quant_config, _ = ModelConfig.load_hf_quant_config( @@ -535,6 +554,7 @@ def test_load_hf_quant_config_fp8_block_scales_deepseek_v3(): ("channel", "token", QuantAlgo.FP8_PER_CHANNEL_PER_TOKEN), ("block", "group", QuantAlgo.FP8_BLOCK_SCALES), ]) +@pytest.mark.cpu_only def test_load_hf_quant_config_compressed_tensors(weights_strategy, inputs_strategy, expected_algo): @@ -561,6 +581,7 @@ def test_load_hf_quant_config_compressed_tensors(weights_strategy, assert quant_config.exclude_modules == ["lm_head"] +@pytest.mark.cpu_only def test_load_hf_quant_config_nvfp4_native_with_modules_to_not_convert(): """HF nvfp4 schema: ``modules_to_not_convert`` is merged into ``exclude_modules``.""" quant_config, _ = ModelConfig.load_hf_quant_config( @@ -577,6 +598,7 @@ def test_load_hf_quant_config_nvfp4_native_with_modules_to_not_convert(): assert "lm_head" in quant_config.exclude_modules # default +@pytest.mark.cpu_only def test_load_hf_quant_config_no_match_returns_empty_quant_config(): """An unrecognized ``quant_method`` returns an empty QuantConfig (no algo set).""" quant_config, _ = ModelConfig.load_hf_quant_config( diff --git a/tests/unittest/llmapi/test_llm_telemetry.py b/tests/unittest/llmapi/test_llm_telemetry.py index 9f7497aa194c..1582d0cc00c5 100644 --- a/tests/unittest/llmapi/test_llm_telemetry.py +++ b/tests/unittest/llmapi/test_llm_telemetry.py @@ -330,6 +330,7 @@ def test_feature_detection_pytorch(self, extra_kwargs, key, expected): # --------------------------------------------------------------------------- +@pytest.mark.cpu_only class TestTelemetryEvalContext: """Verify UsageContext.CLI_EVAL flows through TelemetryConfig.""" @@ -341,6 +342,7 @@ def test_eval_sets_cli_eval_context(self): assert config.usage_context == _llm_args_mod.UsageContext.CLI_EVAL +@pytest.mark.cpu_only class TestTelemetryBenchContext: """Verify UsageContext.CLI_BENCH flows through TelemetryConfig.""" diff --git a/tests/unittest/llmapi/test_llm_utils.py b/tests/unittest/llmapi/test_llm_utils.py index 402155881bd7..1e3321eac7f8 100644 --- a/tests/unittest/llmapi/test_llm_utils.py +++ b/tests/unittest/llmapi/test_llm_utils.py @@ -2,6 +2,7 @@ import threading import time +import pytest import torch from tensorrt_llm.llmapi.llm_args import TorchLlmArgs @@ -12,6 +13,7 @@ # isort: on +@pytest.mark.cpu_only def test_LlmArgs_default_gpus_per_node(): # default llm_args = TorchLlmArgs(model=llama_model_path) @@ -22,6 +24,7 @@ def test_LlmArgs_default_gpus_per_node(): assert llm_args.gpus_per_node == 6 +@pytest.mark.cpu_only def test_AsyncQueue(): queue = AsyncQueue() diff --git a/tests/unittest/llmapi/test_mpi_session.py b/tests/unittest/llmapi/test_mpi_session.py index f0db0d2ba99e..b6c83185e29d 100644 --- a/tests/unittest/llmapi/test_mpi_session.py +++ b/tests/unittest/llmapi/test_mpi_session.py @@ -21,7 +21,6 @@ # isort: off sys.path.append(os.path.join(cur_dir, '..')) -from utils.util import skip_single_gpu # isort: on @@ -32,6 +31,12 @@ def task0(): return MPINodeState.state +@pytest.fixture(autouse=True) +def _enable_mpi(monkeypatch): + monkeypatch.delenv("TLLM_DISABLE_MPI", raising=False) + + +@pytest.mark.cpu_only @pytest.mark.skipif(not ENABLE_MULTI_DEVICE, reason="multi-device required") def test_mpi_session_basic(): from tensorrt_llm.llmapi.mpi_session import MpiPoolSession @@ -66,6 +71,7 @@ def run_client(server_addr, values_to_process, hmac_key: bytes): return f"Error in client: {str(e)}" +@pytest.mark.cpu_only @pytest.mark.parametrize("task_type", ["submit", "submit_sync"]) def test_remote_mpi_session(task_type: Literal["submit", "submit_sync"]): """Test RemoteMpiPoolSessionClient and RemoteMpiPoolSessionServer interaction""" @@ -117,12 +123,13 @@ def task1(): assert mpi_env +@pytest.mark.cpu_only def test_split_mpi_env(): session = MpiPoolSession(n_workers=4) session.submit_sync(task1) -@skip_single_gpu +@pytest.mark.cpu_only @pytest.mark.parametrize( "task_script", ["_run_mpi_comm_task.py", "_run_multi_mpi_comm_tasks.py"]) def test_llmapi_launch_multiple_tasks(task_script: str): diff --git a/tests/unittest/llmapi/test_mx_args.py b/tests/unittest/llmapi/test_mx_args.py index 2877485c9e0d..058d6073f69d 100644 --- a/tests/unittest/llmapi/test_mx_args.py +++ b/tests/unittest/llmapi/test_mx_args.py @@ -14,6 +14,9 @@ from tensorrt_llm.llmapi.llm_args import TorchLlmArgs +pytestmark = pytest.mark.cpu_only + + _LOGGER_PATH = "tensorrt_llm.llmapi.llm_args.logger" _DUMMY_MODEL = "/tmp/test-mx-args-nonexistent" diff --git a/tests/unittest/llmapi/test_reasoning_parser.py b/tests/unittest/llmapi/test_reasoning_parser.py index 68543c247a5f..11c70bf2b334 100644 --- a/tests/unittest/llmapi/test_reasoning_parser.py +++ b/tests/unittest/llmapi/test_reasoning_parser.py @@ -22,6 +22,8 @@ ReasoningParserFactory, resolve_auto_reasoning_parser) +pytestmark = pytest.mark.cpu_only + R1_START, R1_END = "", "" diff --git a/tests/unittest/llmapi/test_request_priority.py b/tests/unittest/llmapi/test_request_priority.py index 18a283340b92..790f82b47678 100644 --- a/tests/unittest/llmapi/test_request_priority.py +++ b/tests/unittest/llmapi/test_request_priority.py @@ -29,6 +29,9 @@ from tensorrt_llm.executor.request import DEFAULT_REQUEST_PRIORITY, GenerationRequest from tensorrt_llm.sampling_params import SamplingParams +pytestmark = pytest.mark.cpu_only + + # --------------------------------------------------------------------------- # GenerationRequest # --------------------------------------------------------------------------- diff --git a/tests/unittest/llmapi/test_sampling_params.py b/tests/unittest/llmapi/test_sampling_params.py index f9e4ad99e5e6..8c5d2a7b5d3f 100644 --- a/tests/unittest/llmapi/test_sampling_params.py +++ b/tests/unittest/llmapi/test_sampling_params.py @@ -31,6 +31,8 @@ ) from tensorrt_llm.serve.resource_governor import ResourceGovernor +pytestmark = pytest.mark.cpu_only + @pytest.mark.parametrize("field", ["logprobs", "prompt_logprobs", "top_logprobs"]) def test_check_logprobs_limit(field): diff --git a/tests/unittest/llmapi/test_serialization.py b/tests/unittest/llmapi/test_serialization.py index 87ff918330e4..c06713e7f7af 100644 --- a/tests/unittest/llmapi/test_serialization.py +++ b/tests/unittest/llmapi/test_serialization.py @@ -1,7 +1,10 @@ +import pytest import torch from tensorrt_llm import serialization +pytestmark = pytest.mark.cpu_only + class TestClass: diff --git a/tests/unittest/llmapi/test_tokenizer_multinode.py b/tests/unittest/llmapi/test_tokenizer_multinode.py index 4594c3990325..a87765913ef9 100644 --- a/tests/unittest/llmapi/test_tokenizer_multinode.py +++ b/tests/unittest/llmapi/test_tokenizer_multinode.py @@ -9,6 +9,8 @@ from tensorrt_llm.tokenizer.tokenizer import TransformersTokenizer, load_hf_tokenizer +pytestmark = pytest.mark.cpu_only + def test_trust_remote_code_tokenizer_pickle_roundtrip_multinode(): """nvbugs/5823783 regression. diff --git a/tests/unittest/llmapi/test_utils.py b/tests/unittest/llmapi/test_utils.py index 24718b064dd7..2d75858bfe3c 100644 --- a/tests/unittest/llmapi/test_utils.py +++ b/tests/unittest/llmapi/test_utils.py @@ -1,7 +1,11 @@ +import pytest + from tensorrt_llm.llmapi import LlmArgs from tensorrt_llm.llmapi.utils import (ApiStatusRegistry, generate_api_docs_as_docstring) +pytestmark = pytest.mark.cpu_only + def test_api_status_registry():