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:
54
.github/workflows/nightly-test-npu.yml
vendored
54
.github/workflows/nightly-test-npu.yml
vendored
@@ -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()
|
||||
|
||||
153
python/sglang/test/ascend/disaggregation_utils.py
Normal file
153
python/sglang/test/ascend/disaggregation_utils.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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",
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
732
test/registered/ascend/interface/test_npu_api.py
Normal file
732
test/registered/ascend/interface/test_npu_api.py
Normal 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()
|
||||
@@ -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()
|
||||
134
test/registered/ascend/interface/test_npu_api_encode.py
Normal file
134
test/registered/ascend/interface/test_npu_api_encode.py
Normal 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()
|
||||
@@ -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"}],
|
||||
163
test/registered/ascend/interface/test_npu_matched_stop.py
Normal file
163
test/registered/ascend/interface/test_npu_matched_stop.py
Normal 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()
|
||||
@@ -0,0 +1,943 @@
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import openai
|
||||
|
||||
from sglang.srt.utils import kill_process_tree
|
||||
from sglang.srt.utils.hf_transformers_utils import get_tokenizer
|
||||
from sglang.test.ascend.test_ascend_utils import LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH
|
||||
from sglang.test.ci.ci_register import register_npu_ci
|
||||
from sglang.test.test_utils import (
|
||||
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||
DEFAULT_URL_FOR_TEST,
|
||||
CustomTestCase,
|
||||
popen_launch_server,
|
||||
)
|
||||
|
||||
register_npu_ci(
|
||||
est_time=400,
|
||||
suite="nightly-1-npu-a3",
|
||||
nightly=True,
|
||||
disabled="https://github.com/Ascend/sglang/issues/39",
|
||||
)
|
||||
|
||||
|
||||
class TestOpenAIServerFunctionCalling(CustomTestCase):
|
||||
"""Testcase:Verify the correctness of full-scenario OpenAI-style function calling with llama3 parser for Llama-3.2-1B-Instruct model.
|
||||
Cover: Single/multi-turn calls, streaming/non-streaming returns, multi-parameter verification of tool_choice, and JSON parsing validity of function parameters.
|
||||
|
||||
[Test Category] Interface
|
||||
[Test Target] /v1/chat/completions
|
||||
"""
|
||||
|
||||
# NOTE: this system_message is for Llama3.2 system prompt. Without this,
|
||||
# sometimes Llama3.2 gives a different tool call format such as:
|
||||
# '<|python_tag|>{"type": "function", "function": "add", "parameters": {"a": "3", "b": "5"}}'
|
||||
SYSTEM_MESSAGE = (
|
||||
"You are a helpful assistant with tool calling capabilities. "
|
||||
"Only reply with a tool call if the function exists in the library provided by the user. "
|
||||
"If it doesn't exist, just reply directly in natural language. "
|
||||
"When you receive a tool call response, use the output to format an answer to the original user question. "
|
||||
"You have access to the following functions. "
|
||||
"To call a function, please respond with JSON for a function call. "
|
||||
'Respond in the format {"name": function name, "parameters": dictionary of argument name and its value}. '
|
||||
"Do not use variables.\n\n"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Replace with the model name needed for testing
|
||||
cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH
|
||||
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||
cls.api_key = "sk-123456"
|
||||
|
||||
# Start the local OpenAI Server. If necessary, you can add other parameters such as --enable-tools.
|
||||
cls.process = popen_launch_server(
|
||||
cls.model,
|
||||
cls.base_url,
|
||||
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||
api_key=cls.api_key,
|
||||
other_args=[
|
||||
# If your server needs extra parameters to test function calling, please add them here.
|
||||
"--attention-backend",
|
||||
"ascend",
|
||||
"--disable-cuda-graph",
|
||||
"--tool-call-parser",
|
||||
"llama3",
|
||||
],
|
||||
)
|
||||
cls.base_url += "/v1"
|
||||
cls.tokenizer = get_tokenizer(cls.model)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
kill_process_tree(cls.process.pid)
|
||||
|
||||
def test_function_calling_format(self):
|
||||
"""
|
||||
Test: Whether the function call format returned by the AI is correct.
|
||||
When returning a tool call, message.content should be None, and tool_calls should be a list.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "add",
|
||||
"description": "Compute the sum of two numbers",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer",
|
||||
"description": "A number",
|
||||
},
|
||||
"b": {
|
||||
"type": "integer",
|
||||
"description": "A number",
|
||||
},
|
||||
},
|
||||
"required": ["a", "b"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": self.SYSTEM_MESSAGE},
|
||||
{"role": "user", "content": "Compute (3+5)"},
|
||||
]
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=False,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
tool_calls = response.choices[0].message.tool_calls
|
||||
|
||||
assert (
|
||||
isinstance(tool_calls, list) and len(tool_calls) > 0
|
||||
), "tool_calls should be a non-empty list"
|
||||
|
||||
function_name = tool_calls[0].function.name
|
||||
assert function_name == "add", "Function name should be 'add'"
|
||||
|
||||
# This unit test is too difficult for default model. Mark it as optional unit tests so it won't trigger unless specified.
|
||||
def _test_function_calling_multiturn(self):
|
||||
"""
|
||||
Test: Whether the function call format returned by the AI is correct.
|
||||
When returning a tool call, message.content should be None, and tool_calls should be a list.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "add",
|
||||
"description": "Compute the sum of two numbers",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer",
|
||||
"description": "A number",
|
||||
},
|
||||
"b": {
|
||||
"type": "integer",
|
||||
"description": "A number",
|
||||
},
|
||||
},
|
||||
"required": ["a", "b"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [{"role": "user", "content": "Compute (3+5)"}]
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=False,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
tool_call = response.choices[0].message.tool_calls[0]
|
||||
function_name = tool_call.function.name
|
||||
assert function_name == "add", "Function name should be 'add'"
|
||||
function_arguments = json.loads(tool_call.function.arguments)
|
||||
assert function_arguments in [
|
||||
{"a": 3, "b": 5},
|
||||
{"a": "3", "b": "5"},
|
||||
], f"Unexpected function arguments: {function_arguments}"
|
||||
|
||||
messages.append(response.choices[0].message)
|
||||
messages.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": "8",
|
||||
"name": function_name,
|
||||
}
|
||||
)
|
||||
|
||||
final_response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=False,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
assert (
|
||||
"8" in final_response.choices[0].message.content
|
||||
), "tool_call response should have the sum 8 in the content"
|
||||
|
||||
def test_function_calling_streaming_simple(self):
|
||||
"""
|
||||
Test: Whether the function name can be correctly recognized in streaming mode.
|
||||
- Expect a function call to be found, and the function name to be correct.
|
||||
- Verify that streaming mode returns at least multiple chunks.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "The city to find the weather for",
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"description": "Weather unit (celsius or fahrenheit)",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
"required": ["city", "unit"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": self.SYSTEM_MESSAGE},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is the temperature in Paris in celsius??",
|
||||
},
|
||||
]
|
||||
|
||||
response_stream = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=True,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
chunks = list(response_stream)
|
||||
self.assertTrue(len(chunks) > 0, "Streaming should return at least one chunk")
|
||||
|
||||
found_function_name = False
|
||||
for chunk in chunks:
|
||||
choice = chunk.choices[0]
|
||||
# Check whether the current chunk contains tool_calls
|
||||
if choice.delta.tool_calls:
|
||||
tool_call = choice.delta.tool_calls[0]
|
||||
if tool_call.function.name:
|
||||
self.assertEqual(
|
||||
tool_call.function.name,
|
||||
"get_current_weather",
|
||||
"Function name should be 'get_current_weather'",
|
||||
)
|
||||
found_function_name = True
|
||||
break
|
||||
|
||||
self.assertTrue(
|
||||
found_function_name,
|
||||
"Target function name 'get_current_weather' was not found in the streaming chunks",
|
||||
)
|
||||
|
||||
finish_reason = chunks[-1].choices[0].finish_reason
|
||||
self.assertEqual(
|
||||
finish_reason,
|
||||
"tool_calls",
|
||||
"Final response of function calling should have finish_reason 'tool_calls'",
|
||||
)
|
||||
|
||||
def test_function_calling_streaming_args_parsing(self):
|
||||
"""
|
||||
Test: Whether the function call arguments returned in streaming mode can be correctly concatenated into valid JSON.
|
||||
- The user request requires multiple parameters.
|
||||
- AI may return the arguments in chunks that need to be concatenated.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "add",
|
||||
"description": "Compute the sum of two integers",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer",
|
||||
"description": "First integer",
|
||||
},
|
||||
"b": {
|
||||
"type": "integer",
|
||||
"description": "Second integer",
|
||||
},
|
||||
},
|
||||
"required": ["a", "b"],
|
||||
},
|
||||
"strict": True, # Llama-3.2-1B is flaky in tool call. It won't always respond with parameters unless we set strict.
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": self.SYSTEM_MESSAGE},
|
||||
{"role": "user", "content": "Please sum 5 and 7, just call the function."},
|
||||
]
|
||||
|
||||
response_stream = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.9,
|
||||
top_p=0.9,
|
||||
stream=True,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
argument_fragments = []
|
||||
chunks = list(response_stream)
|
||||
function_name = None
|
||||
for chunk in chunks:
|
||||
choice = chunk.choices[0]
|
||||
if choice.delta.tool_calls:
|
||||
tool_call = choice.delta.tool_calls[0]
|
||||
# Record the function name on first occurrence
|
||||
function_name = tool_call.function.name or function_name
|
||||
# In case of multiple chunks, JSON fragments may need to be concatenated
|
||||
if tool_call.function.arguments is not None:
|
||||
argument_fragments.append(tool_call.function.arguments)
|
||||
|
||||
self.assertEqual(function_name, "add", "Function name should be 'add'")
|
||||
joined_args = "".join(argument_fragments)
|
||||
self.assertTrue(
|
||||
len(joined_args) > 0,
|
||||
"No parameter fragments were returned in the function call",
|
||||
)
|
||||
|
||||
finish_reason = chunks[-1].choices[0].finish_reason
|
||||
self.assertEqual(
|
||||
finish_reason,
|
||||
"tool_calls",
|
||||
"Final response of function calling should have finish_reason 'tool_calls'",
|
||||
)
|
||||
|
||||
# Check whether the concatenated JSON is valid
|
||||
try:
|
||||
args_obj = json.loads(joined_args)
|
||||
except json.JSONDecodeError:
|
||||
self.fail(
|
||||
"The concatenated tool call arguments are not valid JSON, parsing failed"
|
||||
)
|
||||
|
||||
self.assertIn("a", args_obj, "Missing parameter 'a'")
|
||||
self.assertIn("b", args_obj, "Missing parameter 'b'")
|
||||
self.assertEqual(str(args_obj["a"]), "5", "Parameter a should be 5")
|
||||
self.assertEqual(str(args_obj["b"]), "7", "Parameter b should be 7")
|
||||
|
||||
def test_function_call_strict(self):
|
||||
"""
|
||||
Test: Whether the strict mode of function calling works as expected.
|
||||
- When strict mode is enabled, the AI should not return a function call if the function name is not recognized.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "sub",
|
||||
"description": "Compute the difference of two integers",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"int_a": {
|
||||
"type": "integer",
|
||||
"description": "First integer",
|
||||
},
|
||||
"int_b": {
|
||||
"type": "integer",
|
||||
"description": "Second integer",
|
||||
},
|
||||
},
|
||||
"required": ["int_a", "int_b"],
|
||||
},
|
||||
"strict": True,
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "Please compute 5 - 7, using your tool."}
|
||||
]
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=False,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
tool_calls = response.choices[0].message.tool_calls
|
||||
function_name = tool_calls[0].function.name
|
||||
arguments = tool_calls[0].function.arguments
|
||||
args_obj = json.loads(arguments)
|
||||
|
||||
self.assertEqual(function_name, "sub", "Function name should be 'sub'")
|
||||
self.assertEqual(str(args_obj["int_a"]), "5", "Parameter int_a should be 5")
|
||||
self.assertEqual(str(args_obj["int_b"]), "7", "Parameter int_b should be 7")
|
||||
|
||||
def test_function_call_required(self):
|
||||
"""
|
||||
Test: Whether tool_choice: "required" works as expected
|
||||
- When tool_choice == "required", the model should return one or more tool_calls.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "sub",
|
||||
"description": "Compute the difference of two integers",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"int_a": {
|
||||
"type": "integer",
|
||||
"description": "First integer",
|
||||
},
|
||||
"int_b": {
|
||||
"type": "integer",
|
||||
"description": "Second integer",
|
||||
},
|
||||
},
|
||||
"required": ["int_a", "int_b"],
|
||||
},
|
||||
"strict": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "use this to get latest weather information for a city given its name",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "name of the city to get weather for",
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
messages = [{"role": "user", "content": "What is the capital of France?"}]
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=False,
|
||||
tools=tools,
|
||||
tool_choice="required",
|
||||
)
|
||||
|
||||
tool_calls = response.choices[0].message.tool_calls
|
||||
self.assertIsNotNone(tool_calls, "No tool_calls in the response")
|
||||
function_name = tool_calls[0].function.name
|
||||
arguments = tool_calls[0].function.arguments
|
||||
args_obj = json.loads(arguments)
|
||||
|
||||
self.assertEqual(
|
||||
function_name,
|
||||
"get_weather",
|
||||
f"Function name should be 'get_weather', got: {function_name}",
|
||||
)
|
||||
self.assertIn(
|
||||
"city", args_obj, f"Function arguments should have 'city', got: {args_obj}"
|
||||
)
|
||||
|
||||
# Make the test more robust by checking type and accepting valid responses
|
||||
city_value = args_obj["city"]
|
||||
self.assertIsInstance(
|
||||
city_value,
|
||||
str,
|
||||
f"Parameter city should be a string, got: {type(city_value)}",
|
||||
)
|
||||
self.assertTrue(
|
||||
"Paris" in city_value or "France" in city_value,
|
||||
f"Parameter city should contain either 'Paris' or 'France', got: {city_value}",
|
||||
)
|
||||
|
||||
def test_function_call_specific(self):
|
||||
"""
|
||||
Test: Whether tool_choice: ToolChoice works as expected
|
||||
- When tool_choice is a specific ToolChoice, the model should return one or more tool_calls.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "sub",
|
||||
"description": "Compute the difference of two integers",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"int_a": {
|
||||
"type": "integer",
|
||||
"description": "First integer",
|
||||
},
|
||||
"int_b": {
|
||||
"type": "integer",
|
||||
"description": "Second integer",
|
||||
},
|
||||
},
|
||||
"required": ["int_a", "int_b"],
|
||||
},
|
||||
"strict": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "use this to get latest weather information for a city given its name",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "name of the city to get weather for",
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
messages = [{"role": "user", "content": "What is the capital of France?"}]
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=False,
|
||||
tools=tools,
|
||||
tool_choice={"type": "function", "function": {"name": "get_weather"}},
|
||||
)
|
||||
|
||||
tool_calls = response.choices[0].message.tool_calls
|
||||
self.assertIsNotNone(tool_calls, "No tool_calls in the response")
|
||||
function_name = tool_calls[0].function.name
|
||||
arguments = tool_calls[0].function.arguments
|
||||
args_obj = json.loads(arguments)
|
||||
|
||||
self.assertEqual(
|
||||
function_name, "get_weather", "Function name should be 'get_weather'"
|
||||
)
|
||||
self.assertIn("city", args_obj, "Function arguments should have 'city'")
|
||||
|
||||
def test_streaming_multiple_choices_finish_reason(self):
|
||||
"""
|
||||
Test: Verify that each choice gets its own finish_reason chunk in streaming mode with n > 1.
|
||||
This tests the fix for the bug where only the last index got a finish_reason chunk.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA",
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
"required": ["location"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "What is the weather like in Los Angeles?"}
|
||||
]
|
||||
|
||||
# Request with n=2 to get multiple choices
|
||||
response_stream = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
max_tokens=2048,
|
||||
temperature=0.8,
|
||||
stream=True,
|
||||
tools=tools,
|
||||
tool_choice="required", # Force tool calls
|
||||
n=2, # Multiple choices
|
||||
)
|
||||
|
||||
chunks = list(response_stream)
|
||||
|
||||
# Track finish_reason chunks for each index
|
||||
finish_reason_chunks = {}
|
||||
for chunk in chunks:
|
||||
if chunk.choices:
|
||||
for choice in chunk.choices:
|
||||
if choice.finish_reason is not None:
|
||||
index = choice.index
|
||||
if index not in finish_reason_chunks:
|
||||
finish_reason_chunks[index] = []
|
||||
finish_reason_chunks[index].append(choice.finish_reason)
|
||||
|
||||
# Verify we got finish_reason chunks for both indices
|
||||
self.assertEqual(
|
||||
len(finish_reason_chunks),
|
||||
2,
|
||||
f"Expected finish_reason chunks for 2 indices, got {len(finish_reason_chunks)}",
|
||||
)
|
||||
|
||||
# Verify both index 0 and 1 have finish_reason
|
||||
self.assertIn(
|
||||
0, finish_reason_chunks, "Missing finish_reason chunk for index 0"
|
||||
)
|
||||
self.assertIn(
|
||||
1, finish_reason_chunks, "Missing finish_reason chunk for index 1"
|
||||
)
|
||||
|
||||
# Verify the finish_reason is "tool_calls" since we forced tool calls
|
||||
for index, reasons in finish_reason_chunks.items():
|
||||
self.assertEqual(
|
||||
reasons[-1], # Last finish_reason for this index
|
||||
"tool_calls",
|
||||
f"Expected finish_reason 'tool_calls' for index {index}, got {reasons[-1]}",
|
||||
)
|
||||
|
||||
def test_function_calling_streaming_no_tool_call(self):
|
||||
"""
|
||||
Test: Whether the finish_reason is stop in streaming mode when no tool call is given.
|
||||
- Expect no function call to be found.
|
||||
- Verify that finish_reason is stop
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "The city to find the weather for",
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"description": "Weather unit (celsius or fahrenheit)",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
"required": ["city", "unit"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
messages = [{"role": "user", "content": "Who are you?"}]
|
||||
|
||||
response_stream = client.chat.completions.create(
|
||||
model=self.model,
|
||||
max_tokens=2048,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
top_p=0.8,
|
||||
stream=True,
|
||||
tools=tools,
|
||||
tool_choice="none",
|
||||
)
|
||||
|
||||
chunks = list(response_stream)
|
||||
self.assertTrue(len(chunks) > 0, "Streaming should return at least one chunk")
|
||||
|
||||
found_tool_call = False
|
||||
for chunk in chunks:
|
||||
choice = chunk.choices[0]
|
||||
# Check whether the current chunk contains tool_calls
|
||||
found_tool_call = choice.delta.tool_calls is not None
|
||||
|
||||
self.assertFalse(
|
||||
found_tool_call,
|
||||
"Shouldn't have any tool_call in the streaming chunks",
|
||||
)
|
||||
|
||||
finish_reason = chunks[-1].choices[0].finish_reason
|
||||
self.assertEqual(
|
||||
finish_reason,
|
||||
"stop",
|
||||
"Final response of no function calling should have finish_reason 'stop'",
|
||||
)
|
||||
|
||||
def test_streaming_multiple_choices_without_tools(self):
|
||||
"""
|
||||
Test: Verify that each choice gets its own finish_reason chunk without tool calls.
|
||||
This tests the fix for regular content streaming with multiple choices.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
messages = [{"role": "user", "content": "Say hello in one word."}]
|
||||
|
||||
# Request with n=2 to get multiple choices, no tools
|
||||
response_stream = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
temperature=0.8,
|
||||
stream=True,
|
||||
max_tokens=10, # Keep it short
|
||||
n=2, # Multiple choices
|
||||
)
|
||||
|
||||
chunks = list(response_stream)
|
||||
|
||||
# Track finish_reason chunks for each index
|
||||
finish_reason_chunks = {}
|
||||
for chunk in chunks:
|
||||
if chunk.choices:
|
||||
for choice in chunk.choices:
|
||||
if choice.finish_reason is not None:
|
||||
index = choice.index
|
||||
if index not in finish_reason_chunks:
|
||||
finish_reason_chunks[index] = []
|
||||
finish_reason_chunks[index].append(choice.finish_reason)
|
||||
|
||||
# Verify we got finish_reason chunks for both indices
|
||||
self.assertEqual(
|
||||
len(finish_reason_chunks),
|
||||
2,
|
||||
f"Expected finish_reason chunks for 2 indices, got {len(finish_reason_chunks)}",
|
||||
)
|
||||
|
||||
# Verify both index 0 and 1 have finish_reason
|
||||
self.assertIn(
|
||||
0, finish_reason_chunks, "Missing finish_reason chunk for index 0"
|
||||
)
|
||||
self.assertIn(
|
||||
1, finish_reason_chunks, "Missing finish_reason chunk for index 1"
|
||||
)
|
||||
|
||||
# Verify the finish_reason is "stop" (regular completion)
|
||||
for index, reasons in finish_reason_chunks.items():
|
||||
self.assertIn(
|
||||
reasons[-1],
|
||||
["stop", "length"], # Could be either depending on how model responds
|
||||
f"Expected finish_reason 'stop' or 'length' for index {index}, got {reasons[-1]}",
|
||||
)
|
||||
|
||||
|
||||
class TestOpenAIPythonicFunctionCalling(CustomTestCase):
|
||||
"""Testcase:Verify the functionality of Python-style list-format function calling with pythonic parser for Llama-3.2-1B-Instruct model on Ascend NPU backend.
|
||||
Cover: Explicit format prompt verification, streaming call index integrity, and return validity of parallel tool calls.
|
||||
|
||||
[Test Category] Interface
|
||||
[Test Target] /v1/chat/completions
|
||||
"""
|
||||
|
||||
PYTHONIC_TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get the current weather for a given location.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The name of the city or location.",
|
||||
}
|
||||
},
|
||||
"required": ["location"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_tourist_attractions",
|
||||
"description": "Get a list of top tourist attractions for a given city.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "The name of the city to find attractions for.",
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
PYTHONIC_MESSAGES = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"You are a travel assistant. "
|
||||
"When asked to call functions, ALWAYS respond ONLY with a python list of function calls, "
|
||||
"using this format: [func_name1(param1=value1, param2=value2), func_name2(param=value)]. "
|
||||
"Do NOT use JSON, do NOT use variables, do NOT use any other format. "
|
||||
"Here is an example:\n"
|
||||
'[get_weather(location="Paris"), get_tourist_attractions(city="Paris")]'
|
||||
),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
"I'm planning a trip to Tokyo next week. What's the weather like and what are some top tourist attractions? "
|
||||
"Propose parallel tool calls at once, using the python list of function calls format as shown above."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH
|
||||
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||
cls.api_key = "sk-123456"
|
||||
cls.process = popen_launch_server(
|
||||
cls.model,
|
||||
cls.base_url,
|
||||
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||
api_key=cls.api_key,
|
||||
other_args=[
|
||||
"--attention-backend",
|
||||
"ascend",
|
||||
"--disable-cuda-graph",
|
||||
"--tool-call-parser",
|
||||
"pythonic",
|
||||
],
|
||||
)
|
||||
cls.base_url += "/v1"
|
||||
cls.tokenizer = get_tokenizer(cls.model)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
kill_process_tree(cls.process.pid)
|
||||
|
||||
def test_pythonic_tool_call_prompt(self):
|
||||
"""
|
||||
Test: Explicit prompt for pythonic tool call format without chat template.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=self.PYTHONIC_MESSAGES,
|
||||
tools=self.PYTHONIC_TOOLS,
|
||||
temperature=0.1,
|
||||
stream=False,
|
||||
)
|
||||
tool_calls = response.choices[0].message.tool_calls
|
||||
self.assertIsInstance(tool_calls, list, "No tool_calls found")
|
||||
self.assertGreaterEqual(len(tool_calls), 1)
|
||||
names = [tc.function.name for tc in tool_calls]
|
||||
self.assertTrue(
|
||||
"get_weather" in names or "get_tourist_attractions" in names,
|
||||
f"Function name '{names}' should container either 'get_weather' or 'get_tourist_attractions'",
|
||||
)
|
||||
|
||||
def test_pythonic_tool_call_streaming(self):
|
||||
"""
|
||||
Test: Streaming pythonic tool call format; assert tool_call index is present.
|
||||
"""
|
||||
client = openai.Client(api_key=self.api_key, base_url=self.base_url)
|
||||
response_stream = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=self.PYTHONIC_MESSAGES,
|
||||
tools=self.PYTHONIC_TOOLS,
|
||||
temperature=0.1,
|
||||
stream=True,
|
||||
)
|
||||
found_tool_calls = False
|
||||
found_index = False
|
||||
found_names = set()
|
||||
for chunk in response_stream:
|
||||
choice = chunk.choices[0]
|
||||
if getattr(choice.delta, "tool_calls", None):
|
||||
found_tool_calls = True
|
||||
tool_call = choice.delta.tool_calls[0]
|
||||
if hasattr(tool_call, "index") or (
|
||||
isinstance(tool_call, dict) and "index" in tool_call
|
||||
):
|
||||
found_index = True
|
||||
found_names.add(str(tool_call.function.name))
|
||||
|
||||
self.assertTrue(found_tool_calls, "No tool_calls found in streaming response")
|
||||
self.assertTrue(found_index, "No index field found in any streamed tool_call")
|
||||
self.assertTrue(
|
||||
"get_weather" in found_names or "get_tourist_attractions" in found_names,
|
||||
f"Function name '{found_names}' should container either 'get_weather' or 'get_tourist_attractions'",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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()
|
||||
111
test/registered/ascend/interface/test_npu_penalty.py
Normal file
111
test/registered/ascend/interface/test_npu_penalty.py
Normal 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):
|
||||
"""Testcase:Verify successful processing of inference requests with three specific mechanisms(frequency_penalty, presence_penalty, min_new_tokens).
|
||||
|
||||
[Test Category] Interface
|
||||
[Test Target] /generate
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.model = LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH
|
||||
other_args = [
|
||||
"--attention-backend",
|
||||
"ascend",
|
||||
"--disable-cuda-graph",
|
||||
]
|
||||
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||
cls.process = popen_launch_server(
|
||||
cls.model,
|
||||
cls.base_url,
|
||||
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||
other_args=other_args,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
kill_process_tree(cls.process.pid)
|
||||
|
||||
def run_decode(self, sampling_params):
|
||||
# Send inference request with specified sampling/penalty parameters.
|
||||
|
||||
return_logprob = True
|
||||
top_logprobs_num = 5
|
||||
return_text = True
|
||||
n = 1
|
||||
|
||||
response = requests.post(
|
||||
self.base_url + "/generate",
|
||||
json={
|
||||
# prompt that is supposed to generate < 32 tokens
|
||||
"text": "<|start_header_id|>user<|end_header_id|>\n\nWhat is the answer for 1 + 1 = ?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n",
|
||||
"sampling_params": {
|
||||
"max_new_tokens": 48,
|
||||
"n": n,
|
||||
**sampling_params,
|
||||
},
|
||||
"return_logprob": return_logprob,
|
||||
"top_logprobs_num": top_logprobs_num,
|
||||
"return_text_in_logprobs": return_text,
|
||||
"logprob_start_len": 0,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
print(json.dumps(response.json()))
|
||||
print("=" * 100)
|
||||
|
||||
def test_default_values(self):
|
||||
self.run_decode({})
|
||||
|
||||
def test_frequency_penalty(self):
|
||||
self.run_decode({"frequency_penalty": 2})
|
||||
|
||||
def test_min_new_tokens(self):
|
||||
self.run_decode({"min_new_tokens": 16})
|
||||
|
||||
def test_presence_penalty(self):
|
||||
self.run_decode({"presence_penalty": 2})
|
||||
|
||||
def test_penalty_mixed(self):
|
||||
args = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{"frequency_penalty": 2},
|
||||
{"presence_penalty": 1},
|
||||
{"min_new_tokens": 16},
|
||||
{"frequency_penalty": 0.2},
|
||||
{"presence_penalty": 0.4},
|
||||
{"min_new_tokens": 8},
|
||||
{"frequency_penalty": 0.4, "presence_penalty": 0.8},
|
||||
{"frequency_penalty": 0.4, "min_new_tokens": 12},
|
||||
{"presence_penalty": 0.8, "min_new_tokens": 12},
|
||||
{"presence_penalty": -0.3, "frequency_penalty": 1.3, "min_new_tokens": 32},
|
||||
{"presence_penalty": 0.3, "frequency_penalty": -1.3, "min_new_tokens": 32},
|
||||
]
|
||||
random.shuffle(args * 5)
|
||||
with ThreadPoolExecutor(8) as executor:
|
||||
list(executor.map(self.run_decode, args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=3)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
@@ -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",
|
||||
@@ -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()
|
||||
@@ -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__":
|
||||
@@ -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):
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
@@ -11,7 +11,6 @@ register_npu_ci(
|
||||
est_time=400,
|
||||
suite="nightly-16-npu-a3",
|
||||
nightly=True,
|
||||
disabled="run failed",
|
||||
)
|
||||
|
||||
|
||||
@@ -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):
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
@@ -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):
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user