diff --git a/.github/workflows/nightly-test-npu.yml b/.github/workflows/nightly-test-npu.yml index bfbbe8041..667a61df3 100644 --- a/.github/workflows/nightly-test-npu.yml +++ b/.github/workflows/nightly-test-npu.yml @@ -50,7 +50,6 @@ jobs: pip config set global.index-url http://${CACHING_URL}/pypi/simple pip config set global.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple" pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn" - bash scripts/ci/npu/npu_ci_install_dependency.sh a3 # copy required file from our daily cache cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp @@ -71,7 +70,18 @@ jobs: PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" STREAMS_PER_DEVICE: 32 run: | - pip install sentence_transformers accelerate + pip install sglang_router + hf download lmms-lab/MMMU --repo-type dataset + pip install sentence_transformers torchaudio==2.8.0 + pip install protobuf==6.31.1 zss pre-commit wandb>=0.16.0 tenacity==8.3.0 loguru openpyxl latex2sympy2 zstandard transformers-stream-generator tqdm-multiprocess pycocoevalcap + pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 + pip install jsonlines httpx==0.25.0 evaluate>=0.4.0 datasets==2.16.1 numexpr xgrammar==0.1.25 numpy==1.26.4 dotenv + git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git + cd ./lmms-eval + nohup pip install . > lmmslog.txt 2>&1 & + sleep 120 + export PYTHONPATH=$PYTHONPATH:$(pwd) + cd ../ cd test python3 run_suite.py --hw npu --suite nightly-1-npu-a3 --nightly --continue-on-error --timeout-per-file 3600 --auto-partition-id ${{ matrix.part }} --auto-partition-size 2 @@ -98,7 +108,6 @@ jobs: pip config set global.index-url http://${CACHING_URL}/pypi/simple pip config set global.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple" pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn" - bash scripts/ci/npu/npu_ci_install_dependency.sh a3 # copy required file from our daily cache cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp @@ -118,7 +127,18 @@ jobs: PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" STREAMS_PER_DEVICE: 32 run: | - pip install sentence_transformers accelerate + pip install sglang_router + hf download lmms-lab/MMMU --repo-type dataset + pip install sentence_transformers torchaudio==2.8.0 + pip install protobuf==6.31.1 zss pre-commit wandb>=0.16.0 tenacity==8.3.0 loguru openpyxl latex2sympy2 zstandard transformers-stream-generator tqdm-multiprocess pycocoevalcap + pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 + pip install jsonlines httpx==0.25.0 evaluate>=0.4.0 datasets==2.16.1 numexpr xgrammar==0.1.25 numpy==1.26.4 dotenv + git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git + cd ./lmms-eval + nohup pip install . > lmmslog.txt 2>&1 & + sleep 120 + export PYTHONPATH=$PYTHONPATH:$(pwd) + cd ../ cd test python3 run_suite.py --hw npu --suite nightly-2-npu-a3 --nightly --continue-on-error --timeout-per-file 3600 --auto-partition-id ${{ matrix.part }} --auto-partition-size 1 @@ -142,9 +162,8 @@ jobs: # speed up by using infra cache services CACHING_URL="cache-service.nginx-pypi-cache.svc.cluster.local" sed -Ei "s@(ports|archive).ubuntu.com@${CACHING_URL}:8081@g" /etc/apt/sources.list - pip config set global.index-url http://${CACHING_URL}/pypi/simple - pip config set global.trusted-host "${CACHING_URL}" - + pip config set global.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple" + pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn" bash scripts/ci/npu/npu_ci_install_dependency.sh a3 # copy required file from our daily cache cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp @@ -165,10 +184,11 @@ jobs: PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" STREAMS_PER_DEVICE: 32 run: | + pip install sglang_router hf download lmms-lab/MMMU --repo-type dataset - pip install sentence_transformers + pip install sentence_transformers torchaudio==2.8.0 pip install protobuf==6.31.1 zss pre-commit wandb>=0.16.0 tenacity==8.3.0 loguru openpyxl latex2sympy2 zstandard transformers-stream-generator tqdm-multiprocess pycocoevalcap - pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter peft==0.2.0 black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 + pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 pip install jsonlines httpx==0.25.0 evaluate>=0.4.0 datasets==2.16.1 numexpr xgrammar==0.1.25 numpy==1.26.4 dotenv git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git cd ./lmms-eval @@ -202,7 +222,6 @@ jobs: pip config set global.index-url http://${CACHING_URL}/pypi/simple pip config set global.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple" pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn" - bash scripts/ci/npu/npu_ci_install_dependency.sh a3 # copy required file from our daily cache cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp @@ -223,10 +242,11 @@ jobs: PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" STREAMS_PER_DEVICE: 32 run: | + pip install sglang_router hf download lmms-lab/MMMU --repo-type dataset - pip install sentence_transformers + pip install sentence_transformers torchaudio==2.8.0 pip install protobuf==6.31.1 zss pre-commit wandb>=0.16.0 tenacity==8.3.0 loguru openpyxl latex2sympy2 zstandard transformers-stream-generator tqdm-multiprocess pycocoevalcap - pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter peft==0.2.0 black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 + pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 pip install jsonlines httpx==0.25.0 evaluate>=0.4.0 datasets==2.16.1 numexpr xgrammar==0.1.25 numpy==1.26.4 dotenv git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git cd ./lmms-eval @@ -243,7 +263,7 @@ jobs: strategy: fail-fast: false matrix: - part: [0] + part: [0, 1] container: image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:8.5.0-a3-ubuntu22.04-py3.11 steps: @@ -260,7 +280,6 @@ jobs: pip config set global.index-url http://${CACHING_URL}/pypi/simple pip config set global.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple" pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn" - bash scripts/ci/npu/npu_ci_install_dependency.sh a3 # copy required file from our daily cache cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp @@ -281,10 +300,11 @@ jobs: PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" STREAMS_PER_DEVICE: 32 run: | + pip install sglang_router hf download lmms-lab/MMMU --repo-type dataset - pip install sentence_transformers + pip install sentence_transformers torchaudio==2.8.0 pip install protobuf==6.31.1 zss pre-commit wandb>=0.16.0 tenacity==8.3.0 loguru openpyxl latex2sympy2 zstandard transformers-stream-generator tqdm-multiprocess pycocoevalcap - pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter peft==0.2.0 black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 + pip install yt-dlp sentencepiece==0.1.99 nltk av ftfy sqlitedict==2.1.0 sacrebleu>=1.5.0 pytablewriter black==24.1.0 isort==5.13.2 peft>=0.2.0 accelerate>=0.29.1 pip install jsonlines httpx==0.25.0 evaluate>=0.4.0 datasets==2.16.1 numexpr xgrammar==0.1.25 numpy==1.26.4 dotenv git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git cd ./lmms-eval @@ -293,7 +313,7 @@ jobs: export PYTHONPATH=$PYTHONPATH:$(pwd) cd ../ cd test - python3 run_suite.py --hw npu --suite nightly-16-npu-a3 --nightly --continue-on-error --timeout-per-file 3600 --auto-partition-id ${{ matrix.part }} --auto-partition-size 1 + python3 run_suite.py --hw npu --suite nightly-16-npu-a3 --nightly --continue-on-error --timeout-per-file 3600 --auto-partition-id ${{ matrix.part }} --auto-partition-size 2 check-all-jobs: if: github.repository == 'sgl-project/sglang' && always() diff --git a/python/sglang/test/ascend/disaggregation_utils.py b/python/sglang/test/ascend/disaggregation_utils.py new file mode 100644 index 000000000..cb46a9d3b --- /dev/null +++ b/python/sglang/test/ascend/disaggregation_utils.py @@ -0,0 +1,153 @@ +import logging +import os +import time +import warnings +from urllib.parse import urlparse + +import requests + +from sglang.srt.environ import envs +from sglang.srt.utils import kill_process_tree +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_with_error_check, +) + +logger = logging.getLogger(__name__) + + +class TestDisaggregationBase(CustomTestCase): + @classmethod + def setUpClass(cls): + parsed_url = urlparse(DEFAULT_URL_FOR_TEST) + cls.base_host = parsed_url.hostname + base_port = str(parsed_url.port) + cls.lb_port = base_port + cls.prefill_port = f"{int(base_port) + 100}" + cls.decode_port = f"{int(base_port) + 200}" + cls.prefill_url = f"http://{cls.base_host}:{cls.prefill_port}" + cls.decode_url = f"http://{cls.base_host}:{cls.decode_port}" + cls.lb_url = f"http://{cls.base_host}:{cls.lb_port}" + print(f"{cls.base_host=} {cls.lb_port=} {cls.prefill_port=} {cls.decode_port=}") + cls.process_lb, cls.process_decode, cls.process_prefill = None, None, None + + # config transfer backend and rdma devices + cls.transfer_backend = [ + "--disaggregation-transfer-backend", + envs.SGLANG_TEST_PD_DISAGG_BACKEND.get(), + ] + cls.rdma_devices = [ + "--disaggregation-ib-device", + envs.SGLANG_TEST_PD_DISAGG_DEVICES.get(), + ] + if cls.rdma_devices[1] is None: + cls.rdma_devices = [] + msg = "No RDMA devices specified for disaggregation test, using default settings." + warnings.warn(msg) + + @classmethod + def launch_lb(cls): + lb_command = [ + "python3", + "-m", + "sglang_router.launch_router", + "--pd-disaggregation", + "--mini-lb", + "--prefill", + cls.prefill_url, + "--decode", + cls.decode_url, + "--host", + cls.base_host, + "--port", + cls.lb_port, + ] + print("Starting load balancer:", " ".join(lb_command)) + cls.process_lb = popen_with_error_check(lb_command) + cls.wait_server_ready(cls.lb_url + "/health") + + @classmethod + def wait_server_ready(cls, url, timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH): + start_time = time.perf_counter() + while True: + try: + response = requests.get(url) + if response.status_code == 200: + print(f"Server {url} is ready") + return + except Exception: + pass + + if time.perf_counter() - start_time > timeout: + raise RuntimeError(f"Server {url} failed to start in {timeout}s") + time.sleep(1) + + @classmethod + def tearDownClass(cls): + for process in [cls.process_lb, cls.process_decode, cls.process_prefill]: + if process: + try: + kill_process_tree(process.pid) + except Exception as e: + print(f"Error killing process {process.pid}: {e}") + + # wait for 5 seconds + time.sleep(5) + + +def get_rdma_devices_args(): + def _parse_list_env(var_name: str): + val = os.getenv(var_name) + if not val: + return None + items = [x.strip() for x in val.split(",") if x.strip()] + return items or None + + def _pick_default_pair(rdma_all_devices): + return [rdma_all_devices[0], rdma_all_devices[len(rdma_all_devices) // 2]] + + rdma_all_devices = _parse_list_env("SGLANG_CI_RDMA_ALL_DEVICES") or [ + f"mlx5_roce{i}" for i in range(8) + ] + logger.info("Resolved rdma_all_devices=%s", rdma_all_devices) + + n_rdma = len(rdma_all_devices) + + # 1. Get visible GPU indices + cuda_visible_devices = os.getenv("CUDA_VISIBLE_DEVICES") + if not cuda_visible_devices: + warnings.warn("CUDA_VISIBLE_DEVICES is not set. Using default RDMA devices.") + return ",".join(_pick_default_pair(rdma_all_devices)) + + try: + # Convert to list of integers (handling possible spaces and empty strings) + gpu_indices = [ + int(idx.strip()) for idx in cuda_visible_devices.split(",") if idx.strip() + ] + if not gpu_indices or len(gpu_indices) > 4: + return ",".join(_pick_default_pair(rdma_all_devices)) + except ValueError: + warnings.warn(f"Invalid CUDA_VISIBLE_DEVICES format: {cuda_visible_devices}") + return ",".join(_pick_default_pair(rdma_all_devices)) + + # 2. Calculate base RDMA index group (each group of 4 GPUs uses consecutive devices) + base_rdma_group = (min(gpu_indices) // 4) * 4 + for gpu_idx in gpu_indices: + if not (base_rdma_group <= gpu_idx < base_rdma_group + 4): + warnings.warn( + f"GPU index {gpu_idx} is outside expected group " + f"{base_rdma_group}-{base_rdma_group+3}" + ) + + # 3. Generate RDMA device names + rdma_devices = [] + for gpu_idx in gpu_indices: + nic_index = gpu_idx // (8 // n_rdma) + rdma_devices.append(rdma_all_devices[nic_index]) + + if not rdma_devices: + return ",".join(_pick_default_pair(rdma_all_devices)) + + return ",".join(rdma_devices) diff --git a/python/sglang/test/ascend/test_ascend_utils.py b/python/sglang/test/ascend/test_ascend_utils.py index 09a5ab306..a5328fd97 100644 --- a/python/sglang/test/ascend/test_ascend_utils.py +++ b/python/sglang/test/ascend/test_ascend_utils.py @@ -11,59 +11,142 @@ This file contains the following weight path categories: Please remember to sort by variable name within each section. """ +import asyncio +import copy import os +import subprocess +from types import SimpleNamespace +from typing import Awaitable, Callable, NamedTuple, Optional + +from sglang.bench_serving import run_benchmark +from sglang.srt.utils import kill_process_tree +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + auto_config_device, + popen_launch_server, +) # Model weights storage directory MODEL_WEIGHTS_DIR = "/root/.cache/modelscope/hub/models/" HF_MODEL_WEIGHTS_DIR = "/root/.cache/huggingface/hub/" -# fmt: off -# ruff: noqa: E501 # LLM model weights path AFM_4_5B_BASE_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "arcee-ai/AFM-4.5B-Base") -BAICHUAN2_13B_CHAT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "baichuan-inc/Baichuan2-13B-Chat") -C4AI_COMMAND_R_V01_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "CohereForAI/c4ai-command-r-v01") +BAICHUAN2_13B_CHAT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "baichuan-inc/Baichuan2-13B-Chat" +) +C4AI_COMMAND_R_V01_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "CohereForAI/c4ai-command-r-v01" +) +C4AI_COMMAND_R_V01_CHAT_TEMPLATE_PATH = "/__w/sglang/sglang/test/registered/ascend/llm_models/tool_chat_template_c4ai_command_r_v01.jinja" CHATGLM2_6B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "ZhipuAI/chatglm2-6b") -DBRX_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "AI-ModelScope/dbrx-instruct") -DEEPSEEK_R1_0528_W4A8_PER_CHANNEL_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "DeepSeek-R1-0528-w4a8-per-channel") -DEEPSEEK_R1_0528_W8A8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "vllm-ascend/DeepSeek-R1-0528-W8A8") -DEEPSEEK_V2_LITE_W8A8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "vllm-ascend/DeepSeek-V2-Lite-W8A8") -DEEPSEEK_V3_2_EXP_W8A8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "DeepSeek-V3.2-Exp-W8A8") -EAGLE3_LLAMA3_1_INSTRUCT_8B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "sglang-EAGLE3-LLaMA3.1-Instruct-8B") -ERNIE_4_5_21B_A3B_PT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "baidu/ERNIE-4.5-21B-A3B-PT") -EXAONE_3_5_7_8B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct") +DBRX_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "AI-ModelScope/dbrx-instruct" +) +DEEPSEEK_V3_2_EXP_W8A8_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "DeepSeek-V3.2-Exp-W8A8" +) +DEEPSEEK_V3_2_W8A8_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "vllm-ascend/DeepSeek-V3.2-W8A8" +) +DEEPSEEK_CODER_V2_LITE_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct" +) +DEEPSEEK_CODER_1_3_B_BASE_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "deepseek-ai/deepseek-coder-1.3b-base" +) +ERNIE_4_5_21B_A3B_PT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "baidu/ERNIE-4.5-21B-A3B-PT" +) +EXAONE_3_5_7_8B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct" +) GEMMA_3_4B_IT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "google/gemma-3-4b-it") GLM_4_9B_CHAT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "ZhipuAI/glm-4-9b-chat") -GRANITE_3_0_3B_A800M_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "ibm-granite/granite-3.0-3b-a800m-instruct") -GRANITE_3_1_8B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "ibm-granite/granite-3.1-8b-instruct") +GRANITE_3_0_3B_A800M_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "ibm-granite/granite-3.0-3b-a800m-instruct" +) +GRANITE_3_1_8B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "ibm-granite/granite-3.1-8b-instruct" +) GROK_2_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "huihui-ai/grok-2") -INTERNLM2_7B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Shanghai_AI_Laboratory/internlm2-7b") +INTERNLM2_7B_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Shanghai_AI_Laboratory/internlm2-7b" +) KIMI_K2_THINKING_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Kimi/Kimi-K2-Thinking") LING_LITE_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "inclusionAI/Ling-lite") LLAMA_2_7B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Llama-2-7B") -LLAMA_3_1_8B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "AI-ModelScope/Llama-3.1-8B-Instruct") -LLAMA_3_2_1B_INSTRUCT_TOOL_CALLING_LORA_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "codelion/Llama-3.2-1B-Instruct-tool-calling-lora") -LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Llama-3.2-1B-Instruct") +LLAMA_3_1_8B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "AI-ModelScope/Llama-3.1-8B-Instruct" +) +LLAMA_3_2_1B_INSTRUCT_TOOL_CALLING_LORA_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "codelion/Llama-3.2-1B-Instruct-tool-calling-lora" +) +LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "LLM-Research/Llama-3.2-1B-Instruct" +) LLAMA_3_2_1B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Llama-3.2-1B") -LLAMA_4_SCOUT_17B_16E_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "meta-llama/Llama-4-Scout-17B-16E-Instruct") -META_LLAMA_3_1_8B_INSTRUCT = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Meta-Llama-3.1-8B-Instruct") +LLAMA_4_SCOUT_17B_16E_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "meta-llama/Llama-4-Scout-17B-16E-Instruct" +) +META_LLAMA_3_1_8B_INSTRUCT = os.path.join( + MODEL_WEIGHTS_DIR, "LLM-Research/Meta-Llama-3.1-8B-Instruct" +) MIMO_7B_RL_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "XiaomiMiMo/MiMo-7B-RL") MINICPM3_4B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "OpenBMB/MiniCPM3-4B") -MISTRAL_7B_INSTRUCT_V0_2_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "mistralai/Mistral-7B-Instruct-v0.2") -OLMOE_1B_7B_0924_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "allenai/OLMoE-1B-7B-0924") -PERSIMMON_8B_CHAT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Howeee/persimmon-8b-chat") -PHI_4_MULTIMODAL_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "microsoft/Phi-4-multimodal-instruct") -QWEN2_5_7B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-7B-Instruct") +MISTRAL_7B_INSTRUCT_V0_2_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "mistralai/Mistral-7B-Instruct-v0.2" +) +OLMOE_1B_7B_0924_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "allenai/OLMoE-1B-7B-0924" +) +PERSIMMON_8B_CHAT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Howeee/persimmon-8b-chat" +) +PHI_4_MULTIMODAL_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "microsoft/Phi-4-multimodal-instruct" +) +QWEN2_5_7B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-7B-Instruct" +) QWEN3_0_6B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-0.6B") -QWEN3_1_7B_GPTQ_INT8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-1.7B-GPTQ-Int8") -QWEN3_235B_A22B_W8A8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "vllm-ascend/Qwen3-235B-A22B-W8A8") -QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-30B-A3B-Instruct-2507") +QWEN3_1_7B_GPTQ_INT8_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-1.7B-GPTQ-Int8" +) +QWEN3_235B_A22B_W8A8_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "vllm-ascend/Qwen3-235B-A22B-W8A8" +) +QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-30B-A3B-Instruct-2507" +) +QWEN3_8B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-8B") +QWEN3_8B_EAGLE3_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-8B_eagle3") QWEN3_32B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-32B") -QWEN3_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen3-Coder-480B-A35B-Instruct-w8a8-QuaRot") -QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Next-80B-A3B-Instruct") +QWEN3_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen3-Coder-480B-A35B-Instruct-w8a8-QuaRot" +) +QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Next-80B-A3B-Instruct" +) +QWEN3_32B_EAGLE3_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-32B-Eagle3") +QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "aleoyang/Qwen3-32B-w8a8-MindIE" +) +QWEN3_235B_A22B_W8A8_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "vllm-ascend/Qwen3-235B-A22B-W8A8" +) +QWEN3_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen3-Coder-480B-A35B-Instruct-w8a8-QuaRot" +) +QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Next-80B-A3B-Instruct" +) QWQ_32B_W8A8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "vllm-ascend/QWQ-32B-W8A8") SMOLLM_1_7B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "HuggingFaceTB/SmolLM-1.7B") -STABLELM_2_1_6B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "stabilityai/stablelm-2-1_6b") +STABLELM_2_1_6B_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "stabilityai/stablelm-2-1_6b" +) XVERSE_MOE_A36B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "xverse/XVERSE-MoE-A36B") # VLM model weights path @@ -71,39 +154,397 @@ DEEPSEEK_VL2_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "deepseek-ai/deepsee GLM_4_5V_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "ZhipuAI/GLM-4.5V") JANUS_PRO_1B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "deepseek-ai/Janus-Pro-1B") JANUS_PRO_7B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "deepseek-ai/Janus-Pro-7B") -KIMI_VL_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Kimi/Kimi-VL-A3B-Instruct") -LLAMA_3_2_11B_VISION_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Llama-3.2-11B-Vision-Instruct") +KIMI_VL_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Kimi/Kimi-VL-A3B-Instruct" +) +LLAMA_3_2_11B_VISION_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "LLM-Research/Llama-3.2-11B-Vision-Instruct" +) LLAVA_NEXT_72B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "lmms-lab/llava-next-72b") -LLAVA_ONEVISION_QWEN2_7B_OV_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "lmms-lab/llava-onevision-qwen2-7b-ov") -LLAVA_V1_6_34B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "AI-ModelScope/llava-v1.6-34b") +LLAVA_ONEVISION_QWEN2_7B_OV_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "lmms-lab/llava-onevision-qwen2-7b-ov" +) +LLAVA_V1_6_34B_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "AI-ModelScope/llava-v1.6-34b" +) +LLAVA_V1_6_34B_TOKENIZER_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "AI-ModelScope/llava-v1.6-34b/llava-1.6v-34b-tokenizer" +) MIMO_VL_7B_RL_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "XiaomiMiMo/MiMo-VL-7B-RL") MINICPM_O_2_6_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "openbmb/MiniCPM-o-2_6") MINICPM_V_2_6_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "openbmb/MiniCPM-V-2_6") -MISTRAL_SMALL_3_1_24B_INSTRUCT_2503_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "mistralai/Mistral-Small-3.1-24B-Instruct-2503") -QWEN2_5_VL_3B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-VL-3B-Instruct") -QWEN2_5_VL_72B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-VL-72B-Instruct") +MISTRAL_SMALL_3_1_24B_INSTRUCT_2503_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "mistralai/Mistral-Small-3.1-24B-Instruct-2503" +) +QWEN2_5_VL_3B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-VL-3B-Instruct" +) +QWEN2_5_VL_72B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-VL-72B-Instruct" +) +QWEN3_VL_4B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-4B-Instruct" +) +QWEN3_VL_8B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-8B-Instruct" +) +QWEN3_VL_30B_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-30B-A3B-Instruct" +) +QWEN3_VL_235B_A22B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-235B-A22B-Instruct" +) +QWEN2_0_5B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen2-0.5B-Instruct" +) + QWEN3_30B_A3B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-30B-A3B") -QWEN3_30B_MODELSLIM_INT4_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Eco-Tech/Qwen3-30B-A3B-w4a4-LAOS") -QWEN3_VL_235B_A22B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-235B-A22B-Instruct") -QWEN3_VL_30B_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-30B-A3B-Instruct") -QWEN3_VL_4B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-4B-Instruct") -QWEN3_VL_8B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-8B-Instruct") +QWEN3_30B_A3B_W8A8_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-30B-A3B-w8a8" +) + +DEEPSEEK_R1_DISTILL_QWEN_7B_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B" +) + +QWEN3_30B_MODELSLIM_INT4_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Eco-Tech/Qwen3-30B-A3B-w4a4-LAOS" +) # Embedding model weights path BGE_LARGE_EN_V1_5_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "bge-large-en-v1.5") -CLIP_VIT_LARGE_PATCH14_336_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "AI-ModelScope/clip-vit-large-patch14-336") -E5_MISTRAL_7B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "intfloat/e5-mistral-7b-instruct") -GME_QWEN2_VL_2B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Alibaba-NLP/gme-Qwen2-VL-2B-Instruct") -GTE_QWEN2_1_5B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "iic/gte_Qwen2-1.5B-instruct") -QWEN3_EMBEDDING_8B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Embedding-8B") +CLIP_VIT_LARGE_PATCH14_336_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "AI-ModelScope/clip-vit-large-patch14-336" +) +E5_MISTRAL_7B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "intfloat/e5-mistral-7b-instruct" +) +GME_QWEN2_VL_2B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Alibaba-NLP/gme-Qwen2-VL-2B-Instruct" +) +GTE_QWEN2_1_5B_INSTRUCT_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "iic/gte_Qwen2-1.5B-instruct" +) +QWEN3_EMBEDDING_8B_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Embedding-8B" +) # Rerank model weights path -BGE_RERANKER_V2_M3_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "BAAI/bge-reranker-v2-m3") +BGE_RERANKER_V2_M3_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "BAAI/bge-reranker-v2-m3" +) # Reward model weights path -INTERNLM2_7B_REWARD_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Shanghai_AI_Laboratory/internlm2-7b-reward") -QWEN2_5_1_5B_APEACH_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Howeee/Qwen2.5-1.5B-apeach") -QWEN2_5_MATH_RM_72B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-Math-RM-72B") -SKYWORK_REWARD_GEMMA_2_27B_V0_2_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "AI-ModelScope/Skywork-Reward-Gemma-2-27B-v0.2") -SKYWORK_REWARD_LLAMA_3_1_8B_V0_2_WEIGHTS_PATH = os.path.join(HF_MODEL_WEIGHTS_DIR, "models--Skywork--Skywork-Reward-Llama-3.1-8B-v0.2/snapshots/d4117fbfd81b72f41b96341238baa1e3e90a4ce1") +INTERNLM2_7B_REWARD_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Shanghai_AI_Laboratory/internlm2-7b-reward" +) +QWEN2_5_1_5B_APEACH_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Howeee/Qwen2.5-1.5B-apeach" +) +QWEN2_5_MATH_RM_72B_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-Math-RM-72B" +) +SKYWORK_REWARD_GEMMA_2_27B_V0_2_WEIGHTS_PATH = os.path.join( + MODEL_WEIGHTS_DIR, "AI-ModelScope/Skywork-Reward-Gemma-2-27B-v0.2" +) +SKYWORK_REWARD_LLAMA_3_1_8B_V0_2_WEIGHTS_PATH = os.path.join( + HF_MODEL_WEIGHTS_DIR, + "models--Skywork--Skywork-Reward-Llama-3.1-8B-v0.2/snapshots/d4117fbfd81b72f41b96341238baa1e3e90a4ce1", +) # fmt: on + +# Other +DEEPSEEK_CODER_JSON_PATH = "/__w/sglang/sglang/test/registered/ascend/basic_function/parameter/deepseek_coder.json" + + +class ModelTestConfig(NamedTuple): + """ + Configuration for model testing. + + Attributes: + model_path: Path to the model weights directory + mmlu_score: Weight for MMLU benchmark score + gsm8k_accuracy: Weight for GSM8K benchmark score + mmmu_accuracy: Weight for MMMU benchmark score + """ + + model_path: str + mmlu_score: Optional[float] = None + gsm8k_accuracy: Optional[float] = None + mmmu_accuracy: Optional[float] = None + + +LLAMA_3_2_1B_INSTRUCT_WEIGHTS_FOR_TEST = ModelTestConfig( + model_path=LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH, mmlu_score=0.2 +) + +QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_FOR_TEST = ModelTestConfig( + model_path=QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_PATH, gsm8k_accuracy=0.9 +) + +QWEN3_32B_WEIGHTS_FOR_TEST = ModelTestConfig( + model_path=QWEN3_32B_WEIGHTS_PATH, gsm8k_accuracy=0.82 +) + +QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_FOR_TEST = ModelTestConfig( + model_path=QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH, gsm8k_accuracy=0.92 +) + +QWQ_32B_W8A8_WEIGHTS_FOR_TEST = ModelTestConfig( + model_path=QWQ_32B_W8A8_WEIGHTS_PATH, gsm8k_accuracy=0.59 +) + +# Default configuration for testing +DEFAULT_WEIGHTS_FOR_TEST = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_FOR_TEST + + +def run_command(cmd, shell=True): + """Execute system command and return stdout + + parameter: + cmd: command to execute + shell: + True, Execute command in shell + False, Commands are invoked directly without shell parsing + return: + The result of executing the command + """ + try: + result = subprocess.run( + cmd, shell=shell, capture_output=True, text=True, check=True + ) + return result.stdout + except subprocess.CalledProcessError as e: + print(f"execute command error: {e}") + return None + + +def get_benchmark_args( + base_url="", + backend="sglang", + dataset_name="", + dataset_path="", + tokenizer="", + num_prompts=500, + sharegpt_output_len=None, + random_input_len=4096, + random_output_len=2048, + sharegpt_context_len=None, + request_rate=float("inf"), + disable_stream=False, + disable_ignore_eos=False, + seed: int = 0, + device="auto", + pd_separated: bool = False, + lora_name=None, + lora_request_distribution="uniform", + lora_zipf_alpha=1.5, + gsp_num_groups=4, + gsp_prompts_per_group=4, + gsp_system_prompt_len=128, + gsp_question_len=32, + gsp_output_len=32, + gsp_num_turns=1, + header=None, + max_concurrency=None, +): + """Constructing the parameter objects needed for inference tests + + Parameters: + base_url: url + backend: Inference backend + dataset_name: Data set name + dataset_path: Dataset path + tokenizer: tokenizer + num_prompts: Total number of test requests + sharegpt_output_len: Output the number of tokens + random_input_len: The length of the randomly generated input prompt + random_output_len: The length of the randomly generated output prompt + sharegpt_context_len: Sharegpt dataset context length + request_rate: Request rate + disable_stream: Disable streaming output + disable_ignore_eos: Should eos_token be ignored? + seed: random seed + device: Device type + pd_separated: Enable PD separation + lora_name: LoRA fine-tuning model path + lora_request_distribution: LoRA request distribution strategy + lora_zipf_alpha: Control request distribution skewness + gsp_num_groups: Grouped Sequence Parallelism + gsp_prompts_per_group: Number of parallel prompts within each group + gsp_system_prompt_len: GSP system prompts length + gsp_question_len: GSP question length + gsp_output_len: GSP output length + gsp_num_turns: GSP Dialogue Rounds + header: HTTP request header + max_concurrency: Maximum number of concurrent requests + Returns: + The return parameter is the same as the input. + """ + + return SimpleNamespace( + backend=backend, + base_url=base_url, + host=None, + port=None, + dataset_name=dataset_name, + dataset_path=dataset_path, + model=None, + tokenizer=tokenizer, + num_prompts=num_prompts, + sharegpt_output_len=sharegpt_output_len, + sharegpt_context_len=sharegpt_context_len, + random_input_len=random_input_len, + random_output_len=random_output_len, + random_range_ratio=0.0, + request_rate=request_rate, + multi=None, + output_file=None, + disable_tqdm=False, + disable_stream=disable_stream, + return_logprob=False, + return_routed_experts=False, + seed=seed, + disable_ignore_eos=disable_ignore_eos, + extra_request_body=None, + apply_chat_template=False, + profile=None, + lora_name=lora_name, + lora_request_distribution=lora_request_distribution, + lora_zipf_alpha=lora_zipf_alpha, + prompt_suffix="", + device=device, + pd_separated=pd_separated, + gsp_num_groups=gsp_num_groups, + gsp_prompts_per_group=gsp_prompts_per_group, + gsp_system_prompt_len=gsp_system_prompt_len, + gsp_question_len=gsp_question_len, + gsp_output_len=gsp_output_len, + gsp_num_turns=gsp_num_turns, + header=header, + max_concurrency=max_concurrency, + ) + + +def run_bench_serving( + model, + num_prompts, + request_rate, + other_server_args, + dataset_name="random", + dataset_path="", + tokenizer=None, + random_input_len=4096, + random_output_len=2048, + sharegpt_context_len=None, + disable_stream=False, + disable_ignore_eos=False, + need_warmup=False, + seed: int = 0, + device="auto", + gsp_num_groups=None, + gsp_prompts_per_group=None, + gsp_system_prompt_len=None, + gsp_question_len=None, + gsp_output_len=None, + max_concurrency=None, + background_task: Optional[Callable[[str, asyncio.Event], Awaitable[None]]] = None, + lora_name: Optional[str] = None, +): + """Start the service and obtain the inference results. + + Parameters: + model: Model name + num_prompts: Total number of test requests + request_rate: Request rate + other_server_args: Additional configuration when starting the service + dataset_name: Data set name + dataset_path: Dataset path + tokenizer: tokenizer + random_input_len: The length of the randomly generated input prompt + random_output_len: The length of the randomly generated output prompt + sharegpt_context_len: Sharegpt dataset context length + disable_stream: Disable streaming output + disable_ignore_eos: Should eos_token be ignored? + need_warmup: Preheating required + seed: random seed + device: Device type + gsp_num_groups: Grouped Sequence Parallelism + gsp_prompts_per_group: Number of parallel prompts within each group + gsp_system_prompt_len: GSP system prompts length + gsp_question_len: GSP question length + gsp_output_len: GSP output length + max_concurrency: Maximum number of concurrent requests + background_task: Background tasks + lora_name: LoRA fine-tuning model path + Returns: + res: Number of requests successfully completed + + """ + + if device == "auto": + device = auto_config_device() + # Launch the server + base_url = DEFAULT_URL_FOR_TEST + process = popen_launch_server( + model, + base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_server_args, + ) + + # Run benchmark + args = get_benchmark_args( + base_url=base_url, + dataset_name=dataset_name, + dataset_path=dataset_path, + tokenizer=tokenizer, + num_prompts=num_prompts, + random_input_len=random_input_len, + random_output_len=random_output_len, + sharegpt_context_len=sharegpt_context_len, + request_rate=request_rate, + disable_stream=disable_stream, + disable_ignore_eos=disable_ignore_eos, + seed=seed, + device=device, + lora_name=lora_name, + gsp_num_groups=gsp_num_groups, + gsp_prompts_per_group=gsp_prompts_per_group, + gsp_system_prompt_len=gsp_system_prompt_len, + gsp_question_len=gsp_question_len, + gsp_output_len=gsp_output_len, + max_concurrency=max_concurrency, + ) + + async def _run(): + if need_warmup: + warmup_args = copy.deepcopy(args) + warmup_args.num_prompts = 16 + await asyncio.to_thread(run_benchmark, warmup_args) + + start_event = asyncio.Event() + stop_event = asyncio.Event() + task_handle = ( + asyncio.create_task(background_task(base_url, start_event, stop_event)) + if background_task + else None + ) + + try: + start_event.set() + result = await asyncio.to_thread(run_benchmark, args) + finally: + if task_handle: + stop_event.set() + await task_handle + + return result + + try: + res = asyncio.run(_run()) + finally: + kill_process_tree(process.pid) + + assert res["completed"] == num_prompts + return res diff --git a/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache.py b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache.py new file mode 100644 index 000000000..93a0a25e8 --- /dev/null +++ b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache.py @@ -0,0 +1,127 @@ +import unittest + +import requests + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import QWEN3_8B_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) + + +class TestNPUHierarchicalCache(CustomTestCase): + """Testcase: HierarchicalCache Test on Ascend NPU. + Cover scenarios: + 1. Long identical texts: cache can be reused + 2. Short identical texts: cache cannot be reused (page size limit) + 3. Different long texts: cache cannot be reused (prefix mismatch) + + [Test Category] HiCache + [Test Target] --enable-hierarchical-cache + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_8B_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.prefill_url = DEFAULT_URL_FOR_TEST + other_args = [ + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--mem-fraction-static", + 0.8, + "--tp-size", + 1, + "--enable-hierarchical-cache", + ] + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_args, + ) + cls.base_url += "/v1" + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_hierarchical_cache_reused_long_identical(self): + """Long identical texts should reuse HierarchicalCache""" + # Ultra-long repeated prompt (meets page size requirement) + long_text = "What is The capital of France?" * 36 + for i in range(2): + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/generate", + json={ + "text": long_text, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 10, + }, + }, + ) + self.assertEqual(response.status_code, 200) + cached_tokens = int(response.json()["meta_info"]["cached_tokens"]) + if i == 0: + # First request: no cache + self.assertEqual(cached_tokens, 0) + else: + # Second request: cache reused + self.assertGreater(cached_tokens, 0) + + def test_hierarchical_cache_not_reused_short_identical(self): + """Short identical texts should NOT reuse HierarchicalCache (page size limit)""" + # Short text prompt (does not meet page size requirement) + short_text = "who am i?" + for i in range(2): + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/generate", + json={ + "text": short_text, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 10, + }, + }, + ) + self.assertEqual(response.status_code, 200) + # No cache reuse for both requests + cached_tokens = int(response.json()["meta_info"]["cached_tokens"]) + self.assertEqual(cached_tokens, 0) + + def test_hierarchical_cache_not_reused_different_long(self): + """Different long texts should NOT reuse HierarchicalCache (text uniqueness)""" + # Two different long text prompts (both meet the page size requirement) + texts = [ + "Marie ordered one chicken meal that costs $12, 5 packs of milk that costs $3 each, 4 apples that cost $1.50 each, and some boxes of pizza. Marie paid a total of $50. How many boxes of pizza did Marie order if each box costs $8.50?" + * 8, + "Mishka bought 3 pairs of shorts, 3 pairs of pants, and 3 pairs of shoes. One pair of shorts costs $16.50. One pair of pants costs $22.50 and one pair of shoes costs $42. How many dollars did Mishka spend on all the clothing items?" + * 8, + ] + for text in texts: + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/generate", + json={ + "text": text, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 10, + }, + }, + ) + self.assertEqual(response.status_code, 200) + # No cache reuse for different text requests + cached_tokens = int(response.json()["meta_info"]["cached_tokens"]) + self.assertEqual(cached_tokens, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_mla.py b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_mla.py new file mode 100644 index 000000000..b02347d21 --- /dev/null +++ b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_mla.py @@ -0,0 +1,97 @@ +import unittest + +from sglang.test.ascend.test_ascend_utils import ( + DEEPSEEK_R1_0528_W8A8_WEIGHTS_PATH, + run_bench_serving, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import CustomTestCase + +register_npu_ci( + est_time=400, + suite="nightly-16-npu-a3", + nightly=True, + disabled="run failed", +) + + +class TestNpuHierarchicalCacheMla(CustomTestCase): + """The test used the DeepSeek-R1 model, with hierarchical cache enabled, and TTFT improved by 20%. + + [Test Category] HiCache + [Test Target] --enable-hierarchical-cache + """ + + def test_no_chunked_prefill_without_radix_cache(self): + TTFTS = [] + model = DEEPSEEK_R1_0528_W8A8_WEIGHTS_PATH + common_args = [ + [ + "--trust-remote-code", + "--tp-size", + 16, + "--mem-fraction-static", + 0.8, + "--max-running-requests", + 16, + "--disable-radix-cache", + "--chunked-prefill-size", + "512", + "--disable-cuda-graph", + "--quantization", + "modelslim", + "--attention-backend", + "ascend", + ], + [ + "--trust-remote-code", + "--tp-size", + 16, + "--mem-fraction-static", + 0.8, + "--max-running-requests", + 16, + "--chunked-prefill-size", + "512", + "--disable-cuda-graph", + "--quantization", + "modelslim", + "--attention-backend", + "ascend", + "--enable-hierarchical-cache", + "--hicache-ratio", + 5, + "--hicache-write-policy", + "write_back", + ], + ] + for common_arg in common_args: + other_args = common_arg + ( + [ + "--attention-backend", + "ascend", + ] + ) + res = run_bench_serving( + model=model, + dataset_name="generated-shared-prefix", + num_prompts=128, + random_input_len=3584, + random_output_len=1, + request_rate=float("inf"), + max_concurrency=16, + gsp_num_groups=1, + gsp_prompts_per_group=128, + gsp_system_prompt_len=1792, + gsp_question_len=1792, + gsp_output_len=1, + other_server_args=other_args, + ) + TTFT = res["mean_ttft_ms"] + TTFTS.append(TTFT) + + assert float(TTFTS[1]) <= 0.8 * float(TTFTS[0]) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_mutually_exclusive.py b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_mutually_exclusive.py new file mode 100644 index 000000000..1f29db1ac --- /dev/null +++ b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_mutually_exclusive.py @@ -0,0 +1,68 @@ +import os +import unittest + +from sglang.test.ascend.test_ascend_utils import QWEN3_8B_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci( + est_time=400, + suite="nightly-1-npu-a3", + nightly=True, +) + + +class TestNpuHierarchicalCacheMutuallyExclusive(CustomTestCase): + """Testcase: The test parameter disable-radix-cache and enable-hierarchical-cache + are mutually exclusive and cannot be used simultaneously. + + [Test Category] HiCache + [Test Target] --disable-radix-cache; --enable-hierarchical-cache + """ + + def test_hierarchical_cache_mutually_exclusive(self): + error_message = ( + "The arguments enable-hierarchical-cache and disable-radix-cache are mutually exclusive and " + "cannot be used at the same time. Please use only one of them." + ) + other_args = [ + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--mem-fraction-static", + 0.8, + "--tp-size", + 2, + "--enable-hierarchical-cache", + "--disable-radix-cache", + ] + out_log_file = open("./cache_out_log.txt", "w+", encoding="utf-8") + err_log_file = open("./cache_err_log.txt", "w+", encoding="utf-8") + try: + popen_launch_server( + QWEN3_8B_WEIGHTS_PATH, + DEFAULT_URL_FOR_TEST, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_args, + return_stdout_stderr=(out_log_file, err_log_file), + ) + except Exception as e: + print(f"Server launch failed as expects:{e}") + finally: + err_log_file.seek(0) + content = err_log_file.read() + # error_message information is recorded in the error log + self.assertIn(error_message, content) + out_log_file.close() + err_log_file.close() + os.remove("./cache_out_log.txt") + os.remove("./cache_err_log.txt") + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_ttft_mha.py b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_ttft_mha.py new file mode 100644 index 000000000..116bd2f5d --- /dev/null +++ b/test/registered/ascend/basic_function/HiCache/test_npu_hierarchical_cache_ttft_mha.py @@ -0,0 +1,89 @@ +import unittest + +from sglang.test.ascend.test_ascend_utils import ( + QWEN3_32B_WEIGHTS_PATH, + run_bench_serving, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import CustomTestCase + +register_npu_ci( + est_time=400, + suite="nightly-2-npu-a3", + nightly=True, + disabled="run failed", +) + + +class TestNpuHierarchicalCacheTTFT(CustomTestCase): + """The test used the Qwen3-32B model, with hierarchical cache enabled, and TTFT improved by 40%. + + [Test Category] HiCache + [Test Target] --enable-hierarchical-cache + """ + + def test_no_chunked_prefill_without_radix_cache(self): + TTFTS = [] + model = QWEN3_32B_WEIGHTS_PATH + common_args = [ + [ + "--trust-remote-code", + "--tp-size", + 2, + "--mem-fraction-static", + 0.8, + "--max-running-requests", + 16, + "--disable-radix-cache", + "--chunked-prefill-size", + "-1", + "--disable-cuda-graph", + ], + [ + "--trust-remote-code", + "--tp-size", + 2, + "--mem-fraction-static", + 0.8, + "--max-running-requests", + 16, + "--chunked-prefill-size", + "-1", + "--disable-cuda-graph", + "--enable-hierarchical-cache", + "--hicache-ratio", + 5, + "--hicache-write-policy", + "write_back", + ], + ] + for common_arg in common_args: + other_args = common_arg + ( + [ + "--attention-backend", + "ascend", + ] + ) + res = run_bench_serving( + model=model, + dataset_name="generated-shared-prefix", + num_prompts=128, + random_input_len=3584, + random_output_len=1, + request_rate=float("inf"), + max_concurrency=16, + gsp_num_groups=1, + gsp_prompts_per_group=128, + gsp_system_prompt_len=1792, + gsp_question_len=1792, + gsp_output_len=1, + other_server_args=other_args, + ) + TTFT = res["mean_ttft_ms"] + TTFTS.append(TTFT) + + assert float(TTFTS[1]) <= 0.6 * float(TTFTS[0]) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/HiCache/test_npu_radix_cache.py b/test/registered/ascend/basic_function/HiCache/test_npu_radix_cache.py new file mode 100644 index 000000000..ff5fc4e5e --- /dev/null +++ b/test/registered/ascend/basic_function/HiCache/test_npu_radix_cache.py @@ -0,0 +1,132 @@ +import unittest + +import requests + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import QWEN3_8B_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) + + +class TestNPURadixCache(CustomTestCase): + """Testcase: RadixCache Test on Ascend NPU. + Cover scenarios: + 1. Long identical texts: cache can be reused + 2. Short identical texts: cache cannot be reused (page size limit) + 3. Different long texts: cache cannot be reused (prefix mismatch) + + [Test Category] HiCache + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_8B_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + other_args = [ + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--mem-fraction-static", + 0.8, + "--tp-size", + 1, + ] + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_args, + ) + cls.base_url += "/v1" + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def tearDown(self): + try: + # Call the '/flush_cache' interface to clear RadixCache. + response = requests.post(f"{DEFAULT_URL_FOR_TEST}/flush_cache") + self.assertEqual(response.status_code, 200, "Failed to flush cache") + except Exception as e: + self.fail(f"Flush cache failed with error: {str(e)}") + + def test_radix_cache_reused_long_identical(self): + """Long identical texts should reuse RadixCache""" + # Ultra-long repeated prompt (meets page size requirement) + long_text = "What is The capital of France?" * 36 + for i in range(2): + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/generate", + json={ + "text": long_text, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 10, + }, + }, + ) + self.assertEqual(response.status_code, 200) + cached_tokens = int(response.json()["meta_info"]["cached_tokens"]) + if i == 0: + # First request: no cache + self.assertEqual(cached_tokens, 0) + else: + # Second request: cache reused + self.assertGreater(cached_tokens, 0) + + def test_radix_cache_not_reused_short_identical(self): + """Short identical texts should NOT reuse RadixCache (page size limit)""" + # Short text prompt (does not meet page size requirement) + short_text = "who am i?" + for _ in range(2): + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/generate", + json={ + "text": short_text, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 10, + }, + }, + ) + self.assertEqual(response.status_code, 200) + # No cache reuse for both requests + cached_tokens = int(response.json()["meta_info"]["cached_tokens"]) + self.assertEqual(cached_tokens, 0) + + def test_radix_cache_not_reused_different_long(self): + """Different long texts should NOT reuse RadixCache (text uniqueness)""" + # Two different long text prompts (both meet the page size requirement) + texts = [ + "Marie ordered one chicken meal that costs $12, 5 packs of milk that costs $3 each, 4 apples that cost $1.50 each, and some boxes of pizza. Marie paid a total of $50. How many boxes of pizza did Marie order if each box costs $8.50?" + * 8, + "Mishka bought 3 pairs of shorts, 3 pairs of pants, and 3 pairs of shoes. One pair of shorts costs $16.50. One pair of pants costs $22.50 and one pair of shoes costs $42. How many dollars did Mishka spend on all the clothing items?" + * 8, + ] + for text in texts: + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/generate", + json={ + "text": text, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 10, + }, + }, + ) + self.assertEqual(response.status_code, 200) + # No cache reuse for different text requests + cached_tokens = int(response.json()["meta_info"]["cached_tokens"]) + self.assertEqual(cached_tokens, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_deepseek_v3_2_w8a8.py b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_deepseek_v3_2_w8a8.py new file mode 100644 index 000000000..12076a09a --- /dev/null +++ b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_deepseek_v3_2_w8a8.py @@ -0,0 +1,108 @@ +import os +import unittest +from types import SimpleNamespace + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import DEEPSEEK_V3_2_W8A8_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.few_shot_gsm8k import run_eval as run_gsm8k +from sglang.test.run_eval import run_eval +from sglang.test.test_utils import ( + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=400, suite="nightly-16-npu-a3", nightly=True) + + +class TestDeepEpDeepseekV32(CustomTestCase): + """Testcase: Verify that for the DeepSeek V3.2 model in the single-machine colocation scenario, + its inference accuracy on the MMLU and GSM8K dataset meets the preset standard when the parameter --deepep-mode auto is configured. + + [Test Category] Expert Parallelism + [Test Target] --moe-a2a-backend deepep;--deepep-mode + """ + + @classmethod + def setUpClass(cls): + cls.model = DEEPSEEK_V3_2_W8A8_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=6000, + other_args=[ + "--trust-remote-code", + "--tp-size", + "16", + "--quantization", + "modelslim", + "--moe-a2a-backend", + "deepep", + "--deepep-mode", + "auto", + "--mem-fraction-static", + 0.82, + "--disable-cuda-graph", + "--disable-radix-cache", + "--context-length", + 40960, + "--max-prefill-tokens", + 40960, + "--max-total-tokens", + 40960, + ], + env={ + "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", + "STREAMS_PER_DEVICE": "32", + "SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK": "16", + "HCCL_BUFFSIZE": "1600", + "HCCL_OP_EXPANSION_MODE": "AIV", + "SGLANG_NPU_USE_MLAPO": "0", + "SGLANG_NPU_USE_MULTI_STREAM": "1", + "TASK_QUEUE_ENABLE": "0", + **os.environ, + }, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_mmlu(self): + expect_score = 0.85 + args = SimpleNamespace( + base_url=self.base_url, + model=self.model, + eval_name="mmlu", + num_examples=128, + num_threads=32, + ) + print("Starting mmlu test...") + metrics = run_eval(args) + self.assertGreater(metrics["score"], expect_score) + + def test_gsm8k(self): + expect_accuracy = 0.95 + args = SimpleNamespace( + num_shots=8, + data_path=None, + timeout=60000, + num_questions=200, + max_new_tokens=512, + parallel=128, + host="http://127.0.0.1", + port=int(self.base_url.split(":")[-1]), + ) + print("Starting gsm8k test...") + metrics = run_gsm8k(args) + self.assertGreaterEqual( + metrics["accuracy"], + expect_accuracy, + f'Accuracy of {self.model} is {str(metrics["accuracy"])}, is lower than {expect_accuracy}', + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_qwen3_480b.py b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_qwen3_480b.py new file mode 100644 index 000000000..0082a1bab --- /dev/null +++ b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_qwen3_480b.py @@ -0,0 +1,128 @@ +import os +import unittest +from types import SimpleNamespace + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import ( + QWEN3_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.few_shot_gsm8k import run_eval as run_gsm8k +from sglang.test.run_eval import run_eval +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=200, suite="nightly-16-npu-a3", nightly=True) + + +class TestDeepEpQwen(CustomTestCase): + """ + Testcase:Test the Qwen3-Coder-480B-A35B-Instruct-w8a8-QuaRot model with DeepEP's auto mode enabled, + and verify that there is no drop in accuracy compared to when DeepEP is not enabled. + + [Test Category] Expert Parallelism + [Test Target] --moe-a2a-backend, --deepep-mode + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=[ + "--trust-remote-code", + "--nnodes", + "1", + "--node-rank", + "0", + "--attention-backend", + "ascend", + "--device", + "npu", + "--quantization", + "modelslim", + "--max-running-requests", + 96, + "--context-length", + 8192, + "--dtype", + "bfloat16", + "--chunked-prefill-size", + 28672, + "--max-prefill-tokens", + 458880, + "--disable-radix-cache", + "--moe-a2a-backend", + "deepep", + "--deepep-mode", + "auto", + "--tp-size", + 16, + "--dp-size", + 4, + "--enable-dp-attention", + "--enable-dp-lm-head", + "--mem-fraction-static", + 0.7, + "--cuda-graph-bs", + 16, + 20, + 24, + ], + env={ + "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", + "SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT": "600", + "HCCL_BUFFSIZE": "2100", + "HCCL_OP_EXPANSION_MODE": "AIV", + **os.environ, + }, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_mmlu(self): + expect_score = 0.61 + + args = SimpleNamespace( + base_url=self.base_url, + model=self.model, + eval_name="mmlu", + num_examples=8, + num_threads=32, + ) + metrics = run_eval(args) + self.assertGreater(metrics["score"], expect_score) + + def test_gsm8k(self): + expect_accuracy = 0.91 + + host = "http://127.0.0.1" + port = int(self.base_url.split(":")[-1]) + args = SimpleNamespace( + num_shots=8, + data_path=None, + num_questions=200, + max_new_tokens=512, + parallel=128, + host=host, + port=port, + ) + metrics = run_gsm8k(args) + self.assertGreaterEqual( + metrics["accuracy"], + expect_accuracy, + f'Accuracy of {self.model} is {str(metrics["accuracy"])}, is lower than {expect_accuracy}', + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_qwen3_next.py b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_qwen3_next.py new file mode 100644 index 000000000..46259659e --- /dev/null +++ b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_auto_qwen3_next.py @@ -0,0 +1,118 @@ +import os +import unittest +from types import SimpleNamespace + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import ( + QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.few_shot_gsm8k import run_eval as run_gsm8k +from sglang.test.run_eval import run_eval +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci( + est_time=200, + suite="nightly-8-npu-a3", + nightly=True, + disabled="https://github.com/Ascend/sglang/issues/58", +) + + +class TestQwen3Next(CustomTestCase): + """ + Testcase:Test the Qwen3-Next-80B-A3B-Instruct-W8A8 model with DeepEP's auto mode enabled, and verify that there is + no drop in accuracy compared to when DeepEP is not enabled. + + [Test Category] Parameter + [Test Target] --moe-a2a-backend deepep, --deepep-mode auto + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=[ + "--trust-remote-code", + "--attention-backend", + "ascend", + "--device", + "npu", + "--tp-size", + 8, + "--mem-fraction-static", + 0.8, + "--max-running-requests", + 80, + "--watchdog-timeout", + 9000, + "--disable-radix-cache", + "--disable-cuda-graph", + "--max-prefill-tokens", + 28672, + "--max-total-tokens", + 450560, + "--moe-a2a-backend", + "deepep", + "--deepep-mode", + "auto", + "--chunked-prefill-size", + -1, + ], + env={ + "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", + "STREAMS_PER_DEVICE": "32", + "HCCL_OP_EXPANSION_MODE": "AIV", + "HCCL_ALGO": "level0:NA;level1:ring", + "SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK": "20", + "HCCL_BUFFSIZE": "2000", + **os.environ, + }, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_mmlu(self): + expect_score = 0.56 + args = SimpleNamespace( + base_url=self.base_url, + model=self.model, + eval_name="mmlu", + num_examples=8, + num_threads=32, + ) + metrics = run_eval(args) + self.assertGreater(metrics["score"], expect_score) + + def test_gsm8k(self): + expect_accuracy = 0.9 + args = SimpleNamespace( + num_shots=5, + data_path=None, + num_questions=200, + max_new_tokens=512, + parallel=128, + host="http://127.0.0.1", + port=int(self.base_url.split(":")[-1]), + ) + metrics = run_gsm8k(args) + self.assertGreaterEqual( + metrics["accuracy"], + expect_accuracy, + f'Accuracy of {self.model} is {str(metrics["accuracy"])}, is lower than {expect_accuracy}', + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_deepseek_v3_2_w8a8.py b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_deepseek_v3_2_w8a8.py new file mode 100644 index 000000000..3ff8e13a7 --- /dev/null +++ b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_deepseek_v3_2_w8a8.py @@ -0,0 +1,107 @@ +import os +import unittest +from types import SimpleNamespace + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import DEEPSEEK_V3_2_W8A8_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.few_shot_gsm8k import run_eval as run_gsm8k +from sglang.test.run_eval import run_eval +from sglang.test.test_utils import ( + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=200, suite="nightly-16-npu-a3", nightly=False) + + +class TestDeepEpDeepseekV32(CustomTestCase): + """Testcase: Verify that for the DeepSeek V3.2 model in the single-machine colocation scenario, + its inference accuracy on the MMLU and GSM8K dataset meets the preset standard when the parameter --deepep-mode low_latency is configured. + + [Test Category] Expert Parallelism + [Test Target] --moe-a2a-backend deepep;--deepep-mode + [Test Suggestions] Mixing deployment + low_latency mode is not recommended. + """ + + @classmethod + def setUpClass(cls): + cls.model = DEEPSEEK_V3_2_W8A8_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=6000, + other_args=[ + "--trust-remote-code", + "--tp-size", + "16", + "--quantization", + "modelslim", + "--moe-a2a-backend", + "deepep", + "--deepep-mode", + "low_latency", + "--mem-fraction-static", + 0.82, + "--disable-cuda-graph", + "--disable-radix-cache", + "--context-length", + 40960, + "--max-prefill-tokens", + 128, + "--max-total-tokens", + 40960, + ], + env={ + "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", + "STREAMS_PER_DEVICE": "32", + "SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK": "128", + "HCCL_BUFFSIZE": "2048", + "HCCL_OP_EXPANSION_MODE": "AIV", + "TASK_QUEUE_ENABLE": "0", + **os.environ, + }, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_mmlu(self): + expect_score = 0.85 + args = SimpleNamespace( + base_url=self.base_url, + model=self.model, + eval_name="mmlu", + num_examples=128, + num_threads=32, + ) + print("Starting mmlu test...") + metrics = run_eval(args) + self.assertGreater(metrics["score"], expect_score) + + def test_gsm8k(self): + expect_accuracy = 0.95 + args = SimpleNamespace( + num_shots=8, + data_path=None, + timeout=60000, + num_questions=200, + max_new_tokens=512, + parallel=128, + host="http://127.0.0.1", + port=int(self.base_url.split(":")[-1]), + ) + print("Starting gsm8k test...") + metrics = run_gsm8k(args) + self.assertGreaterEqual( + metrics["accuracy"], + expect_accuracy, + f'Accuracy of {self.model} is {str(metrics["accuracy"])}, is lower than {expect_accuracy}', + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_qwen3_480b.py b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_qwen3_480b.py new file mode 100644 index 000000000..bdfb14f03 --- /dev/null +++ b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_qwen3_480b.py @@ -0,0 +1,125 @@ +import os +import unittest +from types import SimpleNamespace + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import ( + QWEN3_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.few_shot_gsm8k import run_eval as run_gsm8k +from sglang.test.run_eval import run_eval +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=200, suite="nightly-16-npu-a3", nightly=False) + + +class TestDeepEpQwen(CustomTestCase): + """ + Testcase:Test the Qwen3-Coder-480B-A35B-Instruct-w8a8-QuaRot model with DeepEP's low_latency mode enabled, + and verify that there is no drop in accuracy compared to when DeepEP is not enabled. + + [Test Category] Expert Parallelism + [Test Target] --moe-a2a-backend, --deepep-mode + [Test Suggestions] Mixing deployment + low_latency mode is not recommended. + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=[ + "--trust-remote-code", + "--nnodes", + "1", + "--node-rank", + "0", + "--attention-backend", + "ascend", + "--device", + "npu", + "--quantization", + "modelslim", + "--max-running-requests", + 96, + "--context-length", + 8192, + "--dtype", + "bfloat16", + "--chunked-prefill-size", + 1024, + "--max-prefill-tokens", + 458880, + "--disable-radix-cache", + "--moe-a2a-backend", + "deepep", + "--deepep-mode", + "low_latency", + "--tp-size", + 16, + "--dp-size", + 4, + "--enable-dp-attention", + "--enable-dp-lm-head", + "--mem-fraction-static", + 0.7, + "--cuda-graph-bs", + 16, + 20, + 24, + ], + env={ + "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", + "SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT": "600", + "HCCL_BUFFSIZE": "2100", + "HCCL_OP_EXPANSION_MODE": "AIV", + **os.environ, + }, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_mmlu(self): + expect_score = 0.61 + args = SimpleNamespace( + base_url=self.base_url, + model=self.model, + eval_name="mmlu", + num_examples=8, + num_threads=32, + ) + metrics = run_eval(args) + self.assertGreater(metrics["score"], expect_score) + + def test_gsm8k(self): + expect_accuracy = 0.91 + args = SimpleNamespace( + num_shots=8, + data_path=None, + num_questions=200, + max_new_tokens=512, + parallel=128, + host="http://127.0.0.1", + port=int(self.base_url.split(":")[-1]), + ) + metrics = run_gsm8k(args) + self.assertGreaterEqual( + metrics["accuracy"], + expect_accuracy, + f'Accuracy of {self.model} is {str(metrics["accuracy"])}, is lower than {expect_accuracy}', + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_qwen3_next.py b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_qwen3_next.py new file mode 100644 index 000000000..a22b375fa --- /dev/null +++ b/test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep_low_latency_qwen3_next.py @@ -0,0 +1,118 @@ +import os +import unittest +from types import SimpleNamespace + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import ( + QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.few_shot_gsm8k import run_eval as run_gsm8k +from sglang.test.run_eval import run_eval +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci( + est_time=200, + suite="nightly-8-npu-a3", + nightly=True, + disabled="https://github.com/Ascend/sglang/issues/58", +) + + +class TestQwen3Next(CustomTestCase): + """ + Testcase:Test the Qwen3-Next-80B-A3B-Instruct-W8A8 model with DeepEP's low_latency mode enabled, and verify that + there is no drop in accuracy compared to when DeepEP is not enabled. + + [Test Category] Parameter + [Test Target] --moe-a2a-backend deepep, --deepep-mode low_latency + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=[ + "--trust-remote-code", + "--attention-backend", + "ascend", + "--device", + "npu", + "--tp-size", + 8, + "--mem-fraction-static", + 0.8, + "--max-running-requests", + 80, + "--watchdog-timeout", + 9000, + "--disable-radix-cache", + "--disable-cuda-graph", + "--chunked-prefill-size", + 1024, + "--max-prefill-tokens", + 28672, + "--max-total-tokens", + 450560, + "--moe-a2a-backend", + "deepep", + "--deepep-mode", + "low_latency", + ], + env={ + "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", + "STREAMS_PER_DEVICE": "32", + "HCCL_OP_EXPANSION_MODE": "AIV", + "HCCL_ALGO": "level0:NA;level1:ring", + "SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK": "20", + "HCCL_BUFFSIZE": "2048", + **os.environ, + }, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_mmlu(self): + expect_score = 0.56 + args = SimpleNamespace( + base_url=self.base_url, + model=self.model, + eval_name="mmlu", + num_examples=8, + num_threads=32, + ) + metrics = run_eval(args) + self.assertGreater(metrics["score"], expect_score) + + def test_gsm8k(self): + expect_accuracy = 0.9 + args = SimpleNamespace( + num_shots=5, + data_path=None, + num_questions=200, + max_new_tokens=512, + parallel=128, + host="http://127.0.0.1", + port=int(self.base_url.split(":")[-1]), + ) + metrics = run_gsm8k(args) + self.assertGreaterEqual( + metrics["accuracy"], + expect_accuracy, + f'Accuracy of {self.model} is {str(metrics["accuracy"])}, is lower than {expect_accuracy}', + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/deepseek_coder.json b/test/registered/ascend/basic_function/parameter/deepseek_coder.json similarity index 100% rename from test/registered/ascend/basic_function/deepseek_coder.json rename to test/registered/ascend/basic_function/parameter/deepseek_coder.json diff --git a/test/registered/ascend/basic_function/test_ascend_fim_completion.py b/test/registered/ascend/basic_function/parameter/test_npu_fim_completion.py similarity index 90% rename from test/registered/ascend/basic_function/test_ascend_fim_completion.py rename to test/registered/ascend/basic_function/parameter/test_npu_fim_completion.py index 4ba0e7c94..a9c532cb5 100644 --- a/test/registered/ascend/basic_function/test_ascend_fim_completion.py +++ b/test/registered/ascend/basic_function/parameter/test_npu_fim_completion.py @@ -4,6 +4,10 @@ import openai from sglang.srt.utils import kill_process_tree from sglang.srt.utils.hf_transformers_utils import get_tokenizer +from sglang.test.ascend.test_ascend_utils import ( + DEEPSEEK_CODER_1_3_B_BASE_PATH, + DEEPSEEK_CODER_JSON_PATH, +) from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import ( DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, @@ -12,7 +16,12 @@ from sglang.test.test_utils import ( popen_launch_server, ) -register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-1-npu-a3", + nightly=True, + disabled="run failed", +) class TestFimCompletion(CustomTestCase): @@ -22,7 +31,7 @@ class TestFimCompletion(CustomTestCase): [Test Target] --completion-template """ - model = "/root/.cache/modelscope/hub/models/deepseek-ai/deepseek-coder-1.3b-base" + model = DEEPSEEK_CODER_1_3_B_BASE_PATH other_args = [ "--completion-template", "deepseek_coder", @@ -86,7 +95,7 @@ class TestFimCompletion(CustomTestCase): class TestFimCompletionJson(TestFimCompletion): other_args = [ "--completion-template", - "./deepseek_coder.json", + DEEPSEEK_CODER_JSON_PATH, "--attention-backend", "ascend", "--disable-cuda-graph", diff --git a/test/registered/ascend/basic_function/parameter/test_ascend_log_level.py b/test/registered/ascend/basic_function/parameter/test_npu_log_level.py similarity index 100% rename from test/registered/ascend/basic_function/parameter/test_ascend_log_level.py rename to test/registered/ascend/basic_function/parameter/test_npu_log_level.py diff --git a/test/registered/ascend/basic_function/parameter/test_ascend_no_chunked_prefill.py b/test/registered/ascend/basic_function/parameter/test_npu_no_chunked_prefill.py similarity index 100% rename from test/registered/ascend/basic_function/parameter/test_ascend_no_chunked_prefill.py rename to test/registered/ascend/basic_function/parameter/test_npu_no_chunked_prefill.py diff --git a/test/registered/ascend/basic_function/parameter/test_ascend_no_overlap_scheduler.py b/test/registered/ascend/basic_function/parameter/test_npu_no_overlap_scheduler.py similarity index 100% rename from test/registered/ascend/basic_function/parameter/test_ascend_no_overlap_scheduler.py rename to test/registered/ascend/basic_function/parameter/test_npu_no_overlap_scheduler.py diff --git a/test/registered/ascend/basic_function/parameter/test_ascend_original_logprobs.py b/test/registered/ascend/basic_function/parameter/test_npu_original_logprobs.py similarity index 100% rename from test/registered/ascend/basic_function/parameter/test_ascend_original_logprobs.py rename to test/registered/ascend/basic_function/parameter/test_npu_original_logprobs.py diff --git a/test/registered/ascend/basic_function/parameter/test_ascend_warmups.py b/test/registered/ascend/basic_function/parameter/test_npu_warmups.py similarity index 100% rename from test/registered/ascend/basic_function/parameter/test_ascend_warmups.py rename to test/registered/ascend/basic_function/parameter/test_npu_warmups.py diff --git a/test/registered/ascend/basic_function/speculative_inference/test_npu_eagle3.py b/test/registered/ascend/basic_function/speculative_inference/test_npu_eagle3.py new file mode 100644 index 000000000..efbb5f738 --- /dev/null +++ b/test/registered/ascend/basic_function/speculative_inference/test_npu_eagle3.py @@ -0,0 +1,101 @@ +import os +import unittest +from types import SimpleNamespace +from urllib.parse import urlparse + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import ( + QWEN3_8B_EAGLE3_WEIGHTS_PATH, + QWEN3_8B_WEIGHTS_PATH, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.few_shot_gsm8k import run_eval as run_eval_few_shot_gsm8k +from sglang.test.test_utils import ( + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) + + +class TestNpuEagle3(CustomTestCase): + """Testcase: Verify GSM8K inference accuracy ≥0.81 for model with specified EAGLE3 speculative inference parameters. + + [Test Category] Speculative Decoding + [Test Target] --speculative-draft-model-quantization; --speculative-algorithm; --speculative-draft-model-path; --speculative-num-steps; --speculative-eagle-topk; --speculative-num-draft-tokens; --speculative-attention-mode + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_8B_WEIGHTS_PATH + cls.accuracy = 0.81 + cls.base_url = DEFAULT_URL_FOR_TEST + cls.url = urlparse(DEFAULT_URL_FOR_TEST) + + cls.common_args = [ + "--trust-remote-code", + "--attention-backend", + "ascend", + "--disable-radix-cache", + "--speculative-draft-model-quantization", + "unquant", + "--speculative-algorithm", + "EAGLE3", + "--speculative-draft-model-path", + QWEN3_8B_EAGLE3_WEIGHTS_PATH, + "--speculative-num-steps", + "4", + "--speculative-eagle-topk", + "1", + "--speculative-num-draft-tokens", + "5", + "--speculative-attention-mode", + "decode", + "--tp-size", + "1", + "--mem-fraction-static", + "0.7", + "--disable-cuda-graph", + "--dtype", + "bfloat16", + ] + + cls.extra_envs = { + "SGLANG_ENABLE_OVERLAP_PLAN_STREAM": "1", + "SGLANG_ENABLE_SPEC_V2": "1", + } + os.environ.update(cls.extra_envs) + + def test_gsm8k(self): + process = popen_launch_server( + self.model, + self.base_url, + timeout=1500, + other_args=[ + *self.common_args, + ], + ) + + try: + args = SimpleNamespace( + num_shots=5, + data_path=None, + num_questions=1319, + max_new_tokens=512, + parallel=128, + host=f"http://{self.url.hostname}", + port=int(self.url.port), + ) + + metrics = run_eval_few_shot_gsm8k(args) + self.assertGreaterEqual( + metrics["accuracy"], + self.accuracy, + ) + finally: + kill_process_tree(process.pid) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/embedding_models/test_ascend_embedding_models.py b/test/registered/ascend/embedding_models/test_ascend_embedding_models.py deleted file mode 100644 index 33c5e3cfd..000000000 --- a/test/registered/ascend/embedding_models/test_ascend_embedding_models.py +++ /dev/null @@ -1,108 +0,0 @@ -import multiprocessing as mp -import unittest -from typing import Optional - -import torch -from transformers import AutoConfig, AutoTokenizer - -from sglang.test.ci.ci_register import register_npu_ci -from sglang.test.runners import DEFAULT_PROMPTS, HFRunner, SRTRunner -from sglang.test.test_utils import CustomTestCase, get_similarities - -register_npu_ci( - est_time=400, - suite="nightly-1-npu-a3", - nightly=True, - disabled="embeddings are not all close", -) - - -MODELS = [ - ("/root/.cache/modelscope/hub/models/iic/gte_Qwen2-1.5B-instruct", 1, 1e-5), - ("/root/.cache/modelscope/hub/models/Qwen/Qwen3-Embedding-8B", 1, 1e-5), -] -TORCH_DTYPES = [torch.bfloat16] - - -class TestEmbeddingModels(CustomTestCase): - - @classmethod - def setUpClass(cls): - mp.set_start_method("spawn", force=True) - - def _truncate_prompts(self, prompts, model_path): - config = AutoConfig.from_pretrained(model_path) - max_length = getattr(config, "max_position_embeddings", 2048) - - tokenizer = AutoTokenizer.from_pretrained(model_path) - - truncated_prompts = [] - for prompt in prompts: - tokens = tokenizer(prompt, return_tensors="pt", truncation=False) - if len(tokens.input_ids[0]) > max_length: - truncated_text = tokenizer.decode( - tokens.input_ids[0][: max_length - 1], skip_special_tokens=True - ) - truncated_prompts.append(truncated_text) - else: - truncated_prompts.append(prompt) - return truncated_prompts - - def assert_close_prefill_logits( - self, - prompts, - model_path, - tp_size, - torch_dtype, - prefill_tolerance, - matryoshka_dim: Optional[int] = None, - ) -> None: - truncated_prompts = self._truncate_prompts(prompts, model_path) - - with HFRunner( - model_path, - torch_dtype=torch_dtype, - model_type="embedding", - matryoshka_dim=matryoshka_dim, - ) as hf_runner: - hf_outputs = hf_runner.forward(truncated_prompts) - - attention_backend = "ascend" - with SRTRunner( - model_path, - tp_size=tp_size, - torch_dtype=torch_dtype, - model_type="embedding", - attention_backend=attention_backend, - json_model_override_args=( - {"matryoshka_dimensions": [matryoshka_dim]} if matryoshka_dim else None - ), - ) as srt_runner: - srt_outputs = srt_runner.forward( - truncated_prompts, dimensions=matryoshka_dim - ) - - for i in range(len(prompts)): - hf_logits = torch.Tensor(hf_outputs.embed_logits[i]) - srt_logits = torch.Tensor(srt_outputs.embed_logits[i]) - - similarity = torch.tensor(get_similarities(hf_logits, srt_logits)) - print("similarity diff", abs(similarity - 1)) - - if len(prompts[i]) <= 1000: - assert torch.all( - abs(similarity - 1) < prefill_tolerance - ), "embeddings are not all close" - - def test_prefill_logits(self): - models_to_test = MODELS - - for model, tp_size, prefill_tolerance in models_to_test: - for torch_dtype in TORCH_DTYPES: - self.assert_close_prefill_logits( - DEFAULT_PROMPTS, model, tp_size, torch_dtype, prefill_tolerance - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/registered/ascend/embedding_models/test_ascend_bge_large_en_v1_5.py b/test/registered/ascend/embedding_models/test_npu_bge_large_en_v1_5.py similarity index 100% rename from test/registered/ascend/embedding_models/test_ascend_bge_large_en_v1_5.py rename to test/registered/ascend/embedding_models/test_npu_bge_large_en_v1_5.py diff --git a/test/registered/ascend/interface/test_npu_api.py b/test/registered/ascend/interface/test_npu_api.py new file mode 100644 index 000000000..81eaef4f8 --- /dev/null +++ b/test/registered/ascend/interface/test_npu_api.py @@ -0,0 +1,732 @@ +import json +import logging +import os +import shutil +import unittest + +import requests +from transformers import AutoTokenizer + +from sglang.srt.environ import envs +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +# Global variables: Manage server process and initialization status +GLOBAL_SERVER_PROCESS = None +GLOBAL_SERVER_INITIALIZED = False +OUTPUT_DIR = "./profiler_dir" + +register_npu_ci(est_time=1600, suite="nightly-npu-a3-merged", nightly=True) + + +class TestNpuApi(CustomTestCase): + """Testcase: Verify that the basic functions of the API interfaces work properly and the returned parameters are consistent with the configurations. + + [Test Category] Interface + [Test Target] /health; /health_generate; /ping; /model_info; /server_info; /get_load; /v1/models; /v1/models/{model:path}; /generate + """ + + @classmethod + def setUpClass(cls): + global GLOBAL_SERVER_PROCESS, GLOBAL_SERVER_INITIALIZED + # Start server only if not initialized + if not GLOBAL_SERVER_INITIALIZED: + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + other_args = [ + "--attention-backend", + "ascend", + "--enable-return-hidden-states", + ] + # Start server and save to global variable + GLOBAL_SERVER_PROCESS = popen_launch_server( + cls.model, + DEFAULT_URL_FOR_TEST, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_args, + ) + GLOBAL_SERVER_INITIALIZED = True + cls.base_url = DEFAULT_URL_FOR_TEST + + @classmethod + def tearDownClass(cls): + # First class does not terminate server + pass + + def test_api_health(self): + response = requests.get(f"{self.base_url}/health") + self.assertEqual(response.status_code, 200) + + def test_api_health_generate(self): + response = requests.get(f"{self.base_url}/health_generate") + self.assertEqual(response.status_code, 200) + + def test_api_ping(self): + response = requests.get(f"{self.base_url}/ping") + self.assertEqual(response.status_code, 200) + + def test_api_model_info(self): + response = requests.get(f"{self.base_url}/model_info") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["model_path"], self.model) + self.assertEqual(response.json()["tokenizer_path"], self.model) + self.assertTrue(response.json()["is_generation"]) + self.assertIsNone(response.json()["preferred_sampling_params"]) + self.assertEqual(response.json()["weight_version"], "default") + self.assertFalse(response.json()["has_image_understanding"]) + self.assertFalse(response.json()["has_audio_understanding"]) + self.assertEqual(response.json()["model_type"], "llama") + self.assertEqual(response.json()["architectures"][0], "LlamaForCausalLM") + + def test_api_server_info(self): + response = requests.get(f"{self.base_url}/server_info") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["model_path"], self.model) + self.assertEqual(response.json()["tokenizer_path"], self.model) + + def test_api_get_load(self): + response = requests.get(f"{self.base_url}/get_load") + self.assertEqual(response.status_code, 200) + self.assertIsNone(response.json()[0]["rid"]) + self.assertIsNone(response.json()[0]["http_worker_ipc"]) + self.assertIsNone(response.json()[0]["dp_rank"]) + self.assertGreaterEqual(response.json()[0]["num_reqs"], 0) + self.assertGreaterEqual(response.json()[0]["num_waiting_reqs"], 0) + self.assertGreaterEqual(response.json()[0]["num_tokens"], 0) + + def test_api_v1_models(self): + response = requests.get(f"{self.base_url}/v1/models") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["data"][0]["id"], self.model) + self.assertEqual(response.json()["data"][0]["object"], "model") + self.assertEqual(response.json()["data"][0]["owned_by"], "sglang") + self.assertEqual(response.json()["data"][0]["root"], self.model) + self.assertEqual(response.json()["data"][0]["max_model_len"], 131072) + + def test_api_v1_models_path(self): + response = requests.get(f"{self.base_url}/v1/models/{self.model}") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["id"], self.model) + self.assertEqual(response.json()["object"], "model") + self.assertEqual(response.json()["owned_by"], "sglang") + self.assertEqual(response.json()["root"], self.model) + self.assertEqual(response.json()["max_model_len"], 131072) + + def test_api_generate_single_text(self): + response = requests.post( + f"{self.base_url}/generate", + json={ + "rid": "req_001", + "text": "The capital of France is", + "sampling_params": { + "temperature": 0, + "max_new_tokens": 20, + }, + "return_logprob": True, + "stream": False, + "return_hidden_states": True, + }, + ) + self.assertEqual(response.status_code, 200) + meta_info_keys = response.json()["meta_info"].keys() + self.assertEqual("req_001", response.json()["meta_info"]["id"]) + self.assertIn("Paris", response.json()["text"]) + self.assertEqual(20, response.json()["meta_info"]["completion_tokens"]) + self.assertIn("input_token_logprobs", meta_info_keys) + self.assertIn("output_token_logprobs", meta_info_keys) + self.assertIn("hidden_states", meta_info_keys) + + def test_api_generate_batch_texts(self): + rids = ["req_1", "req_2"] + texts = [ + "The capital of France is", + "What is the best time of year to visit Japan for cherry blossoms?", + ] + response = requests.post( + f"{self.base_url}/generate", + json={ + "rid": rids, + "text": texts, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 20, + }, + "return_logprob": False, + "stream": False, + "return_hidden_states": False, + }, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual("req_1", response.json()[0]["meta_info"]["id"]) + self.assertIn("Paris", response.json()[0]["text"]) + self.assertEqual("req_2", response.json()[1]["meta_info"]["id"]) + self.assertIn("Japan", response.json()[1]["text"]) + + def test_api_generate_temperature(self): + response = requests.post( + f"{self.base_url}/generate", + json={ + "text": "The capital of France is", + "sampling_params": { + "temperature": 5, + "max_new_tokens": 20, + }, + }, + ) + self.assertEqual(response.status_code, 200) + text1 = response.json()["text"] + response = requests.post( + f"{self.base_url}/generate", + json={ + "text": "The capital of France is", + "sampling_params": { + "temperature": 5, + "max_new_tokens": 20, + }, + }, + ) + self.assertEqual(response.status_code, 200) + text2 = response.json()["text"] + self.assertNotEqual(text2, text1) + + def test_api_generate_input_ids(self): + text = "The capital of France is" + tokenizer = AutoTokenizer.from_pretrained(self.model) + input_ids = tokenizer(text, return_tensors="pt")["input_ids"][0].tolist() + response = requests.post( + f"{self.base_url}/generate", + json={ + "rid": "req_002", + "input_ids": input_ids, + "sampling_params": { + "temperature": 0, + "max_new_tokens": 10, + }, + "return_logprob": False, + "stream": True, + "return_hidden_states": False, + }, + ) + self.assertEqual(response.status_code, 200) + lines = response.text.strip().split("\n") + self.assertGreaterEqual(len(lines), 10) + json_data = lines[-3][6:] + data = json.loads(json_data) + meta_info_keys = data["meta_info"].keys() + self.assertEqual("req_002", data["meta_info"]["id"]) + self.assertIn("Paris", data["text"]) + self.assertEqual(10, data["meta_info"]["completion_tokens"]) + self.assertNotIn("input_token_logprobs", meta_info_keys) + self.assertNotIn("output_token_logprobs", meta_info_keys) + self.assertNotIn("hidden_states", meta_info_keys) + + +class TestChatCompletionsInterface(CustomTestCase): + """Testcase: The test is to verify whether the functions of each parameter of the v1/chat/completions interface are normal. + + [Test Category] Interface + [Test Target] v1/chat/completions + """ + + @classmethod + def setUpClass(cls): + # Skip initialization, directly reuse global server + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.additional_chat_kwargs = {} + + @classmethod + def tearDownClass(cls): + # Do not terminate server + pass + + def test_model_and_messages(self): + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [{"role": "user", "content": "Hello"}], + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + data = response.json() + self.assertEqual(data["model"], self.model) + self.assertIsNotNone(data["choices"][0]["message"]["reasoning_content"]) + + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "messages": [{"role": "user", "content": "Hello"}], + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + data = response.json() + self.assertEqual(data["model"], "default") + self.assertIsNotNone(data["choices"][0]["message"]["reasoning_content"]) + + def test_max_completion_tokens(self): + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "messages": [{"role": "user", "content": "Hello"}], + "max_completion_tokens": 1, + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertEqual(response.json()["choices"][0]["finish_reason"], "length") + + def test_stream(self): + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [{"role": "user", "content": "Hello"}], + "stream": True, + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + has_reasoning = False + has_content = False + + for line in response.iter_lines(): + if line: + line = line.decode("utf-8") + if line.startswith("data:") and not line.startswith("data: [DONE]"): + data = json.loads(line[6:]) + if "choices" in data and len(data["choices"]) > 0: + delta = data["choices"][0].get("delta", {}) + if "reasoning_content" in delta and delta["reasoning_content"]: + has_reasoning = True + if "content" in delta and delta["content"]: + has_content = True + + self.assertTrue( + has_reasoning, "Reasoning content not included in stream response" + ) + self.assertTrue(has_content, "Normal content not included in stream response") + + def test_temperature(self): + response1 = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [ + { + "role": "user", + "content": "Please write a five-character quatrain for me.", + } + ], + "temperature": 0, + }, + ) + self.assertEqual(response1.status_code, 200, f"Failed with: {response1.text}") + content1 = response1.json()["choices"][0]["message"]["content"] + + response2 = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [ + { + "role": "user", + "content": "Please write a five-character quatrain for me.", + } + ], + "temperature": 0, + }, + ) + self.assertEqual(response2.status_code, 200, f"Failed with: {response2.text}") + content2 = response2.json()["choices"][0]["message"]["content"] + self.assertEqual(content1, content2) + + response3 = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [ + { + "role": "user", + "content": "Please write a five-character quatrain for me.", + } + ], + "temperature": 2, + }, + ) + self.assertEqual(response3.status_code, 200, f"Failed with: {response3.text}") + content3 = response3.json()["choices"][0]["message"]["content"] + + response4 = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [ + { + "role": "user", + "content": "Please write a five-character quatrain for me.", + } + ], + "temperature": 2, + }, + ) + self.assertEqual(response4.status_code, 200, f"Failed with: {response4.text}") + content4 = response4.json()["choices"][0]["message"]["content"] + self.assertNotEqual(content3, content4) + + def test_return_hidden_states(self): + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [{"role": "user", "content": "Hello"}], + "return_hidden_states": True, + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertIn("hidden_states", response.json()["choices"][0]) + + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [{"role": "user", "content": "Hello"}], + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertNotIn("hidden_states", response.json()["choices"][0]) + + def test_top_k(self): + response1 = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [ + { + "role": "user", + "content": "Please write a five-character quatrain for me.", + } + ], + "top_k": 20, + }, + ) + self.assertEqual(response1.status_code, 200, f"Failed with: {response1.text}") + content1 = response1.json()["choices"][0]["message"]["content"] + + response2 = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [ + { + "role": "user", + "content": "Please write a five-character quatrain for me.", + } + ], + "top_k": 20, + }, + ) + self.assertEqual(response2.status_code, 200, f"Failed with: {response2.text}") + content2 = response2.json()["choices"][0]["message"]["content"] + self.assertNotEqual(content1, content2) + + def test_stop_token_ids(self): + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [{"role": "user", "content": "Hello"}], + "stop_token_ids": [1, 13], + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertEqual(response.json()["choices"][0]["matched_stop"], 13) + + def test_rid(self): + response = requests.post( + f"{self.base_url}/v1/chat/completions", + json={ + "model": self.model, + "messages": [{"role": "user", "content": "Hello"}], + "rid": "sssss", + }, + ) + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertEqual(response.json()["id"], "sssss") + + +class TestEnableThinking(CustomTestCase): + """Testcase: The test is to verify whether the functions of each parameter of the v1/completions interface are normal. + + [Test Category] Interface + [Test Target] v1/completions + """ + + @classmethod + def setUpClass(cls): + # Skip initialization, directly reuse global server + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.additional_chat_kwargs = {} + logging.basicConfig(level=logging.INFO) # Initialize logging + + @classmethod + def tearDownClass(cls): + # Do not terminate server + pass + + def test_model_parameters_model(self): + response = requests.post( + f"{self.base_url}/v1/completions", + json={"model": self.model, "prompt": "who are you?"}, + ) + logging.info(f"response.json:{response.json()}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + data = response.json() + self.assertEqual(data["model"], self.model) + + def test_model_parameters_prompt(self): + # str format + response = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?"}, + ) + logging.info(f"response.json:{response.json()}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + + # list[int] format + list_int = [1, 2, 3, 4] + response1 = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": list_int}, + ) + logging.info(f"response1.json:{response1.json()}") + self.assertEqual(response1.status_code, 200, f"Failed with: {response1.text}") + + # list[str] format + list_str = ["who is you", "hello world", "ABChello"] + response2 = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": list_str}, + ) + logging.info(f"response2.json:{response2.json()}") + self.assertEqual(response2.status_code, 200, f"Failed with: {response2.text}") + + # list[list[int]] format + list_list_int = [[14990], [1350, 445, 14990, 1879, 899], [14623, 525, 498, 30]] + response3 = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": list_list_int}, + ) + logging.info(f"response3.json:{response3.json()}") + self.assertEqual(response3.status_code, 200, f"Failed with: {response3.text}") + + def test_model_parameters_max_tokens(self): + response = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "max_tokens": 1}, + ) + logging.info(f"response.json:{response.json()}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + logging.info(f"finish_reason:{response.json()['choices'][0]['finish_reason']}") + self.assertEqual(response.json()["choices"][0]["finish_reason"], "length") + + def test_model_parameters_stream(self): + response = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "stream": True}, + ) + logging.info(f"response.text:{response.text}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + + has_text = False + logging.info("\n=== Stream With Reasoning ===") + for line in response.iter_lines(): + if line: + line = line.decode("utf-8") + if line.startswith("data:") and not line.startswith("data: [DONE]"): + data = json.loads(line[6:]) + if "choices" in data and len(data["choices"]) > 0: + if "text" in data["choices"][0]: + has_text = True + self.assertTrue(has_text, "Text content not included in stream response") + + def test_model_parameters_temperature(self): + response = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "temperature": 0}, + ) + logging.info(f"response.json:{response.json()}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + + response1 = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "temperature": 0}, + ) + logging.info(f"response1.json:{response1.json()}") + self.assertEqual(response1.status_code, 200, f"Failed with: {response1.text}") + self.assertEqual( + response.json()["choices"][0]["text"], + response1.json()["choices"][0]["text"], + ) + + response2 = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "temperature": 2}, + ) + logging.info(f"response2.json:{response2.json()}") + self.assertEqual(response2.status_code, 200, f"Failed with: {response2.text}") + + response3 = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "temperature": 2}, + ) + logging.info(f"response3.json:{response3.json()}") + self.assertEqual(response3.status_code, 200, f"Failed with: {response3.text}") + self.assertNotEqual( + response2.json()["choices"][0]["text"], + response3.json()["choices"][0]["text"], + ) + + def test_model_parameters_hidden_states(self): + response = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "return_hidden_states": True}, + ) + logging.info(f"response.json:{response.json()}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertIn("hidden_states", response.json()["choices"][0]) + + def test_model_parameters_top_k(self): + response = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "top_k": 20}, + ) + logging.info(f"response.json:{response.json()}") + logging.info(f"response.text:{response.text}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + + response1 = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "top_k": 20}, + ) + logging.info(f"response1.json:{response1.json()}") + logging.info(f"response1.text:{response1.text}") + self.assertEqual(response1.status_code, 200, f"Failed with: {response1.text}") + self.assertNotEqual( + response.json()["choices"][0]["text"], + response1.json()["choices"][0]["text"], + ) + + def test_model_parameters_stop_token_ids(self): + list_ids = [13] + response = requests.post( + f"{self.base_url}/v1/completions", + json={ + "prompt": "who are you?", + "stop_token_ids": list_ids, + "max_tokens": 1024, + }, + ) + logging.info(f"response.json:{response.json()}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertEqual(response.json()["choices"][0]["matched_stop"], 13) + + def test_model_parameters_rid(self): + response = requests.post( + f"{self.base_url}/v1/completions", + json={"prompt": "who are you?", "rid": "10086"}, + ) + logging.info(f"response.json:{response.json()}") + self.assertEqual(response.status_code, 200, f"Failed with: {response.text}") + self.assertEqual(response.json()["id"], "10086") + + +class TestStartProfile(CustomTestCase): + """Testcase: Verify the correctness of /start_profile API with different parameter combinations (start_step/num_steps) on Ascend NPU backend. + + [Test Category] Interface + [Test Target] /start_profile + """ + + @classmethod + def setUpClass(cls): + # Skip initialization, reuse global server + configure profiler directory + envs.SGLANG_TORCH_PROFILER_DIR.set(OUTPUT_DIR) + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.additional_chat_kwargs = {} + + @classmethod + def tearDownClass(cls): + # Terminate server in last class + global GLOBAL_SERVER_PROCESS + if GLOBAL_SERVER_PROCESS: + kill_process_tree(GLOBAL_SERVER_PROCESS.pid) + GLOBAL_SERVER_PROCESS = None + + def setUp(self): + self._clear_profile_dir() + + def test_start_profile_1(self): + self._start_profile(start_step="15", num_steps=5) + self._post_request() + self._check_non_empty_profile_dir() + + def test_start_profile_2(self): + self._clear_profile_dir() + self._check_empty_profile_dir() + self._start_profile() + self._post_request() + requests.post(f"{self.base_url}/stop_profile") + self._check_non_empty_profile_dir() + + def test_start_profile_3(self): + self._start_profile(num_steps=5) + self._post_request() + self._check_non_empty_profile_dir() + + def _start_profile(self, **kwargs): + response = requests.post( + f"{self.base_url}/start_profile", + json=kwargs if kwargs else None, + ) + self.assertEqual(response.status_code, 200) + return response + + def _post_request(self): + response = requests.post( + f"{self.base_url}/generate", + json={ + "text": "The capital of France is", + "sampling_params": { + "temperature": 0, + "max_new_tokens": 32, + }, + }, + ) + self.assertEqual(response.status_code, 200) + + def _clear_profile_dir(self): + if os.path.isdir(OUTPUT_DIR): + shutil.rmtree(OUTPUT_DIR) + + def _check_non_empty_profile_dir(self): + self.assertTrue(os.path.isdir(OUTPUT_DIR), "Profiler directory does not exist") + self.assertNotEqual( + len(os.listdir(OUTPUT_DIR)), 0, "Profiler directory is empty" + ) + + def _check_empty_profile_dir(self): + if os.path.isdir(OUTPUT_DIR): + self.assertEqual( + len(os.listdir(OUTPUT_DIR)), 0, "Profiler directory is not empty" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/interface/test_npu_api_abort_request.py b/test/registered/ascend/interface/test_npu_api_abort_request.py new file mode 100644 index 000000000..4a5baaef7 --- /dev/null +++ b/test/registered/ascend/interface/test_npu_api_abort_request.py @@ -0,0 +1,78 @@ +import threading +import time +import unittest + +import requests + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +responses = [] + + +def send_requests(url, **kwargs): + response = requests.post(DEFAULT_URL_FOR_TEST + url, json=kwargs) + responses.append(response) + + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) + + +class TestNpuApi(CustomTestCase): + """Testcase: Verify the functionality of /abort_request API to terminate a running /generate request on Ascend backend. + + [Test Category] Interface + [Test Target] /abort_request + """ + + @classmethod + def setUpClass(cls): + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + other_args = [ + "--attention-backend", + "ascend", + ] + cls.process = popen_launch_server( + cls.model, + DEFAULT_URL_FOR_TEST, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_args, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_api_abort_request(self): + # Create thread 1: Send a long-running /generate request with rid=10086 + thread1 = threading.Thread( + target=send_requests, + args=("/generate",), + kwargs={ + "rid": "10086", + "text": "who are you?", + "sampling_params": {"temperature": 0.0, "max_new_tokens": 1024}, + }, + ) + # Create thread 2: Send an /abort_request to terminate the request with rid=10086 + thread2 = threading.Thread( + target=send_requests, args=("/abort_request",), kwargs={"rid": "10086"} + ) + thread1.start() + time.sleep(0.5) + thread2.start() + thread1.join() + thread2.join() + print(responses[1].text) + + +if __name__ == "__main__": + + unittest.main() diff --git a/test/registered/ascend/interface/test_npu_api_encode.py b/test/registered/ascend/interface/test_npu_api_encode.py new file mode 100644 index 000000000..800d774c8 --- /dev/null +++ b/test/registered/ascend/interface/test_npu_api_encode.py @@ -0,0 +1,134 @@ +import logging +import unittest + +import requests + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import QWEN3_VL_4B_INSTRUCT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[logging.StreamHandler()], +) +logger = logging.getLogger(__name__) + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) + + +class TestNpuApi(CustomTestCase): + """Testcase: Verify the availability and correctness of the /encode API on Ascend backend with GME_QWEN2_VL_2B_INSTRUCT model. + + [Test Category] Interface + [Test Target] /encode + """ + + @classmethod + def setUpClass(cls): + cls.model = QWEN3_VL_4B_INSTRUCT_WEIGHTS_PATH + other_args = [ + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--tp-size", + 2, + "--is-embedding", + ] + cls.process = popen_launch_server( + cls.model, + DEFAULT_URL_FOR_TEST, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_args, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_api_encode_01(self): + # Test Scenario 1: Call /encode API with plain text parameter + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/encode", + json={ + "rid": "2", + "text": "what is the capital of France", + "sampling_params": { + "temperature": 0, + "max_new_tokens": 200, + "top_p": 1, + }, + }, + ) + logger.info("Test 01 response keys: %s", response.json().keys()) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["meta_info"]["id"], "2") + + def test_api_encode_02(self): + # Test Scenario 2: Call /encode API with input_ids parameter + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/encode", + json={ + "rid": "3", + "input_ids": [101, 7592, 2088, 102], + "sampling_params": {"temperature": 0, "max_new_tokens": 200}, + }, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["meta_info"]["id"], "3") + + def test_api_encode_03(self): + # Test Scenario 3: Call /encode API with text and image parameters (multimodal capability verification) + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/encode", + json={ + "rid": "4", + "text": "show me the words", + "image_data": "https://miaobi-lite.bj.bcebos.com/miaobi/5mao/b%27b2Ny6K%2BG5Yir5Luj56CBXzE3MzQ2MzcyNjAuMzgxNDk5NQ%3D%3D%27/0.png", + "sampling_params": {"temperature": 0, "max_new_tokens": 200}, + }, + ) + logger.info("Test 03 response keys: %s", response.json().keys()) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["meta_info"]["id"], "4") + + def test_api_encode_04(self): + # Test Scenario 4: Call /encode API with list of rids (multiple requests) - text input + request_rids = ["5", "6", "7"] + response = requests.post( + f"{DEFAULT_URL_FOR_TEST}/encode", + json={ + "rid": request_rids, + "text": [ + "what is the capital of UK", + "what is the capital of Germany", + "what is the capital of Japan", + ], + "sampling_params": { + "temperature": 0, + "max_new_tokens": 200, + "top_p": 1, + }, + }, + ) + response_json = response.json() + logger.info( + "Test 04 response type: %s, first item meta_info: %s", + type(response_json), + response_json[0].get("meta_info", {}), + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response_json), len(request_rids)) + for idx, result in enumerate(response_json): + self.assertEqual(result["meta_info"]["id"], request_rids[idx]) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/basic_function/interface/test_ascend_enable_thinking.py b/test/registered/ascend/interface/test_npu_enable_thinking.py similarity index 93% rename from test/registered/ascend/basic_function/interface/test_ascend_enable_thinking.py rename to test/registered/ascend/interface/test_npu_enable_thinking.py index 7ff93bd30..6efe84c28 100644 --- a/test/registered/ascend/basic_function/interface/test_ascend_enable_thinking.py +++ b/test/registered/ascend/interface/test_npu_enable_thinking.py @@ -13,7 +13,12 @@ from sglang.test.test_utils import ( popen_launch_server, ) -register_npu_ci(est_time=400, suite="nightly-2-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-2-npu-a3", + nightly=True, + disabled="https://github.com/Ascend/sglang/issues/32", +) class TestEnableThinking(CustomTestCase): @@ -28,23 +33,19 @@ class TestEnableThinking(CustomTestCase): def setUpClass(cls): cls.model = QWEN3_30B_A3B_WEIGHTS_PATH cls.base_url = DEFAULT_URL_FOR_TEST - cls.api_key = "sk-1234" cls.other_args = [ - "--reasoning-parser", - "qwen3", "--attention-backend", "ascend", "--disable-cuda-graph", "--mem-fraction-static", 0.95, "--tp", - 2, + 16, ] cls.process = popen_launch_server( cls.model, cls.base_url, timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, - api_key=cls.api_key, other_args=cls.other_args, ) cls.additional_chat_kwargs = {} @@ -57,7 +58,6 @@ class TestEnableThinking(CustomTestCase): # Test non-streaming with "enable_thinking": True, reasoning_content should not be empty client = requests.post( f"{self.base_url}/v1/chat/completions", - headers={"Authorization": f"Bearer {self.api_key}"}, json={ "model": self.model, "messages": [{"role": "user", "content": "Hello"}], @@ -81,7 +81,6 @@ class TestEnableThinking(CustomTestCase): # Test non-streaming with "enable_thinking": False, reasoning_content should be empty client = requests.post( f"{self.base_url}/v1/chat/completions", - headers={"Authorization": f"Bearer {self.api_key}"}, json={ "model": self.model, "messages": [{"role": "user", "content": "Hello"}], @@ -106,7 +105,6 @@ class TestEnableThinking(CustomTestCase): # Test streaming with "enable_thinking": True, reasoning_content should not be empty response = requests.post( f"{self.base_url}/v1/chat/completions", - headers={"Authorization": f"Bearer {self.api_key}"}, json={ "model": self.model, "messages": [{"role": "user", "content": "Hello"}], @@ -151,7 +149,6 @@ class TestEnableThinking(CustomTestCase): # Test streaming with "enable_thinking": False, reasoning_content should be empty response = requests.post( f"{self.base_url}/v1/chat/completions", - headers={"Authorization": f"Bearer {self.api_key}"}, json={ "model": self.model, "messages": [{"role": "user", "content": "Hello"}], diff --git a/test/registered/ascend/interface/test_npu_matched_stop.py b/test/registered/ascend/interface/test_npu_matched_stop.py new file mode 100644 index 000000000..5dcf40ff5 --- /dev/null +++ b/test/registered/ascend/interface/test_npu_matched_stop.py @@ -0,0 +1,163 @@ +import json +import unittest + +import requests + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import LLAMA_3_1_8B_INSTRUCT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +MANY_NEW_TOKENS_PROMPT = """ +Please write an extremely detailed and vivid fantasy story, set in a world full of intricate magic systems, political intrigue, and complex characters. +Ensure that you thoroughly describe every scene, character's motivations, and the environment. Include long, engaging dialogues and elaborate on the inner thoughts of the characters. +Each section should be as comprehensive as possible to create a rich and immersive experience for the reader. +The story should span multiple events, challenges, and character developments over time. Aim to make the story at least 3,000 words long. +""" + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) + + +class TestMatchedStop(CustomTestCase): + """Testcase: Test configuring 'matched_stop' to different values(string, EOS token, length) correctly identifies + it as a stop signal. + + [Test Category] Interface + [Test Target] /v1/chat/completions; /v1/completions + """ + + @classmethod + def setUpClass(cls): + cls.model = LLAMA_3_1_8B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.other_args = [ + "--max-running-requests", + 10, + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--mem-fraction-static", + 0.8, + ] + cls.process = popen_launch_server( + cls.model, cls.base_url, timeout=300, other_args=cls.other_args + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def run_completions_generation( + self, + prompt=MANY_NEW_TOKENS_PROMPT, + max_tokens=1, + stop=None, + finish_reason=None, + matched_stop=None, + ): + # Configure matched_stop to None, and use the '/v1/completions' interface + # verify that the actual termination reason matches the configured value. + payload = { + "prompt": prompt, + "model": self.model, + "temperature": 0, + "top_p": 1, + "max_tokens": max_tokens, + } + + if stop is not None: + payload["stop"] = stop + + response_completions = requests.post( + self.base_url + "/v1/completions", + json=payload, + ) + print(json.dumps(response_completions.json())) + print("=" * 100) + + assert ( + response_completions.json()["choices"][0]["finish_reason"] == finish_reason + ) + assert response_completions.json()["choices"][0]["matched_stop"] == matched_stop + + def run_chat_completions_generation( + self, + prompt=MANY_NEW_TOKENS_PROMPT, + max_tokens=1, + stop=None, + finish_reason=None, + matched_stop=None, + ): + # Configure matched_stop to None, and use the '/v1/chat/completions' interface + # verify that the actual termination reason matches the configured value. + chat_payload = { + "model": self.model, + "messages": [ + {"role": "system", "content": "You are a helpful AI assistant"}, + {"role": "user", "content": prompt}, + ], + "temperature": 0, + "top_p": 1, + "max_tokens": max_tokens, + } + + if stop is not None: + chat_payload["stop"] = stop + + response_chat = requests.post( + self.base_url + "/v1/chat/completions", + json=chat_payload, + ) + print(json.dumps(response_chat.json())) + print("=" * 100) + + assert response_chat.json()["choices"][0]["finish_reason"] == finish_reason + assert response_chat.json()["choices"][0]["matched_stop"] == matched_stop + + def test_finish_stop_str(self): + # Setting finish_reason="stop",'matched_stop="\n"' allows for correct termination + self.run_completions_generation( + max_tokens=1000, stop="\n", finish_reason="stop", matched_stop="\n" + ) + self.run_chat_completions_generation( + max_tokens=1000, stop="\n", finish_reason="stop", matched_stop="\n" + ) + + def test_finish_stop_eos(self): + # Setting matched_stop is a specific EOS end flagallows for correct identification and termination of signal + llama_format_prompt = """ + <|begin_of_text|><|start_header_id|>system<|end_header_id|> + You are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|> + + What is 2 + 2?<|eot_id|><|start_header_id|>assistant<|end_header_id|> + """ + eos_token_id = 128009 + self.run_completions_generation( + prompt=llama_format_prompt, + max_tokens=1000, + finish_reason="stop", + matched_stop=eos_token_id, + ) + self.run_chat_completions_generation( + prompt="What is 2 + 2?", + max_tokens=1000, + finish_reason="stop", + matched_stop=eos_token_id, + ) + + def test_finish_length(self): + # Setting finish_reason="length",'matched_stop="\n"' allows for correct termination + self.run_completions_generation( + max_tokens=5, finish_reason="length", matched_stop=None + ) + self.run_chat_completions_generation( + max_tokens=5, finish_reason="length", matched_stop=None + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/interface/test_npu_openai_function_calling.py b/test/registered/ascend/interface/test_npu_openai_function_calling.py new file mode 100644 index 000000000..0055b6c0d --- /dev/null +++ b/test/registered/ascend/interface/test_npu_openai_function_calling.py @@ -0,0 +1,943 @@ +import json +import unittest + +import openai + +from sglang.srt.utils import kill_process_tree +from sglang.srt.utils.hf_transformers_utils import get_tokenizer +from sglang.test.ascend.test_ascend_utils import LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci( + est_time=400, + suite="nightly-1-npu-a3", + nightly=True, + disabled="https://github.com/Ascend/sglang/issues/39", +) + + +class TestOpenAIServerFunctionCalling(CustomTestCase): + """Testcase:Verify the correctness of full-scenario OpenAI-style function calling with llama3 parser for Llama-3.2-1B-Instruct model. + Cover: Single/multi-turn calls, streaming/non-streaming returns, multi-parameter verification of tool_choice, and JSON parsing validity of function parameters. + + [Test Category] Interface + [Test Target] /v1/chat/completions + """ + + # NOTE: this system_message is for Llama3.2 system prompt. Without this, + # sometimes Llama3.2 gives a different tool call format such as: + # '<|python_tag|>{"type": "function", "function": "add", "parameters": {"a": "3", "b": "5"}}' + SYSTEM_MESSAGE = ( + "You are a helpful assistant with tool calling capabilities. " + "Only reply with a tool call if the function exists in the library provided by the user. " + "If it doesn't exist, just reply directly in natural language. " + "When you receive a tool call response, use the output to format an answer to the original user question. " + "You have access to the following functions. " + "To call a function, please respond with JSON for a function call. " + 'Respond in the format {"name": function name, "parameters": dictionary of argument name and its value}. ' + "Do not use variables.\n\n" + ) + + @classmethod + def setUpClass(cls): + # Replace with the model name needed for testing + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.api_key = "sk-123456" + + # Start the local OpenAI Server. If necessary, you can add other parameters such as --enable-tools. + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + api_key=cls.api_key, + other_args=[ + # If your server needs extra parameters to test function calling, please add them here. + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--tool-call-parser", + "llama3", + ], + ) + cls.base_url += "/v1" + cls.tokenizer = get_tokenizer(cls.model) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_function_calling_format(self): + """ + Test: Whether the function call format returned by the AI is correct. + When returning a tool call, message.content should be None, and tool_calls should be a list. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "add", + "description": "Compute the sum of two numbers", + "parameters": { + "type": "object", + "properties": { + "a": { + "type": "integer", + "description": "A number", + }, + "b": { + "type": "integer", + "description": "A number", + }, + }, + "required": ["a", "b"], + }, + }, + } + ] + + messages = [ + {"role": "system", "content": self.SYSTEM_MESSAGE}, + {"role": "user", "content": "Compute (3+5)"}, + ] + response = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools, + ) + + tool_calls = response.choices[0].message.tool_calls + + assert ( + isinstance(tool_calls, list) and len(tool_calls) > 0 + ), "tool_calls should be a non-empty list" + + function_name = tool_calls[0].function.name + assert function_name == "add", "Function name should be 'add'" + + # This unit test is too difficult for default model. Mark it as optional unit tests so it won't trigger unless specified. + def _test_function_calling_multiturn(self): + """ + Test: Whether the function call format returned by the AI is correct. + When returning a tool call, message.content should be None, and tool_calls should be a list. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "add", + "description": "Compute the sum of two numbers", + "parameters": { + "type": "object", + "properties": { + "a": { + "type": "integer", + "description": "A number", + }, + "b": { + "type": "integer", + "description": "A number", + }, + }, + "required": ["a", "b"], + }, + }, + } + ] + + messages = [{"role": "user", "content": "Compute (3+5)"}] + + response = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools, + ) + + tool_call = response.choices[0].message.tool_calls[0] + function_name = tool_call.function.name + assert function_name == "add", "Function name should be 'add'" + function_arguments = json.loads(tool_call.function.arguments) + assert function_arguments in [ + {"a": 3, "b": 5}, + {"a": "3", "b": "5"}, + ], f"Unexpected function arguments: {function_arguments}" + + messages.append(response.choices[0].message) + messages.append( + { + "role": "tool", + "tool_call_id": tool_call.id, + "content": "8", + "name": function_name, + } + ) + + final_response = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools, + ) + + assert ( + "8" in final_response.choices[0].message.content + ), "tool_call response should have the sum 8 in the content" + + def test_function_calling_streaming_simple(self): + """ + Test: Whether the function name can be correctly recognized in streaming mode. + - Expect a function call to be found, and the function name to be correct. + - Verify that streaming mode returns at least multiple chunks. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "The city to find the weather for", + }, + "unit": { + "type": "string", + "description": "Weather unit (celsius or fahrenheit)", + "enum": ["celsius", "fahrenheit"], + }, + }, + "required": ["city", "unit"], + }, + }, + } + ] + + messages = [ + {"role": "system", "content": self.SYSTEM_MESSAGE}, + { + "role": "user", + "content": "What is the temperature in Paris in celsius??", + }, + ] + + response_stream = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=True, + tools=tools, + ) + + chunks = list(response_stream) + self.assertTrue(len(chunks) > 0, "Streaming should return at least one chunk") + + found_function_name = False + for chunk in chunks: + choice = chunk.choices[0] + # Check whether the current chunk contains tool_calls + if choice.delta.tool_calls: + tool_call = choice.delta.tool_calls[0] + if tool_call.function.name: + self.assertEqual( + tool_call.function.name, + "get_current_weather", + "Function name should be 'get_current_weather'", + ) + found_function_name = True + break + + self.assertTrue( + found_function_name, + "Target function name 'get_current_weather' was not found in the streaming chunks", + ) + + finish_reason = chunks[-1].choices[0].finish_reason + self.assertEqual( + finish_reason, + "tool_calls", + "Final response of function calling should have finish_reason 'tool_calls'", + ) + + def test_function_calling_streaming_args_parsing(self): + """ + Test: Whether the function call arguments returned in streaming mode can be correctly concatenated into valid JSON. + - The user request requires multiple parameters. + - AI may return the arguments in chunks that need to be concatenated. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "add", + "description": "Compute the sum of two integers", + "parameters": { + "type": "object", + "properties": { + "a": { + "type": "integer", + "description": "First integer", + }, + "b": { + "type": "integer", + "description": "Second integer", + }, + }, + "required": ["a", "b"], + }, + "strict": True, # Llama-3.2-1B is flaky in tool call. It won't always respond with parameters unless we set strict. + }, + } + ] + + messages = [ + {"role": "system", "content": self.SYSTEM_MESSAGE}, + {"role": "user", "content": "Please sum 5 and 7, just call the function."}, + ] + + response_stream = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.9, + top_p=0.9, + stream=True, + tools=tools, + ) + + argument_fragments = [] + chunks = list(response_stream) + function_name = None + for chunk in chunks: + choice = chunk.choices[0] + if choice.delta.tool_calls: + tool_call = choice.delta.tool_calls[0] + # Record the function name on first occurrence + function_name = tool_call.function.name or function_name + # In case of multiple chunks, JSON fragments may need to be concatenated + if tool_call.function.arguments is not None: + argument_fragments.append(tool_call.function.arguments) + + self.assertEqual(function_name, "add", "Function name should be 'add'") + joined_args = "".join(argument_fragments) + self.assertTrue( + len(joined_args) > 0, + "No parameter fragments were returned in the function call", + ) + + finish_reason = chunks[-1].choices[0].finish_reason + self.assertEqual( + finish_reason, + "tool_calls", + "Final response of function calling should have finish_reason 'tool_calls'", + ) + + # Check whether the concatenated JSON is valid + try: + args_obj = json.loads(joined_args) + except json.JSONDecodeError: + self.fail( + "The concatenated tool call arguments are not valid JSON, parsing failed" + ) + + self.assertIn("a", args_obj, "Missing parameter 'a'") + self.assertIn("b", args_obj, "Missing parameter 'b'") + self.assertEqual(str(args_obj["a"]), "5", "Parameter a should be 5") + self.assertEqual(str(args_obj["b"]), "7", "Parameter b should be 7") + + def test_function_call_strict(self): + """ + Test: Whether the strict mode of function calling works as expected. + - When strict mode is enabled, the AI should not return a function call if the function name is not recognized. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "sub", + "description": "Compute the difference of two integers", + "parameters": { + "type": "object", + "properties": { + "int_a": { + "type": "integer", + "description": "First integer", + }, + "int_b": { + "type": "integer", + "description": "Second integer", + }, + }, + "required": ["int_a", "int_b"], + }, + "strict": True, + }, + } + ] + + messages = [ + {"role": "user", "content": "Please compute 5 - 7, using your tool."} + ] + response = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools, + ) + + tool_calls = response.choices[0].message.tool_calls + function_name = tool_calls[0].function.name + arguments = tool_calls[0].function.arguments + args_obj = json.loads(arguments) + + self.assertEqual(function_name, "sub", "Function name should be 'sub'") + self.assertEqual(str(args_obj["int_a"]), "5", "Parameter int_a should be 5") + self.assertEqual(str(args_obj["int_b"]), "7", "Parameter int_b should be 7") + + def test_function_call_required(self): + """ + Test: Whether tool_choice: "required" works as expected + - When tool_choice == "required", the model should return one or more tool_calls. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "sub", + "description": "Compute the difference of two integers", + "parameters": { + "type": "object", + "properties": { + "int_a": { + "type": "integer", + "description": "First integer", + }, + "int_b": { + "type": "integer", + "description": "Second integer", + }, + }, + "required": ["int_a", "int_b"], + }, + "strict": True, + }, + }, + { + "type": "function", + "function": { + "name": "get_weather", + "description": "use this to get latest weather information for a city given its name", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "name of the city to get weather for", + } + }, + "required": ["city"], + }, + }, + }, + ] + + messages = [{"role": "user", "content": "What is the capital of France?"}] + response = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools, + tool_choice="required", + ) + + tool_calls = response.choices[0].message.tool_calls + self.assertIsNotNone(tool_calls, "No tool_calls in the response") + function_name = tool_calls[0].function.name + arguments = tool_calls[0].function.arguments + args_obj = json.loads(arguments) + + self.assertEqual( + function_name, + "get_weather", + f"Function name should be 'get_weather', got: {function_name}", + ) + self.assertIn( + "city", args_obj, f"Function arguments should have 'city', got: {args_obj}" + ) + + # Make the test more robust by checking type and accepting valid responses + city_value = args_obj["city"] + self.assertIsInstance( + city_value, + str, + f"Parameter city should be a string, got: {type(city_value)}", + ) + self.assertTrue( + "Paris" in city_value or "France" in city_value, + f"Parameter city should contain either 'Paris' or 'France', got: {city_value}", + ) + + def test_function_call_specific(self): + """ + Test: Whether tool_choice: ToolChoice works as expected + - When tool_choice is a specific ToolChoice, the model should return one or more tool_calls. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "sub", + "description": "Compute the difference of two integers", + "parameters": { + "type": "object", + "properties": { + "int_a": { + "type": "integer", + "description": "First integer", + }, + "int_b": { + "type": "integer", + "description": "Second integer", + }, + }, + "required": ["int_a", "int_b"], + }, + "strict": True, + }, + }, + { + "type": "function", + "function": { + "name": "get_weather", + "description": "use this to get latest weather information for a city given its name", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "name of the city to get weather for", + } + }, + "required": ["city"], + }, + }, + }, + ] + + messages = [{"role": "user", "content": "What is the capital of France?"}] + response = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools, + tool_choice={"type": "function", "function": {"name": "get_weather"}}, + ) + + tool_calls = response.choices[0].message.tool_calls + self.assertIsNotNone(tool_calls, "No tool_calls in the response") + function_name = tool_calls[0].function.name + arguments = tool_calls[0].function.arguments + args_obj = json.loads(arguments) + + self.assertEqual( + function_name, "get_weather", "Function name should be 'get_weather'" + ) + self.assertIn("city", args_obj, "Function arguments should have 'city'") + + def test_streaming_multiple_choices_finish_reason(self): + """ + Test: Verify that each choice gets its own finish_reason chunk in streaming mode with n > 1. + This tests the fix for the bug where only the last index got a finish_reason chunk. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"], + }, + }, + "required": ["location"], + }, + }, + } + ] + + messages = [ + {"role": "user", "content": "What is the weather like in Los Angeles?"} + ] + + # Request with n=2 to get multiple choices + response_stream = client.chat.completions.create( + model=self.model, + messages=messages, + max_tokens=2048, + temperature=0.8, + stream=True, + tools=tools, + tool_choice="required", # Force tool calls + n=2, # Multiple choices + ) + + chunks = list(response_stream) + + # Track finish_reason chunks for each index + finish_reason_chunks = {} + for chunk in chunks: + if chunk.choices: + for choice in chunk.choices: + if choice.finish_reason is not None: + index = choice.index + if index not in finish_reason_chunks: + finish_reason_chunks[index] = [] + finish_reason_chunks[index].append(choice.finish_reason) + + # Verify we got finish_reason chunks for both indices + self.assertEqual( + len(finish_reason_chunks), + 2, + f"Expected finish_reason chunks for 2 indices, got {len(finish_reason_chunks)}", + ) + + # Verify both index 0 and 1 have finish_reason + self.assertIn( + 0, finish_reason_chunks, "Missing finish_reason chunk for index 0" + ) + self.assertIn( + 1, finish_reason_chunks, "Missing finish_reason chunk for index 1" + ) + + # Verify the finish_reason is "tool_calls" since we forced tool calls + for index, reasons in finish_reason_chunks.items(): + self.assertEqual( + reasons[-1], # Last finish_reason for this index + "tool_calls", + f"Expected finish_reason 'tool_calls' for index {index}, got {reasons[-1]}", + ) + + def test_function_calling_streaming_no_tool_call(self): + """ + Test: Whether the finish_reason is stop in streaming mode when no tool call is given. + - Expect no function call to be found. + - Verify that finish_reason is stop + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + tools = [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "The city to find the weather for", + }, + "unit": { + "type": "string", + "description": "Weather unit (celsius or fahrenheit)", + "enum": ["celsius", "fahrenheit"], + }, + }, + "required": ["city", "unit"], + }, + }, + } + ] + + messages = [{"role": "user", "content": "Who are you?"}] + + response_stream = client.chat.completions.create( + model=self.model, + max_tokens=2048, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=True, + tools=tools, + tool_choice="none", + ) + + chunks = list(response_stream) + self.assertTrue(len(chunks) > 0, "Streaming should return at least one chunk") + + found_tool_call = False + for chunk in chunks: + choice = chunk.choices[0] + # Check whether the current chunk contains tool_calls + found_tool_call = choice.delta.tool_calls is not None + + self.assertFalse( + found_tool_call, + "Shouldn't have any tool_call in the streaming chunks", + ) + + finish_reason = chunks[-1].choices[0].finish_reason + self.assertEqual( + finish_reason, + "stop", + "Final response of no function calling should have finish_reason 'stop'", + ) + + def test_streaming_multiple_choices_without_tools(self): + """ + Test: Verify that each choice gets its own finish_reason chunk without tool calls. + This tests the fix for regular content streaming with multiple choices. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + messages = [{"role": "user", "content": "Say hello in one word."}] + + # Request with n=2 to get multiple choices, no tools + response_stream = client.chat.completions.create( + model=self.model, + messages=messages, + temperature=0.8, + stream=True, + max_tokens=10, # Keep it short + n=2, # Multiple choices + ) + + chunks = list(response_stream) + + # Track finish_reason chunks for each index + finish_reason_chunks = {} + for chunk in chunks: + if chunk.choices: + for choice in chunk.choices: + if choice.finish_reason is not None: + index = choice.index + if index not in finish_reason_chunks: + finish_reason_chunks[index] = [] + finish_reason_chunks[index].append(choice.finish_reason) + + # Verify we got finish_reason chunks for both indices + self.assertEqual( + len(finish_reason_chunks), + 2, + f"Expected finish_reason chunks for 2 indices, got {len(finish_reason_chunks)}", + ) + + # Verify both index 0 and 1 have finish_reason + self.assertIn( + 0, finish_reason_chunks, "Missing finish_reason chunk for index 0" + ) + self.assertIn( + 1, finish_reason_chunks, "Missing finish_reason chunk for index 1" + ) + + # Verify the finish_reason is "stop" (regular completion) + for index, reasons in finish_reason_chunks.items(): + self.assertIn( + reasons[-1], + ["stop", "length"], # Could be either depending on how model responds + f"Expected finish_reason 'stop' or 'length' for index {index}, got {reasons[-1]}", + ) + + +class TestOpenAIPythonicFunctionCalling(CustomTestCase): + """Testcase:Verify the functionality of Python-style list-format function calling with pythonic parser for Llama-3.2-1B-Instruct model on Ascend NPU backend. + Cover: Explicit format prompt verification, streaming call index integrity, and return validity of parallel tool calls. + + [Test Category] Interface + [Test Target] /v1/chat/completions + """ + + PYTHONIC_TOOLS = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather for a given location.", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The name of the city or location.", + } + }, + "required": ["location"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "get_tourist_attractions", + "description": "Get a list of top tourist attractions for a given city.", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "The name of the city to find attractions for.", + } + }, + "required": ["city"], + }, + }, + }, + ] + + PYTHONIC_MESSAGES = [ + { + "role": "system", + "content": ( + "You are a travel assistant. " + "When asked to call functions, ALWAYS respond ONLY with a python list of function calls, " + "using this format: [func_name1(param1=value1, param2=value2), func_name2(param=value)]. " + "Do NOT use JSON, do NOT use variables, do NOT use any other format. " + "Here is an example:\n" + '[get_weather(location="Paris"), get_tourist_attractions(city="Paris")]' + ), + }, + { + "role": "user", + "content": ( + "I'm planning a trip to Tokyo next week. What's the weather like and what are some top tourist attractions? " + "Propose parallel tool calls at once, using the python list of function calls format as shown above." + ), + }, + ] + + @classmethod + def setUpClass(cls): + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.api_key = "sk-123456" + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + api_key=cls.api_key, + other_args=[ + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--tool-call-parser", + "pythonic", + ], + ) + cls.base_url += "/v1" + cls.tokenizer = get_tokenizer(cls.model) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_pythonic_tool_call_prompt(self): + """ + Test: Explicit prompt for pythonic tool call format without chat template. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + response = client.chat.completions.create( + model=self.model, + messages=self.PYTHONIC_MESSAGES, + tools=self.PYTHONIC_TOOLS, + temperature=0.1, + stream=False, + ) + tool_calls = response.choices[0].message.tool_calls + self.assertIsInstance(tool_calls, list, "No tool_calls found") + self.assertGreaterEqual(len(tool_calls), 1) + names = [tc.function.name for tc in tool_calls] + self.assertTrue( + "get_weather" in names or "get_tourist_attractions" in names, + f"Function name '{names}' should container either 'get_weather' or 'get_tourist_attractions'", + ) + + def test_pythonic_tool_call_streaming(self): + """ + Test: Streaming pythonic tool call format; assert tool_call index is present. + """ + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + response_stream = client.chat.completions.create( + model=self.model, + messages=self.PYTHONIC_MESSAGES, + tools=self.PYTHONIC_TOOLS, + temperature=0.1, + stream=True, + ) + found_tool_calls = False + found_index = False + found_names = set() + for chunk in response_stream: + choice = chunk.choices[0] + if getattr(choice.delta, "tool_calls", None): + found_tool_calls = True + tool_call = choice.delta.tool_calls[0] + if hasattr(tool_call, "index") or ( + isinstance(tool_call, dict) and "index" in tool_call + ): + found_index = True + found_names.add(str(tool_call.function.name)) + + self.assertTrue(found_tool_calls, "No tool_calls found in streaming response") + self.assertTrue(found_index, "No index field found in any streamed tool_call") + self.assertTrue( + "get_weather" in found_names or "get_tourist_attractions" in found_names, + f"Function name '{found_names}' should container either 'get_weather' or 'get_tourist_attractions'", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/interface/test_npu_openai_server_ignore_eos.py b/test/registered/ascend/interface/test_npu_openai_server_ignore_eos.py new file mode 100644 index 000000000..0fee4ecac --- /dev/null +++ b/test/registered/ascend/interface/test_npu_openai_server_ignore_eos.py @@ -0,0 +1,101 @@ +import unittest + +import openai + +from sglang.srt.utils import kill_process_tree +from sglang.srt.utils.hf_transformers_utils import get_tokenizer +from sglang.test.ascend.test_ascend_utils import LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=400, suite="nightly-2-npu-a3", nightly=True) + + +class TestOpenAIServerIgnoreEOS(CustomTestCase): + """Testcase: Test 'ignore_eos' is True, the EOS is ignored and continue reasoning + + [Test Category] Interface + [Test Target] ignore_eos + """ + + @classmethod + def setUpClass(cls): + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + cls.base_url = DEFAULT_URL_FOR_TEST + cls.api_key = "sk-123456" + cls.other_args = [ + "--attention-backend", + "ascend", + "--disable-cuda-graph", + ] + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + api_key=cls.api_key, + other_args=cls.other_args, + ) + cls.base_url += "/v1" + cls.tokenizer = get_tokenizer(cls.model) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def test_ignore_eos(self): + client = openai.Client(api_key=self.api_key, base_url=self.base_url) + + max_tokens = 200 + + response_default = client.chat.completions.create( + model=self.model, + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Count from 1 to 20."}, + ], + temperature=0, + max_tokens=max_tokens, + extra_body={"ignore_eos": False}, + ) + + response_ignore_eos = client.chat.completions.create( + model=self.model, + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Count from 1 to 20."}, + ], + temperature=0, + max_tokens=max_tokens, + extra_body={"ignore_eos": True}, + ) + + default_tokens = len( + self.tokenizer.encode(response_default.choices[0].message.content) + ) + ignore_eos_tokens = len( + self.tokenizer.encode(response_ignore_eos.choices[0].message.content) + ) + + # Check if ignore_eos resulted in more tokens or exactly max_tokens + # The ignore_eos response should either: + # 1. Have more tokens than the default response (if default stopped at EOS before max_tokens) + # 2. Have exactly max_tokens (if it reached the max_tokens limit) + self.assertTrue( + ignore_eos_tokens > default_tokens or ignore_eos_tokens >= max_tokens, + f"ignore_eos did not generate more tokens: {ignore_eos_tokens} vs {default_tokens}", + ) + + self.assertEqual( + response_ignore_eos.choices[0].finish_reason, + "length", + f"Expected finish_reason='length' for ignore_eos=True, got {response_ignore_eos.choices[0].finish_reason}", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/interface/test_npu_penalty.py b/test/registered/ascend/interface/test_npu_penalty.py new file mode 100644 index 000000000..f8c3f9dec --- /dev/null +++ b/test/registered/ascend/interface/test_npu_penalty.py @@ -0,0 +1,111 @@ +import json +import random +import unittest +from concurrent.futures import ThreadPoolExecutor + +import requests + +from sglang.srt.utils import kill_process_tree +from sglang.test.ascend.test_ascend_utils import LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import ( + DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + DEFAULT_URL_FOR_TEST, + CustomTestCase, + popen_launch_server, +) + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) + + +class TestPenalty(CustomTestCase): + """Testcase:Verify successful processing of inference requests with three specific mechanisms(frequency_penalty, presence_penalty, min_new_tokens). + + [Test Category] Interface + [Test Target] /generate + """ + + @classmethod + def setUpClass(cls): + cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH + other_args = [ + "--attention-backend", + "ascend", + "--disable-cuda-graph", + ] + cls.base_url = DEFAULT_URL_FOR_TEST + cls.process = popen_launch_server( + cls.model, + cls.base_url, + timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, + other_args=other_args, + ) + + @classmethod + def tearDownClass(cls): + kill_process_tree(cls.process.pid) + + def run_decode(self, sampling_params): + # Send inference request with specified sampling/penalty parameters. + + return_logprob = True + top_logprobs_num = 5 + return_text = True + n = 1 + + response = requests.post( + self.base_url + "/generate", + json={ + # prompt that is supposed to generate < 32 tokens + "text": "<|start_header_id|>user<|end_header_id|>\n\nWhat is the answer for 1 + 1 = ?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", + "sampling_params": { + "max_new_tokens": 48, + "n": n, + **sampling_params, + }, + "return_logprob": return_logprob, + "top_logprobs_num": top_logprobs_num, + "return_text_in_logprobs": return_text, + "logprob_start_len": 0, + }, + ) + self.assertEqual(response.status_code, 200) + print(json.dumps(response.json())) + print("=" * 100) + + def test_default_values(self): + self.run_decode({}) + + def test_frequency_penalty(self): + self.run_decode({"frequency_penalty": 2}) + + def test_min_new_tokens(self): + self.run_decode({"min_new_tokens": 16}) + + def test_presence_penalty(self): + self.run_decode({"presence_penalty": 2}) + + def test_penalty_mixed(self): + args = [ + {}, + {}, + {}, + {"frequency_penalty": 2}, + {"presence_penalty": 1}, + {"min_new_tokens": 16}, + {"frequency_penalty": 0.2}, + {"presence_penalty": 0.4}, + {"min_new_tokens": 8}, + {"frequency_penalty": 0.4, "presence_penalty": 0.8}, + {"frequency_penalty": 0.4, "min_new_tokens": 12}, + {"presence_penalty": 0.8, "min_new_tokens": 12}, + {"presence_penalty": -0.3, "frequency_penalty": 1.3, "min_new_tokens": 32}, + {"presence_penalty": 0.3, "frequency_penalty": -1.3, "min_new_tokens": 32}, + ] + random.shuffle(args * 5) + with ThreadPoolExecutor(8) as executor: + list(executor.map(self.run_decode, args)) + + +if __name__ == "__main__": + unittest.main(verbosity=3) diff --git a/test/registered/ascend/llm_models/test_ascend_c4ai_command_r_v01.py b/test/registered/ascend/llm_models/test_ascend_c4ai_command_r_v01.py deleted file mode 100644 index 5adb892fc..000000000 --- a/test/registered/ascend/llm_models/test_ascend_c4ai_command_r_v01.py +++ /dev/null @@ -1,91 +0,0 @@ -import os -import unittest -from types import SimpleNamespace - -from sglang.srt.utils import kill_process_tree -from sglang.test.ci.ci_register import register_npu_ci -from sglang.test.few_shot_gsm8k import run_eval -from sglang.test.test_utils import ( - DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, - DEFAULT_URL_FOR_TEST, - CustomTestCase, - popen_launch_server, -) - -register_npu_ci( - est_time=400, - suite="nightly-2-npu-a3", - nightly=True, - disabled="The accuracy test result is 0.", -) - - -class TestC4AI(CustomTestCase): - model = "/root/.cache/modelscope/hub/models/CohereForAI/c4ai-command-r-v01" - accuracy = 0.05 - - @classmethod - def setUpClass(cls): - cls.base_url = DEFAULT_URL_FOR_TEST - chat_template_path = "/__w/sglang/sglang/test/nightly/ascend/llm_models/tool_chat_template_c4ai_command_r_v01.jinja" - - other_args = [ - "--trust-remote-code", - "--mem-fraction-static", - "0.8", - "--attention-backend", - "ascend", - "--disable-cuda-graph", - "--chat-template", - chat_template_path, - "--tp-size", - "2", - "--dtype", - "bfloat16", - ] - env = os.environ.copy() - env.update( - { - "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", - "ASCEND_MF_STORE_URL": "tcp://127.0.0.1:24666", - "HCCL_BUFFSIZE": "200", - "SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK": "24", - "USE_VLLM_CUSTOM_ALLREDUCE": "1", - "HCCL_EXEC_TIMEOUT": "200", - "STREAMS_PER_DEVICE": "32", - "SGLANG_ENABLE_TORCH_COMPILE": "1", - } - ) - - cls.process = popen_launch_server( - cls.model, - cls.base_url, - timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH, - other_args=other_args, - env=env, - ) - - @classmethod - def tearDownClass(cls): - kill_process_tree(cls.process.pid) - - def test_gsm8k(self): - args = SimpleNamespace( - num_shots=5, - data_path=None, - num_questions=200, - max_new_tokens=512, - parallel=128, - host="http://127.0.0.1", - port=int(self.base_url.split(":")[-1]), - ) - metrics = run_eval(args) - self.assertGreater( - metrics["accuracy"], - self.accuracy, - f'Accuracy of {self.model} is {str(metrics["accuracy"])}, is lower than {self.accuracy}', - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/registered/ascend/llm_models/test_ascend_gemma_3_1b_it.py b/test/registered/ascend/llm_models/test_ascend_gemma_3_1b_it.py deleted file mode 100644 index e018a890a..000000000 --- a/test/registered/ascend/llm_models/test_ascend_gemma_3_1b_it.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest - -from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin -from sglang.test.ci.ci_register import register_npu_ci -from sglang.test.test_utils import CustomTestCase - -register_npu_ci( - est_time=400, - suite="nightly-1-npu-a3", - nightly=True, - disabled="The accuracy test result is 0.", -) - - -class TestMistral7B(GSM8KAscendMixin, CustomTestCase): - model = "/root/.cache/modelscope/hub/models/LLM-Research/gemma-3-1b-it" - accuracy = 0.00 - - -if __name__ == "__main__": - unittest.main() diff --git a/test/registered/ascend/llm_models/test_ascend_afm_4_5b.py b/test/registered/ascend/llm_models/test_npu_afm_4_5b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_afm_4_5b.py rename to test/registered/ascend/llm_models/test_npu_afm_4_5b.py diff --git a/test/registered/ascend/llm_models/test_ascend_baichuan2_13b_chat.py b/test/registered/ascend/llm_models/test_npu_baichuan2_13b_chat.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_baichuan2_13b_chat.py rename to test/registered/ascend/llm_models/test_npu_baichuan2_13b_chat.py diff --git a/test/registered/ascend/llm_models/test_npu_c4ai_command_r_v01.py b/test/registered/ascend/llm_models/test_npu_c4ai_command_r_v01.py new file mode 100644 index 000000000..c4ee782b4 --- /dev/null +++ b/test/registered/ascend/llm_models/test_npu_c4ai_command_r_v01.py @@ -0,0 +1,40 @@ +import unittest + +from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin +from sglang.test.ascend.test_ascend_utils import ( + C4AI_COMMAND_R_V01_CHAT_TEMPLATE_PATH, + C4AI_COMMAND_R_V01_WEIGHTS_PATH, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import CustomTestCase + +register_npu_ci(est_time=400, suite="nightly-2-npu-a3", nightly=False) + + +class TestC4AI(GSM8KAscendMixin, CustomTestCase): + """Testcase: Verify that the inference accuracy of the CohereForAI/c4ai-command-r-v01 model on the GSM8K dataset is no less than 0.55. + + [Test Category] Model + [Test Target] CohereForAI/c4ai-command-r-v01 + """ + + model = C4AI_COMMAND_R_V01_WEIGHTS_PATH + accuracy = 0.55 + other_args = [ + "--trust-remote-code", + "--mem-fraction-static", + "0.8", + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--chat-template", + C4AI_COMMAND_R_V01_CHAT_TEMPLATE_PATH, + "--tp-size", + "2", + "--dtype", + "bfloat16", + ] + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/llm_models/test_ascend_charglm2_6b.py b/test/registered/ascend/llm_models/test_npu_chatglm2_6b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_charglm2_6b.py rename to test/registered/ascend/llm_models/test_npu_chatglm2_6b.py diff --git a/test/registered/ascend/llm_models/test_ascend_deepseek_v3_2_exp_w8a8.py b/test/registered/ascend/llm_models/test_npu_deepseek_v3_2_exp_w8a8.py similarity index 90% rename from test/registered/ascend/llm_models/test_ascend_deepseek_v3_2_exp_w8a8.py rename to test/registered/ascend/llm_models/test_npu_deepseek_v3_2_exp_w8a8.py index 03132e425..7cf589196 100644 --- a/test/registered/ascend/llm_models/test_ascend_deepseek_v3_2_exp_w8a8.py +++ b/test/registered/ascend/llm_models/test_npu_deepseek_v3_2_exp_w8a8.py @@ -5,12 +5,7 @@ from sglang.test.ascend.test_ascend_utils import DEEPSEEK_V3_2_EXP_W8A8_WEIGHTS_ from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import CustomTestCase -register_npu_ci( - est_time=400, - suite="nightly-16-npu-a3", - nightly=True, - disabled="run failed", -) +register_npu_ci(est_time=400, suite="nightly-16-npu-a3", nightly=True) class TestDeepSeekV32(GSM8KAscendMixin, CustomTestCase): diff --git a/test/registered/ascend/llm_models/test_ascend_exaone_3.py b/test/registered/ascend/llm_models/test_npu_exaone_3.py similarity index 90% rename from test/registered/ascend/llm_models/test_ascend_exaone_3.py rename to test/registered/ascend/llm_models/test_npu_exaone_3.py index a61ebf03f..23e72d4cd 100644 --- a/test/registered/ascend/llm_models/test_ascend_exaone_3.py +++ b/test/registered/ascend/llm_models/test_npu_exaone_3.py @@ -5,7 +5,7 @@ from sglang.test.ascend.test_ascend_utils import EXAONE_3_5_7_8B_INSTRUCT_WEIGHT from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import CustomTestCase -register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=False) class TestEXAONE(GSM8KAscendMixin, CustomTestCase): @@ -16,7 +16,8 @@ class TestEXAONE(GSM8KAscendMixin, CustomTestCase): """ model = EXAONE_3_5_7_8B_INSTRUCT_WEIGHTS_PATH - accuracy = 0.8 + # Allow 1% tolerance for the accuracy threshold + accuracy = round(0.8 * 0.99, 3) other_args = [ "--trust-remote-code", "--mem-fraction-static", diff --git a/test/registered/ascend/llm_models/test_npu_gemma_3_4b_it_llm.py b/test/registered/ascend/llm_models/test_npu_gemma_3_4b_it_llm.py new file mode 100644 index 000000000..293deb22f --- /dev/null +++ b/test/registered/ascend/llm_models/test_npu_gemma_3_4b_it_llm.py @@ -0,0 +1,38 @@ +import unittest + +from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin +from sglang.test.ascend.test_ascend_utils import GEMMA_3_4B_IT_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.test_utils import CustomTestCase + +register_npu_ci( + est_time=400, + suite="nightly-1-npu-a3", + nightly=True, +) + + +class TestGemma34B(GSM8KAscendMixin, CustomTestCase): + """Testcase: Verify that the inference accuracy of the google/gemma-3-4b-it model on the GSM8K dataset is no less than 0.7. + + [Test Category] Model + [Test Target] google/gemma-3-4b-it + """ + + model = GEMMA_3_4B_IT_WEIGHTS_PATH + accuracy = 0.7 + other_args = [ + "--trust-remote-code", + "--mem-fraction-static", + "0.8", + "--attention-backend", + "ascend", + "--disable-cuda-graph", + "--disable-radix-cache", + "--chunked-prefill-size", + "-1", + ] + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/llm_models/test_ascend_glm4_9b_chat.py b/test/registered/ascend/llm_models/test_npu_glm4_9b_chat.py similarity index 85% rename from test/registered/ascend/llm_models/test_ascend_glm4_9b_chat.py rename to test/registered/ascend/llm_models/test_npu_glm4_9b_chat.py index 7652f2e4b..e19eb3105 100644 --- a/test/registered/ascend/llm_models/test_ascend_glm4_9b_chat.py +++ b/test/registered/ascend/llm_models/test_npu_glm4_9b_chat.py @@ -9,19 +9,18 @@ register_npu_ci( est_time=400, suite="nightly-1-npu-a3", nightly=True, - disabled="run failed", ) class TestGLM49BChat(GSM8KAscendMixin, CustomTestCase): - """Testcase: Verify that the inference accuracy of the ZhipuAI/glm-4-9b-chat model on the GSM8K dataset is no less than 0.79. + """Testcase: Verify that the inference accuracy of the ZhipuAI/glm-4-9b-chat model on the GSM8K dataset is no less than 0.77. [Test Category] Model [Test Target] ZhipuAI/glm-4-9b-chat """ model = GLM_4_9B_CHAT_WEIGHTS_PATH - accuracy = 0.79 + accuracy = 0.77 if __name__ == "__main__": diff --git a/test/registered/ascend/llm_models/test_ascend_granite_3_0_3b_a800m.py b/test/registered/ascend/llm_models/test_npu_granite_3_0_3b_a800m.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_granite_3_0_3b_a800m.py rename to test/registered/ascend/llm_models/test_npu_granite_3_0_3b_a800m.py diff --git a/test/registered/ascend/llm_models/test_ascend_granite_3_1_8b.py b/test/registered/ascend/llm_models/test_npu_granite_3_1_8b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_granite_3_1_8b.py rename to test/registered/ascend/llm_models/test_npu_granite_3_1_8b.py diff --git a/test/registered/ascend/llm_models/test_ascend_grok_2.py b/test/registered/ascend/llm_models/test_npu_grok_2.py similarity index 83% rename from test/registered/ascend/llm_models/test_ascend_grok_2.py rename to test/registered/ascend/llm_models/test_npu_grok_2.py index 9923131f9..3eff4c194 100644 --- a/test/registered/ascend/llm_models/test_ascend_grok_2.py +++ b/test/registered/ascend/llm_models/test_npu_grok_2.py @@ -4,7 +4,12 @@ from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import CustomTestCase -register_npu_ci(est_time=400, suite="nightly-16-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-16-npu-a3", + nightly=False, + disabled="https://github.com/Ascend/sglang/issues/25", +) class TestGrok2(GSM8KAscendMixin, CustomTestCase): diff --git a/test/registered/ascend/llm_models/test_ascend_internlm2_7b.py b/test/registered/ascend/llm_models/test_npu_internlm2_7b.py similarity index 80% rename from test/registered/ascend/llm_models/test_ascend_internlm2_7b.py rename to test/registered/ascend/llm_models/test_npu_internlm2_7b.py index 888da3c51..0f319cfad 100644 --- a/test/registered/ascend/llm_models/test_ascend_internlm2_7b.py +++ b/test/registered/ascend/llm_models/test_npu_internlm2_7b.py @@ -1,3 +1,4 @@ +import os import unittest from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin @@ -5,7 +6,11 @@ from sglang.test.ascend.test_ascend_utils import INTERNLM2_7B_WEIGHTS_PATH from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import CustomTestCase -register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-1-npu-a3", + nightly=True, +) class TestInternlm2(GSM8KAscendMixin, CustomTestCase): @@ -15,6 +20,7 @@ class TestInternlm2(GSM8KAscendMixin, CustomTestCase): [Test Target] Shanghai_AI_Laboratory/internlm2-7b """ + os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" model = INTERNLM2_7B_WEIGHTS_PATH accuracy = 0.585 diff --git a/test/registered/ascend/llm_models/test_ascend_ling_lite.py b/test/registered/ascend/llm_models/test_npu_ling_lite.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_ling_lite.py rename to test/registered/ascend/llm_models/test_npu_ling_lite.py diff --git a/test/registered/ascend/llm_models/test_ascend_llama4_scount_17b_16e.py b/test/registered/ascend/llm_models/test_npu_llama4_scount_17b_16e.py similarity index 50% rename from test/registered/ascend/llm_models/test_ascend_llama4_scount_17b_16e.py rename to test/registered/ascend/llm_models/test_npu_llama4_scount_17b_16e.py index ee6be144e..a3acde5a6 100644 --- a/test/registered/ascend/llm_models/test_ascend_llama4_scount_17b_16e.py +++ b/test/registered/ascend/llm_models/test_npu_llama4_scount_17b_16e.py @@ -1,16 +1,28 @@ import unittest from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin +from sglang.test.ascend.test_ascend_utils import ( + LLAMA_4_SCOUT_17B_16E_INSTRUCT_WEIGHTS_PATH, +) from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import CustomTestCase -register_npu_ci(est_time=400, suite="nightly-4-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-4-npu-a3", + nightly=True, + disabled="https://github.com/Ascend/sglang/issues/25", +) class TestLlama4(GSM8KAscendMixin, CustomTestCase): - model = ( - "/root/.cache/modelscope/hub/models/meta-llama/Llama-4-Scout-17B-16E-Instruct" - ) + """Testcase: Verify that the inference accuracy of the meta-llama/Llama-4-Scout-17B-16E-Instruct model on the GSM8K dataset is no less than 0.9. + + [Test Category] Model + [Test Target] meta-llama/Llama-4-Scout-17B-16E-Instruct + """ + + model = LLAMA_4_SCOUT_17B_16E_INSTRUCT_WEIGHTS_PATH accuracy = 0.9 other_args = [ "--chat-template", @@ -24,6 +36,7 @@ class TestLlama4(GSM8KAscendMixin, CustomTestCase): "--attention-backend", "ascend", "--disable-cuda-graph", + "--disable-radix-cache", ] diff --git a/test/registered/ascend/llm_models/test_ascend_llama_2_7b.py b/test/registered/ascend/llm_models/test_npu_llama_2_7b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_llama_2_7b.py rename to test/registered/ascend/llm_models/test_npu_llama_2_7b.py diff --git a/test/registered/ascend/llm_models/test_ascend_mimo_7b_rl.py b/test/registered/ascend/llm_models/test_npu_mimo_7b_rl.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_mimo_7b_rl.py rename to test/registered/ascend/llm_models/test_npu_mimo_7b_rl.py diff --git a/test/registered/ascend/llm_models/test_ascend_minicpm3_4b.py b/test/registered/ascend/llm_models/test_npu_minicpm3_4b.py similarity index 94% rename from test/registered/ascend/llm_models/test_ascend_minicpm3_4b.py rename to test/registered/ascend/llm_models/test_npu_minicpm3_4b.py index 972d30dd3..d3db84743 100644 --- a/test/registered/ascend/llm_models/test_ascend_minicpm3_4b.py +++ b/test/registered/ascend/llm_models/test_npu_minicpm3_4b.py @@ -9,7 +9,7 @@ register_npu_ci( est_time=400, suite="nightly-1-npu-a3", nightly=True, - disabled="run failed", + disabled="https://github.com/Ascend/sglang/issues/23", ) diff --git a/test/registered/ascend/llm_models/test_ascend_mistral_7b.py b/test/registered/ascend/llm_models/test_npu_mistral_7b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_mistral_7b.py rename to test/registered/ascend/llm_models/test_npu_mistral_7b.py diff --git a/test/registered/ascend/llm_models/test_ascend_persimmon_8b_chat.py b/test/registered/ascend/llm_models/test_npu_persimmon_8b_chat.py similarity index 80% rename from test/registered/ascend/llm_models/test_ascend_persimmon_8b_chat.py rename to test/registered/ascend/llm_models/test_npu_persimmon_8b_chat.py index 1bb336ca1..9958edc25 100644 --- a/test/registered/ascend/llm_models/test_ascend_persimmon_8b_chat.py +++ b/test/registered/ascend/llm_models/test_npu_persimmon_8b_chat.py @@ -1,3 +1,4 @@ +import os import unittest from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin @@ -5,7 +6,11 @@ from sglang.test.ascend.test_ascend_utils import PERSIMMON_8B_CHAT_WEIGHTS_PATH from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import CustomTestCase -register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-1-npu-a3", + nightly=False, +) class TestPersimmon8BChat(GSM8KAscendMixin, CustomTestCase): @@ -15,6 +20,7 @@ class TestPersimmon8BChat(GSM8KAscendMixin, CustomTestCase): [Test Target] Howeee/persimmon-8b-chat """ + os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" model = PERSIMMON_8B_CHAT_WEIGHTS_PATH accuracy = 0.17 diff --git a/test/registered/ascend/llm_models/test_ascend_phi_4_multimodal.py b/test/registered/ascend/llm_models/test_npu_phi_4_multimodal_llm.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_phi_4_multimodal.py rename to test/registered/ascend/llm_models/test_npu_phi_4_multimodal_llm.py diff --git a/test/registered/ascend/llm_models/test_ascend_qwen3_0_6b.py b/test/registered/ascend/llm_models/test_npu_qwen3_0_6b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_qwen3_0_6b.py rename to test/registered/ascend/llm_models/test_npu_qwen3_0_6b.py diff --git a/test/registered/ascend/llm_models/test_ascend_qwen3_1_7b_gptq_int8.py b/test/registered/ascend/llm_models/test_npu_qwen3_1_7b_gptq_int8.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_qwen3_1_7b_gptq_int8.py rename to test/registered/ascend/llm_models/test_npu_qwen3_1_7b_gptq_int8.py diff --git a/test/registered/ascend/llm_models/test_ascend_qwen3_235b_a22b_w8a8.py b/test/registered/ascend/llm_models/test_npu_qwen3_235b_a22b_w8a8.py similarity index 96% rename from test/registered/ascend/llm_models/test_ascend_qwen3_235b_a22b_w8a8.py rename to test/registered/ascend/llm_models/test_npu_qwen3_235b_a22b_w8a8.py index 99e09aa62..944a0bc7c 100644 --- a/test/registered/ascend/llm_models/test_ascend_qwen3_235b_a22b_w8a8.py +++ b/test/registered/ascend/llm_models/test_npu_qwen3_235b_a22b_w8a8.py @@ -17,6 +17,7 @@ class TestQwen3235BA22BW8A8(GSM8KAscendMixin, CustomTestCase): model = QWEN3_235B_A22B_W8A8_WEIGHTS_PATH accuracy = 0.955 + timeout_for_server_launch = 3000 other_args = [ "--trust-remote-code", "--mem-fraction-static", diff --git a/test/registered/ascend/llm_models/test_ascend_qwen3_30b.py b/test/registered/ascend/llm_models/test_npu_qwen3_30b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_qwen3_30b.py rename to test/registered/ascend/llm_models/test_npu_qwen3_30b.py diff --git a/test/registered/ascend/llm_models/test_ascend_qwen3_30b_w4a4.py b/test/registered/ascend/llm_models/test_npu_qwen3_30b_w4a4.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_qwen3_30b_w4a4.py rename to test/registered/ascend/llm_models/test_npu_qwen3_30b_w4a4.py diff --git a/test/registered/ascend/llm_models/test_ascend_qwen3_32b.py b/test/registered/ascend/llm_models/test_npu_qwen3_32b.py similarity index 88% rename from test/registered/ascend/llm_models/test_ascend_qwen3_32b.py rename to test/registered/ascend/llm_models/test_npu_qwen3_32b.py index eb8806a2c..b6d638d92 100644 --- a/test/registered/ascend/llm_models/test_ascend_qwen3_32b.py +++ b/test/registered/ascend/llm_models/test_npu_qwen3_32b.py @@ -9,19 +9,18 @@ register_npu_ci( est_time=400, suite="nightly-4-npu-a3", nightly=True, - disabled="run failed", ) class TestQwen332B(GSM8KAscendMixin, CustomTestCase): - """Testcase: Verify that the inference accuracy of the Qwen/Qwen3-32B model on the GSM8K dataset is no less than 0.88. + """Testcase: Verify that the inference accuracy of the Qwen/Qwen3-32B model on the GSM8K dataset is no less than 0.86. [Test Category] Model [Test Target] Qwen/Qwen3-32B """ model = QWEN3_32B_WEIGHTS_PATH - accuracy = 0.88 + accuracy = 0.86 other_args = [ "--trust-remote-code", "--mem-fraction-static", diff --git a/test/registered/ascend/llm_models/test_ascend_qwen3_coder_480b_a35b.py b/test/registered/ascend/llm_models/test_npu_qwen3_coder_480b_a35b.py similarity index 97% rename from test/registered/ascend/llm_models/test_ascend_qwen3_coder_480b_a35b.py rename to test/registered/ascend/llm_models/test_npu_qwen3_coder_480b_a35b.py index cf3c42665..ada30cf75 100644 --- a/test/registered/ascend/llm_models/test_ascend_qwen3_coder_480b_a35b.py +++ b/test/registered/ascend/llm_models/test_npu_qwen3_coder_480b_a35b.py @@ -11,7 +11,6 @@ register_npu_ci( est_time=400, suite="nightly-16-npu-a3", nightly=True, - disabled="run failed", ) diff --git a/test/registered/ascend/llm_models/test_ascend_qwq_32b_w8a8.py b/test/registered/ascend/llm_models/test_npu_qwq_32b_w8a8.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_qwq_32b_w8a8.py rename to test/registered/ascend/llm_models/test_npu_qwq_32b_w8a8.py diff --git a/test/registered/ascend/llm_models/test_ascend_smollm_1_7b.py b/test/registered/ascend/llm_models/test_npu_smollm_1_7b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_smollm_1_7b.py rename to test/registered/ascend/llm_models/test_npu_smollm_1_7b.py diff --git a/test/registered/ascend/llm_models/test_ascend_stablelm-2-1_6b.py b/test/registered/ascend/llm_models/test_npu_stablelm_2_1_6b.py similarity index 100% rename from test/registered/ascend/llm_models/test_ascend_stablelm-2-1_6b.py rename to test/registered/ascend/llm_models/test_npu_stablelm_2_1_6b.py diff --git a/test/registered/ascend/rerank_models/test_ascend_cross_encoder_models.py b/test/registered/ascend/rerank_models/test_npu_bge_reranker_v2_m3.py similarity index 84% rename from test/registered/ascend/rerank_models/test_ascend_cross_encoder_models.py rename to test/registered/ascend/rerank_models/test_npu_bge_reranker_v2_m3.py index 8593bf367..800a2f824 100644 --- a/test/registered/ascend/rerank_models/test_ascend_cross_encoder_models.py +++ b/test/registered/ascend/rerank_models/test_npu_bge_reranker_v2_m3.py @@ -3,6 +3,7 @@ import unittest import torch +from sglang.test.ascend.test_ascend_utils import BGE_RERANKER_V2_M3_WEIGHTS_PATH from sglang.test.ci.ci_register import register_npu_ci from sglang.test.runners import TEST_RERANK_QUERY_DOCS, HFRunner, SRTRunner from sglang.test.test_utils import CustomTestCase @@ -11,17 +12,22 @@ register_npu_ci( est_time=400, suite="nightly-1-npu-a3", nightly=True, - disabled="cross encoder scores are not all close", ) MODELS = [ - ("/root/.cache/modelscope/hub/models/BAAI/bge-reranker-v2-m3", 1, 1e-2), + (BGE_RERANKER_V2_M3_WEIGHTS_PATH, 1, 1e-2), ] ATTENTION_BACKEND = ["ascend"] TORCH_DTYPES = [torch.bfloat16] -class TestCrossEncoderModels(CustomTestCase): +class TestBgeReranker(CustomTestCase): + """Testcase: This test case validates that the cross-encoder scores from the BAAI/bge-reranker-v2-m3 model in the + SGLang framework are less than 1e-2 different from the Hugging Face implementation. + + [Test Category] Model + [Test Target] BAAI/bge-reranker-v2-m3 + """ @classmethod def setUpClass(cls): diff --git a/test/registered/ascend/reward_models/test_ascend_gemma_2_27b_v0_2.py b/test/registered/ascend/reward_models/test_npu_gemma_2_27b_v0_2.py similarity index 100% rename from test/registered/ascend/reward_models/test_ascend_gemma_2_27b_v0_2.py rename to test/registered/ascend/reward_models/test_npu_gemma_2_27b_v0_2.py diff --git a/test/registered/ascend/reward_models/test_npu_internlm2_7b_reward.py b/test/registered/ascend/reward_models/test_npu_internlm2_7b_reward.py new file mode 100644 index 000000000..7386e4df9 --- /dev/null +++ b/test/registered/ascend/reward_models/test_npu_internlm2_7b_reward.py @@ -0,0 +1,65 @@ +import os + +os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" +import multiprocessing as mp +import unittest + +import torch + +from sglang.test.ascend.test_ascend_utils import INTERNLM2_7B_REWARD_WEIGHTS_PATH +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.runners import SRTRunner +from sglang.test.test_utils import CustomTestCase + +register_npu_ci( + est_time=400, + suite="nightly-4-npu-a3", + nightly=False, +) + +PROMPT = ( + "What is the range of the numeric output of a sigmoid node in a neural network?" +) +RESPONSE1 = "The output of a sigmoid node is bounded between -1 and 1." +RESPONSE2 = "The output of a sigmoid node is bounded between 0 and 1." + +CONVS = [ + [{"role": "user", "content": PROMPT}, {"role": "assistant", "content": RESPONSE1}], + [{"role": "user", "content": PROMPT}, {"role": "assistant", "content": RESPONSE2}], +] + + +class TestInternlm2(CustomTestCase): + """Testcase: This test case verifies that the Shanghai_AI_Laboratory/internlm2-7b-reward model can successfully generate reward + scores for different conversational responses using the SGLang framework, without comparing to a reference implementation. + + [Test Category] Model + [Test Target] Shanghai_AI_Laboratory/internlm2-7b-reward + """ + + model_path = INTERNLM2_7B_REWARD_WEIGHTS_PATH + torch_dtype = torch.float16 + + @classmethod + def setUpClass(cls): + mp.set_start_method("spawn", force=True) + + def test_assert_close_reward_scores(self): + with SRTRunner( + self.model_path, + torch_dtype=self.torch_dtype, + model_type="reward", + trust_remote_code=True, + disable_cuda_graph=True, + tp_size=4, + mem_fraction_static=0.8, + ) as srt_runner: + prompts = srt_runner.tokenizer.apply_chat_template(CONVS, tokenize=False) + srt_outputs = srt_runner.forward(prompts) + srt_scores = torch.tensor(srt_outputs.scores) + print(f"accuracy: {srt_scores}") + self.assertIsInstance(srt_scores, torch.Tensor) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/reward_models/test_npu_llama_3_1_8b_v0_2.py b/test/registered/ascend/reward_models/test_npu_llama_3_1_8b_v0_2.py new file mode 100644 index 000000000..2f23aaf59 --- /dev/null +++ b/test/registered/ascend/reward_models/test_npu_llama_3_1_8b_v0_2.py @@ -0,0 +1,86 @@ +import multiprocessing as mp +import unittest + +import torch + +from sglang.test.ascend.test_ascend_utils import ( + SKYWORK_REWARD_LLAMA_3_1_8B_V0_2_WEIGHTS_PATH, +) +from sglang.test.ci.ci_register import register_npu_ci +from sglang.test.runners import HFRunner, SRTRunner +from sglang.test.test_utils import CustomTestCase + +register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=False) + +MODELS = [ + (SKYWORK_REWARD_LLAMA_3_1_8B_V0_2_WEIGHTS_PATH, 1, 4e-2), +] +TORCH_DTYPES = [torch.float16] + +PROMPT = ( + "What is the range of the numeric output of a sigmoid node in a neural network?" +) +RESPONSE1 = "The output of a sigmoid node is bounded between -1 and 1." +RESPONSE2 = "The output of a sigmoid node is bounded between 0 and 1." + +CONVS = [ + [{"role": "user", "content": PROMPT}, {"role": "assistant", "content": RESPONSE1}], + [{"role": "user", "content": PROMPT}, {"role": "assistant", "content": RESPONSE2}], +] + + +class TestLlama(CustomTestCase): + """Testcase: This test case validates that the reward scores from the Skywork/Skywork-Reward-Llama-3.1-8B-v0.2 model + in the SGLang framework are less than 4e-2 different from the Hugging Face implementation. + + [Test Category] Model + [Test Target] Skywork/Skywork-Reward-Llama-3.1-8B-v0.2 + """ + + @classmethod + def setUpClass(cls): + mp.set_start_method("spawn", force=True) + + def assert_close_reward_scores( + self, + convs, + model_path, + tp_size, + torch_dtype, + tolerance, + ) -> None: + with HFRunner( + model_path, + torch_dtype=torch_dtype, + model_type="reward", + ) as hf_runner: + hf_outputs = hf_runner.forward(convs) + + with SRTRunner( + model_path, + tp_size=tp_size, + torch_dtype=torch_dtype, + model_type="reward", + ) as srt_runner: + prompts = srt_runner.tokenizer.apply_chat_template(convs, tokenize=False) + srt_outputs = srt_runner.forward(prompts) + + hf_scores = torch.tensor(hf_outputs.scores) + srt_scores = torch.tensor(srt_outputs.scores) + print(f"{hf_scores=}") + print(f"{srt_scores=}") + + assert torch.all( + abs(hf_scores - srt_scores) < tolerance + ), "reward scores are not all close" + + def test_reward_scores(self): + for model, tp_size, tolerance in MODELS: + for torch_dtype in TORCH_DTYPES: + self.assert_close_reward_scores( + CONVS, model, tp_size, torch_dtype, tolerance + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/registered/ascend/test_ascend_memory_consumption.py b/test/registered/ascend/test_npu_memory_consumption.py similarity index 100% rename from test/registered/ascend/test_ascend_memory_consumption.py rename to test/registered/ascend/test_npu_memory_consumption.py diff --git a/test/registered/ascend/vlm_models/test_ascend_deepseek_vl2.py b/test/registered/ascend/vlm_models/test_npu_deepseek_vl2.py similarity index 85% rename from test/registered/ascend/vlm_models/test_ascend_deepseek_vl2.py rename to test/registered/ascend/vlm_models/test_npu_deepseek_vl2.py index 57a397620..4e796d734 100644 --- a/test/registered/ascend/vlm_models/test_ascend_deepseek_vl2.py +++ b/test/registered/ascend/vlm_models/test_npu_deepseek_vl2.py @@ -4,7 +4,12 @@ from sglang.test.ascend.test_ascend_utils import DEEPSEEK_VL2_WEIGHTS_PATH from sglang.test.ascend.vlm_utils import TestVLMModels from sglang.test.ci.ci_register import register_npu_ci -register_npu_ci(est_time=400, suite="nightly-4-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-4-npu-a3", + nightly=True, + disabled="run failed", +) class TestDeepseekVl2(TestVLMModels): diff --git a/test/registered/ascend/vlm_models/test_ascend_gemma_3_4b_it.py b/test/registered/ascend/vlm_models/test_npu_gemma_3_4b_it.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_gemma_3_4b_it.py rename to test/registered/ascend/vlm_models/test_npu_gemma_3_4b_it.py diff --git a/test/registered/ascend/vlm_models/test_ascend_janus_pro_1b.py b/test/registered/ascend/vlm_models/test_npu_janus_pro_1b.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_janus_pro_1b.py rename to test/registered/ascend/vlm_models/test_npu_janus_pro_1b.py diff --git a/test/registered/ascend/vlm_models/test_ascend_janus_pro_7b.py b/test/registered/ascend/vlm_models/test_npu_janus_pro_7b.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_janus_pro_7b.py rename to test/registered/ascend/vlm_models/test_npu_janus_pro_7b.py diff --git a/test/registered/ascend/vlm_models/test_ascend_kimi_vl_a3b_instruct.py b/test/registered/ascend/vlm_models/test_npu_kimi_vl_a3b_instruct.py similarity index 85% rename from test/registered/ascend/vlm_models/test_ascend_kimi_vl_a3b_instruct.py rename to test/registered/ascend/vlm_models/test_npu_kimi_vl_a3b_instruct.py index 6eded6df4..f5eecb3a1 100644 --- a/test/registered/ascend/vlm_models/test_ascend_kimi_vl_a3b_instruct.py +++ b/test/registered/ascend/vlm_models/test_npu_kimi_vl_a3b_instruct.py @@ -4,7 +4,12 @@ from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin from sglang.test.ci.ci_register import register_npu_ci from sglang.test.test_utils import CustomTestCase -register_npu_ci(est_time=400, suite="nightly-4-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-4-npu-a3", + nightly=True, + disabled="run failed", +) class TestKimiVLA3BInstruct(GSM8KAscendMixin, CustomTestCase): diff --git a/test/registered/ascend/vlm_models/test_ascend_llama_3_2_11b_vision_instruct.py b/test/registered/ascend/vlm_models/test_npu_llama_3_2_11b_vision_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_llama_3_2_11b_vision_instruct.py rename to test/registered/ascend/vlm_models/test_npu_llama_3_2_11b_vision_instruct.py diff --git a/test/registered/ascend/vlm_models/test_ascend_mimo_vl_7b_rl.py b/test/registered/ascend/vlm_models/test_npu_mimo_vl_7b_rl.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_mimo_vl_7b_rl.py rename to test/registered/ascend/vlm_models/test_npu_mimo_vl_7b_rl.py diff --git a/test/registered/ascend/vlm_models/test_ascend_minicpm_o_2_6.py b/test/registered/ascend/vlm_models/test_npu_minicpm_o_2_6.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_minicpm_o_2_6.py rename to test/registered/ascend/vlm_models/test_npu_minicpm_o_2_6.py diff --git a/test/registered/ascend/vlm_models/test_ascend_minicpm_v_2_6.py b/test/registered/ascend/vlm_models/test_npu_minicpm_v_2_6.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_minicpm_v_2_6.py rename to test/registered/ascend/vlm_models/test_npu_minicpm_v_2_6.py diff --git a/test/registered/ascend/vlm_models/test_ascend_mistral_small_3_1_24b_instruct_2503.py b/test/registered/ascend/vlm_models/test_npu_mistral_small_3_1_24b_instruct_2503.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_mistral_small_3_1_24b_instruct_2503.py rename to test/registered/ascend/vlm_models/test_npu_mistral_small_3_1_24b_instruct_2503.py diff --git a/test/registered/ascend/vlm_models/test_ascend_phi4_multimodal_instruct.py b/test/registered/ascend/vlm_models/test_npu_phi4_multimodal_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_phi4_multimodal_instruct.py rename to test/registered/ascend/vlm_models/test_npu_phi4_multimodal_instruct.py diff --git a/test/registered/ascend/vlm_models/test_ascend_qwen2_5_vl_3b_instruct.py b/test/registered/ascend/vlm_models/test_npu_qwen2_5_vl_3b_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_qwen2_5_vl_3b_instruct.py rename to test/registered/ascend/vlm_models/test_npu_qwen2_5_vl_3b_instruct.py diff --git a/test/registered/ascend/vlm_models/test_ascend_qwen2_5_vl_72b_instruct.py b/test/registered/ascend/vlm_models/test_npu_qwen2_5_vl_72b_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_qwen2_5_vl_72b_instruct.py rename to test/registered/ascend/vlm_models/test_npu_qwen2_5_vl_72b_instruct.py diff --git a/test/registered/ascend/vlm_models/test_ascend_qwen3_vl_235b_a22b_instruct.py b/test/registered/ascend/vlm_models/test_npu_qwen3_vl_235b_a22b_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_qwen3_vl_235b_a22b_instruct.py rename to test/registered/ascend/vlm_models/test_npu_qwen3_vl_235b_a22b_instruct.py diff --git a/test/registered/ascend/vlm_models/test_ascend_qwen3_vl_30b_a3b_instruct.py b/test/registered/ascend/vlm_models/test_npu_qwen3_vl_30b_a3b_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_qwen3_vl_30b_a3b_instruct.py rename to test/registered/ascend/vlm_models/test_npu_qwen3_vl_30b_a3b_instruct.py diff --git a/test/registered/ascend/vlm_models/test_ascend_qwen3_vl_4b_instruct.py b/test/registered/ascend/vlm_models/test_npu_qwen3_vl_4b_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_qwen3_vl_4b_instruct.py rename to test/registered/ascend/vlm_models/test_npu_qwen3_vl_4b_instruct.py diff --git a/test/registered/ascend/vlm_models/test_ascend_qwen3_vl_8b_instruct.py b/test/registered/ascend/vlm_models/test_npu_qwen3_vl_8b_instruct.py similarity index 100% rename from test/registered/ascend/vlm_models/test_ascend_qwen3_vl_8b_instruct.py rename to test/registered/ascend/vlm_models/test_npu_qwen3_vl_8b_instruct.py diff --git a/test/registered/distributed/test_load_weights_from_remote_instance_npu.py b/test/registered/distributed/test_load_weights_from_remote_instance_npu.py index 7ace72c5f..6253610ea 100644 --- a/test/registered/distributed/test_load_weights_from_remote_instance_npu.py +++ b/test/registered/distributed/test_load_weights_from_remote_instance_npu.py @@ -38,7 +38,12 @@ from sglang.utils import terminate_process mp.set_start_method("spawn", force=True) -register_npu_ci(est_time=400, suite="nightly-1-npu-a3", nightly=True) +register_npu_ci( + est_time=400, + suite="nightly-1-npu-a3", + nightly=True, + disabled="run failed", +) def verify_params_close(params1, params2, error_msg):