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:
co-authored by
cy
Cherry_ming
parent
e96a3752a0
commit
895e56097c
@@ -50,7 +50,6 @@ jobs:
|
|||||||
pip config set global.index-url http://${CACHING_URL}/pypi/simple
|
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.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
||||||
|
|
||||||
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
# copy required file from our daily cache
|
# copy required file from our daily cache
|
||||||
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
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"
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
STREAMS_PER_DEVICE: 32
|
STREAMS_PER_DEVICE: 32
|
||||||
run: |
|
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
|
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
|
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.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.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
||||||
|
|
||||||
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
# copy required file from our daily cache
|
# copy required file from our daily cache
|
||||||
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
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"
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
STREAMS_PER_DEVICE: 32
|
STREAMS_PER_DEVICE: 32
|
||||||
run: |
|
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
|
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
|
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
|
# speed up by using infra cache services
|
||||||
CACHING_URL="cache-service.nginx-pypi-cache.svc.cluster.local"
|
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
|
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.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
pip config set global.trusted-host "${CACHING_URL}"
|
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
||||||
|
|
||||||
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
# copy required file from our daily cache
|
# copy required file from our daily cache
|
||||||
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
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"
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
STREAMS_PER_DEVICE: 32
|
STREAMS_PER_DEVICE: 32
|
||||||
run: |
|
run: |
|
||||||
|
pip install sglang_router
|
||||||
hf download lmms-lab/MMMU --repo-type dataset
|
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 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
|
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
|
git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git
|
||||||
cd ./lmms-eval
|
cd ./lmms-eval
|
||||||
@@ -202,7 +222,6 @@ jobs:
|
|||||||
pip config set global.index-url http://${CACHING_URL}/pypi/simple
|
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.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
||||||
|
|
||||||
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
# copy required file from our daily cache
|
# copy required file from our daily cache
|
||||||
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
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"
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
STREAMS_PER_DEVICE: 32
|
STREAMS_PER_DEVICE: 32
|
||||||
run: |
|
run: |
|
||||||
|
pip install sglang_router
|
||||||
hf download lmms-lab/MMMU --repo-type dataset
|
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 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
|
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
|
git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git
|
||||||
cd ./lmms-eval
|
cd ./lmms-eval
|
||||||
@@ -243,7 +263,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
part: [0]
|
part: [0, 1]
|
||||||
container:
|
container:
|
||||||
image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:8.5.0-a3-ubuntu22.04-py3.11
|
image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:8.5.0-a3-ubuntu22.04-py3.11
|
||||||
steps:
|
steps:
|
||||||
@@ -260,7 +280,6 @@ jobs:
|
|||||||
pip config set global.index-url http://${CACHING_URL}/pypi/simple
|
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.extra-index-url "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
pip config set global.trusted-host "${CACHING_URL} pypi.tuna.tsinghua.edu.cn"
|
||||||
|
|
||||||
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
# copy required file from our daily cache
|
# copy required file from our daily cache
|
||||||
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
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"
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
STREAMS_PER_DEVICE: 32
|
STREAMS_PER_DEVICE: 32
|
||||||
run: |
|
run: |
|
||||||
|
pip install sglang_router
|
||||||
hf download lmms-lab/MMMU --repo-type dataset
|
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 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
|
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
|
git clone --branch v0.3.3 --depth 1 https://github.com/EvolvingLMMs-Lab/lmms-eval.git
|
||||||
cd ./lmms-eval
|
cd ./lmms-eval
|
||||||
@@ -293,7 +313,7 @@ jobs:
|
|||||||
export PYTHONPATH=$PYTHONPATH:$(pwd)
|
export PYTHONPATH=$PYTHONPATH:$(pwd)
|
||||||
cd ../
|
cd ../
|
||||||
cd test
|
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:
|
check-all-jobs:
|
||||||
if: github.repository == 'sgl-project/sglang' && always()
|
if: github.repository == 'sgl-project/sglang' && always()
|
||||||
|
|||||||
@@ -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.
|
Please remember to sort by variable name within each section.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import copy
|
||||||
import os
|
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 storage directory
|
||||||
MODEL_WEIGHTS_DIR = "/root/.cache/modelscope/hub/models/"
|
MODEL_WEIGHTS_DIR = "/root/.cache/modelscope/hub/models/"
|
||||||
HF_MODEL_WEIGHTS_DIR = "/root/.cache/huggingface/hub/"
|
HF_MODEL_WEIGHTS_DIR = "/root/.cache/huggingface/hub/"
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
# ruff: noqa: E501
|
|
||||||
# LLM model weights path
|
# LLM model weights path
|
||||||
AFM_4_5B_BASE_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "arcee-ai/AFM-4.5B-Base")
|
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")
|
BAICHUAN2_13B_CHAT_WEIGHTS_PATH = os.path.join(
|
||||||
C4AI_COMMAND_R_V01_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "CohereForAI/c4ai-command-r-v01")
|
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")
|
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")
|
DBRX_INSTRUCT_WEIGHTS_PATH = os.path.join(
|
||||||
DEEPSEEK_R1_0528_W4A8_PER_CHANNEL_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "DeepSeek-R1-0528-w4a8-per-channel")
|
MODEL_WEIGHTS_DIR, "AI-ModelScope/dbrx-instruct"
|
||||||
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(
|
||||||
DEEPSEEK_V3_2_EXP_W8A8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "DeepSeek-V3.2-Exp-W8A8")
|
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")
|
DEEPSEEK_V3_2_W8A8_WEIGHTS_PATH = os.path.join(
|
||||||
EXAONE_3_5_7_8B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct")
|
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")
|
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")
|
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_0_3B_A800M_INSTRUCT_WEIGHTS_PATH = os.path.join(
|
||||||
GRANITE_3_1_8B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "ibm-granite/granite-3.1-8b-instruct")
|
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")
|
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")
|
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")
|
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_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_1_8B_INSTRUCT_WEIGHTS_PATH = os.path.join(
|
||||||
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")
|
MODEL_WEIGHTS_DIR, "AI-ModelScope/Llama-3.1-8B-Instruct"
|
||||||
LLAMA_3_2_1B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Llama-3.2-1B-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_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")
|
LLAMA_4_SCOUT_17B_16E_INSTRUCT_WEIGHTS_PATH = os.path.join(
|
||||||
META_LLAMA_3_1_8B_INSTRUCT = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Meta-Llama-3.1-8B-Instruct")
|
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")
|
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")
|
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")
|
MISTRAL_7B_INSTRUCT_V0_2_WEIGHTS_PATH = os.path.join(
|
||||||
OLMOE_1B_7B_0924_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "allenai/OLMoE-1B-7B-0924")
|
MODEL_WEIGHTS_DIR, "mistralai/Mistral-7B-Instruct-v0.2"
|
||||||
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")
|
OLMOE_1B_7B_0924_WEIGHTS_PATH = os.path.join(
|
||||||
QWEN2_5_7B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-7B-Instruct")
|
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_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_1_7B_GPTQ_INT8_WEIGHTS_PATH = os.path.join(
|
||||||
QWEN3_235B_A22B_W8A8_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "vllm-ascend/Qwen3-235B-A22B-W8A8")
|
MODEL_WEIGHTS_DIR, "Qwen/Qwen3-1.7B-GPTQ-Int8"
|
||||||
QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-30B-A3B-Instruct-2507")
|
)
|
||||||
|
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_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_CODER_480B_A35B_INSTRUCT_W8A8_QUAROT_WEIGHTS_PATH = os.path.join(
|
||||||
QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Next-80B-A3B-Instruct")
|
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")
|
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")
|
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")
|
XVERSE_MOE_A36B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "xverse/XVERSE-MoE-A36B")
|
||||||
|
|
||||||
# VLM model weights path
|
# 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")
|
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_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")
|
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")
|
KIMI_VL_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join(
|
||||||
LLAMA_3_2_11B_VISION_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "LLM-Research/Llama-3.2-11B-Vision-Instruct")
|
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_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_ONEVISION_QWEN2_7B_OV_WEIGHTS_PATH = os.path.join(
|
||||||
LLAVA_V1_6_34B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "AI-ModelScope/llava-v1.6-34b")
|
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")
|
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_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")
|
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")
|
MISTRAL_SMALL_3_1_24B_INSTRUCT_2503_WEIGHTS_PATH = os.path.join(
|
||||||
QWEN2_5_VL_3B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-VL-3B-Instruct")
|
MODEL_WEIGHTS_DIR, "mistralai/Mistral-Small-3.1-24B-Instruct-2503"
|
||||||
QWEN2_5_VL_72B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen2.5-VL-72B-Instruct")
|
)
|
||||||
|
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_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_30B_A3B_W8A8_WEIGHTS_PATH = os.path.join(
|
||||||
QWEN3_VL_235B_A22B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-VL-235B-A22B-Instruct")
|
MODEL_WEIGHTS_DIR, "Qwen/Qwen3-30B-A3B-w8a8"
|
||||||
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")
|
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
|
# Embedding model weights path
|
||||||
BGE_LARGE_EN_V1_5_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "bge-large-en-v1.5")
|
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")
|
CLIP_VIT_LARGE_PATCH14_336_WEIGHTS_PATH = os.path.join(
|
||||||
E5_MISTRAL_7B_INSTRUCT_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "intfloat/e5-mistral-7b-instruct")
|
MODEL_WEIGHTS_DIR, "AI-ModelScope/clip-vit-large-patch14-336"
|
||||||
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")
|
E5_MISTRAL_7B_INSTRUCT_WEIGHTS_PATH = os.path.join(
|
||||||
QWEN3_EMBEDDING_8B_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Embedding-8B")
|
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
|
# 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
|
# Reward model weights path
|
||||||
INTERNLM2_7B_REWARD_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Shanghai_AI_Laboratory/internlm2-7b-reward")
|
INTERNLM2_7B_REWARD_WEIGHTS_PATH = os.path.join(
|
||||||
QWEN2_5_1_5B_APEACH_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Howeee/Qwen2.5-1.5B-apeach")
|
MODEL_WEIGHTS_DIR, "Shanghai_AI_Laboratory/internlm2-7b-reward"
|
||||||
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")
|
QWEN2_5_1_5B_APEACH_WEIGHTS_PATH = os.path.join(
|
||||||
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")
|
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
|
# 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()
|
||||||
+68
@@ -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()
|
||||||
+108
@@ -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()
|
||||||
+128
@@ -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()
|
||||||
+118
@@ -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()
|
||||||
+107
@@ -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()
|
||||||
+125
@@ -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()
|
||||||
+118
@@ -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()
|
||||||
+12
-3
@@ -4,6 +4,10 @@ import openai
|
|||||||
|
|
||||||
from sglang.srt.utils import kill_process_tree
|
from sglang.srt.utils import kill_process_tree
|
||||||
from sglang.srt.utils.hf_transformers_utils import get_tokenizer
|
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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import (
|
from sglang.test.test_utils import (
|
||||||
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
@@ -12,7 +16,12 @@ from sglang.test.test_utils import (
|
|||||||
popen_launch_server,
|
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):
|
class TestFimCompletion(CustomTestCase):
|
||||||
@@ -22,7 +31,7 @@ class TestFimCompletion(CustomTestCase):
|
|||||||
[Test Target] --completion-template
|
[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 = [
|
other_args = [
|
||||||
"--completion-template",
|
"--completion-template",
|
||||||
"deepseek_coder",
|
"deepseek_coder",
|
||||||
@@ -86,7 +95,7 @@ class TestFimCompletion(CustomTestCase):
|
|||||||
class TestFimCompletionJson(TestFimCompletion):
|
class TestFimCompletionJson(TestFimCompletion):
|
||||||
other_args = [
|
other_args = [
|
||||||
"--completion-template",
|
"--completion-template",
|
||||||
"./deepseek_coder.json",
|
DEEPSEEK_CODER_JSON_PATH,
|
||||||
"--attention-backend",
|
"--attention-backend",
|
||||||
"ascend",
|
"ascend",
|
||||||
"--disable-cuda-graph",
|
"--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()
|
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
+7
-10
@@ -13,7 +13,12 @@ from sglang.test.test_utils import (
|
|||||||
popen_launch_server,
|
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):
|
class TestEnableThinking(CustomTestCase):
|
||||||
@@ -28,23 +33,19 @@ class TestEnableThinking(CustomTestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.model = QWEN3_30B_A3B_WEIGHTS_PATH
|
cls.model = QWEN3_30B_A3B_WEIGHTS_PATH
|
||||||
cls.base_url = DEFAULT_URL_FOR_TEST
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
cls.api_key = "sk-1234"
|
|
||||||
cls.other_args = [
|
cls.other_args = [
|
||||||
"--reasoning-parser",
|
|
||||||
"qwen3",
|
|
||||||
"--attention-backend",
|
"--attention-backend",
|
||||||
"ascend",
|
"ascend",
|
||||||
"--disable-cuda-graph",
|
"--disable-cuda-graph",
|
||||||
"--mem-fraction-static",
|
"--mem-fraction-static",
|
||||||
0.95,
|
0.95,
|
||||||
"--tp",
|
"--tp",
|
||||||
2,
|
16,
|
||||||
]
|
]
|
||||||
cls.process = popen_launch_server(
|
cls.process = popen_launch_server(
|
||||||
cls.model,
|
cls.model,
|
||||||
cls.base_url,
|
cls.base_url,
|
||||||
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
api_key=cls.api_key,
|
|
||||||
other_args=cls.other_args,
|
other_args=cls.other_args,
|
||||||
)
|
)
|
||||||
cls.additional_chat_kwargs = {}
|
cls.additional_chat_kwargs = {}
|
||||||
@@ -57,7 +58,6 @@ class TestEnableThinking(CustomTestCase):
|
|||||||
# Test non-streaming with "enable_thinking": True, reasoning_content should not be empty
|
# Test non-streaming with "enable_thinking": True, reasoning_content should not be empty
|
||||||
client = requests.post(
|
client = requests.post(
|
||||||
f"{self.base_url}/v1/chat/completions",
|
f"{self.base_url}/v1/chat/completions",
|
||||||
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
||||||
json={
|
json={
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"messages": [{"role": "user", "content": "Hello"}],
|
"messages": [{"role": "user", "content": "Hello"}],
|
||||||
@@ -81,7 +81,6 @@ class TestEnableThinking(CustomTestCase):
|
|||||||
# Test non-streaming with "enable_thinking": False, reasoning_content should be empty
|
# Test non-streaming with "enable_thinking": False, reasoning_content should be empty
|
||||||
client = requests.post(
|
client = requests.post(
|
||||||
f"{self.base_url}/v1/chat/completions",
|
f"{self.base_url}/v1/chat/completions",
|
||||||
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
||||||
json={
|
json={
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"messages": [{"role": "user", "content": "Hello"}],
|
"messages": [{"role": "user", "content": "Hello"}],
|
||||||
@@ -106,7 +105,6 @@ class TestEnableThinking(CustomTestCase):
|
|||||||
# Test streaming with "enable_thinking": True, reasoning_content should not be empty
|
# Test streaming with "enable_thinking": True, reasoning_content should not be empty
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.base_url}/v1/chat/completions",
|
f"{self.base_url}/v1/chat/completions",
|
||||||
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
||||||
json={
|
json={
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"messages": [{"role": "user", "content": "Hello"}],
|
"messages": [{"role": "user", "content": "Hello"}],
|
||||||
@@ -151,7 +149,6 @@ class TestEnableThinking(CustomTestCase):
|
|||||||
# Test streaming with "enable_thinking": False, reasoning_content should be empty
|
# Test streaming with "enable_thinking": False, reasoning_content should be empty
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.base_url}/v1/chat/completions",
|
f"{self.base_url}/v1/chat/completions",
|
||||||
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
||||||
json={
|
json={
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"messages": [{"role": "user", "content": "Hello"}],
|
"messages": [{"role": "user", "content": "Hello"}],
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
+1
-6
@@ -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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import CustomTestCase
|
from sglang.test.test_utils import CustomTestCase
|
||||||
|
|
||||||
register_npu_ci(
|
register_npu_ci(est_time=400, suite="nightly-16-npu-a3", nightly=True)
|
||||||
est_time=400,
|
|
||||||
suite="nightly-16-npu-a3",
|
|
||||||
nightly=True,
|
|
||||||
disabled="run failed",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeepSeekV32(GSM8KAscendMixin, CustomTestCase):
|
class TestDeepSeekV32(GSM8KAscendMixin, CustomTestCase):
|
||||||
+3
-2
@@ -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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import CustomTestCase
|
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):
|
class TestEXAONE(GSM8KAscendMixin, CustomTestCase):
|
||||||
@@ -16,7 +16,8 @@ class TestEXAONE(GSM8KAscendMixin, CustomTestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
model = EXAONE_3_5_7_8B_INSTRUCT_WEIGHTS_PATH
|
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 = [
|
other_args = [
|
||||||
"--trust-remote-code",
|
"--trust-remote-code",
|
||||||
"--mem-fraction-static",
|
"--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()
|
||||||
+2
-3
@@ -9,19 +9,18 @@ register_npu_ci(
|
|||||||
est_time=400,
|
est_time=400,
|
||||||
suite="nightly-1-npu-a3",
|
suite="nightly-1-npu-a3",
|
||||||
nightly=True,
|
nightly=True,
|
||||||
disabled="run failed",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestGLM49BChat(GSM8KAscendMixin, CustomTestCase):
|
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 Category] Model
|
||||||
[Test Target] ZhipuAI/glm-4-9b-chat
|
[Test Target] ZhipuAI/glm-4-9b-chat
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = GLM_4_9B_CHAT_WEIGHTS_PATH
|
model = GLM_4_9B_CHAT_WEIGHTS_PATH
|
||||||
accuracy = 0.79
|
accuracy = 0.77
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
+6
-1
@@ -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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import CustomTestCase
|
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):
|
class TestGrok2(GSM8KAscendMixin, CustomTestCase):
|
||||||
+7
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin
|
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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import CustomTestCase
|
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):
|
class TestInternlm2(GSM8KAscendMixin, CustomTestCase):
|
||||||
@@ -15,6 +20,7 @@ class TestInternlm2(GSM8KAscendMixin, CustomTestCase):
|
|||||||
[Test Target] Shanghai_AI_Laboratory/internlm2-7b
|
[Test Target] Shanghai_AI_Laboratory/internlm2-7b
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
||||||
model = INTERNLM2_7B_WEIGHTS_PATH
|
model = INTERNLM2_7B_WEIGHTS_PATH
|
||||||
accuracy = 0.585
|
accuracy = 0.585
|
||||||
|
|
||||||
+17
-4
@@ -1,16 +1,28 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin
|
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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import CustomTestCase
|
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):
|
class TestLlama4(GSM8KAscendMixin, CustomTestCase):
|
||||||
model = (
|
"""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.
|
||||||
"/root/.cache/modelscope/hub/models/meta-llama/Llama-4-Scout-17B-16E-Instruct"
|
|
||||||
)
|
[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
|
accuracy = 0.9
|
||||||
other_args = [
|
other_args = [
|
||||||
"--chat-template",
|
"--chat-template",
|
||||||
@@ -24,6 +36,7 @@ class TestLlama4(GSM8KAscendMixin, CustomTestCase):
|
|||||||
"--attention-backend",
|
"--attention-backend",
|
||||||
"ascend",
|
"ascend",
|
||||||
"--disable-cuda-graph",
|
"--disable-cuda-graph",
|
||||||
|
"--disable-radix-cache",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
+1
-1
@@ -9,7 +9,7 @@ register_npu_ci(
|
|||||||
est_time=400,
|
est_time=400,
|
||||||
suite="nightly-1-npu-a3",
|
suite="nightly-1-npu-a3",
|
||||||
nightly=True,
|
nightly=True,
|
||||||
disabled="run failed",
|
disabled="https://github.com/Ascend/sglang/issues/23",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
+7
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin
|
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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import CustomTestCase
|
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):
|
class TestPersimmon8BChat(GSM8KAscendMixin, CustomTestCase):
|
||||||
@@ -15,6 +20,7 @@ class TestPersimmon8BChat(GSM8KAscendMixin, CustomTestCase):
|
|||||||
[Test Target] Howeee/persimmon-8b-chat
|
[Test Target] Howeee/persimmon-8b-chat
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
||||||
model = PERSIMMON_8B_CHAT_WEIGHTS_PATH
|
model = PERSIMMON_8B_CHAT_WEIGHTS_PATH
|
||||||
accuracy = 0.17
|
accuracy = 0.17
|
||||||
|
|
||||||
+1
@@ -17,6 +17,7 @@ class TestQwen3235BA22BW8A8(GSM8KAscendMixin, CustomTestCase):
|
|||||||
|
|
||||||
model = QWEN3_235B_A22B_W8A8_WEIGHTS_PATH
|
model = QWEN3_235B_A22B_W8A8_WEIGHTS_PATH
|
||||||
accuracy = 0.955
|
accuracy = 0.955
|
||||||
|
timeout_for_server_launch = 3000
|
||||||
other_args = [
|
other_args = [
|
||||||
"--trust-remote-code",
|
"--trust-remote-code",
|
||||||
"--mem-fraction-static",
|
"--mem-fraction-static",
|
||||||
+2
-3
@@ -9,19 +9,18 @@ register_npu_ci(
|
|||||||
est_time=400,
|
est_time=400,
|
||||||
suite="nightly-4-npu-a3",
|
suite="nightly-4-npu-a3",
|
||||||
nightly=True,
|
nightly=True,
|
||||||
disabled="run failed",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestQwen332B(GSM8KAscendMixin, CustomTestCase):
|
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 Category] Model
|
||||||
[Test Target] Qwen/Qwen3-32B
|
[Test Target] Qwen/Qwen3-32B
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = QWEN3_32B_WEIGHTS_PATH
|
model = QWEN3_32B_WEIGHTS_PATH
|
||||||
accuracy = 0.88
|
accuracy = 0.86
|
||||||
other_args = [
|
other_args = [
|
||||||
"--trust-remote-code",
|
"--trust-remote-code",
|
||||||
"--mem-fraction-static",
|
"--mem-fraction-static",
|
||||||
-1
@@ -11,7 +11,6 @@ register_npu_ci(
|
|||||||
est_time=400,
|
est_time=400,
|
||||||
suite="nightly-16-npu-a3",
|
suite="nightly-16-npu-a3",
|
||||||
nightly=True,
|
nightly=True,
|
||||||
disabled="run failed",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
+9
-3
@@ -3,6 +3,7 @@ import unittest
|
|||||||
|
|
||||||
import torch
|
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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.runners import TEST_RERANK_QUERY_DOCS, HFRunner, SRTRunner
|
from sglang.test.runners import TEST_RERANK_QUERY_DOCS, HFRunner, SRTRunner
|
||||||
from sglang.test.test_utils import CustomTestCase
|
from sglang.test.test_utils import CustomTestCase
|
||||||
@@ -11,17 +12,22 @@ register_npu_ci(
|
|||||||
est_time=400,
|
est_time=400,
|
||||||
suite="nightly-1-npu-a3",
|
suite="nightly-1-npu-a3",
|
||||||
nightly=True,
|
nightly=True,
|
||||||
disabled="cross encoder scores are not all close",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
MODELS = [
|
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"]
|
ATTENTION_BACKEND = ["ascend"]
|
||||||
TORCH_DTYPES = [torch.bfloat16]
|
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
|
@classmethod
|
||||||
def setUpClass(cls):
|
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()
|
||||||
+6
-1
@@ -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.ascend.vlm_utils import TestVLMModels
|
||||||
from sglang.test.ci.ci_register import register_npu_ci
|
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):
|
class TestDeepseekVl2(TestVLMModels):
|
||||||
+6
-1
@@ -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.ci.ci_register import register_npu_ci
|
||||||
from sglang.test.test_utils import CustomTestCase
|
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):
|
class TestKimiVLA3BInstruct(GSM8KAscendMixin, CustomTestCase):
|
||||||
@@ -38,7 +38,12 @@ from sglang.utils import terminate_process
|
|||||||
|
|
||||||
mp.set_start_method("spawn", force=True)
|
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):
|
def verify_params_close(params1, params2, error_msg):
|
||||||
|
|||||||
Reference in New Issue
Block a user