Add NPU basic function testcases (#19382)

Co-authored-by: cy <chenyang08056032@163.com>
Co-authored-by: Cherry_ming <136634645@qq.com>
This commit is contained in:
Sugar920
2026-03-16 15:09:56 +08:00
committed by GitHub
parent e96a3752a0
commit 895e56097c
87 changed files with 4587 additions and 333 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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",

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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"}],

View File

@@ -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()

View File

@@ -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):
"""TestcaseVerify 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):
"""TestcaseVerify 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()

View File

@@ -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()

View File

@@ -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):
"""TestcaseVerify 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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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):

View File

@@ -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",

View File

@@ -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()

View File

@@ -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__":

View File

@@ -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):

View File

@@ -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

View File

@@ -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",
]

View File

@@ -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",
)

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -11,7 +11,6 @@ register_npu_ci(
est_time=400,
suite="nightly-16-npu-a3",
nightly=True,
disabled="run failed",
)

View File

@@ -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):

View File

@@ -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()

View File

@@ -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()

View File

@@ -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):

View File

@@ -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):

View File

@@ -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):