[NPU] Add PR test cases (#32392)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: sglang-npu-bot <sglangnpu@163.com>
This commit is contained in:
co-authored by
Claude
sglang-npu-bot
parent
27b15349e5
commit
f8e62a9224
@@ -236,10 +236,10 @@ jobs:
|
|||||||
cd test
|
cd test
|
||||||
python3 run_suite.py --hw npu --suite stage-b-test-2-npu-a2 --auto-partition-id ${{ matrix.part }} --auto-partition-size 2
|
python3 run_suite.py --hw npu --suite stage-b-test-2-npu-a2 --auto-partition-id ${{ matrix.part }} --auto-partition-size 2
|
||||||
|
|
||||||
stage-b-test-4-npu-a3:
|
stage-b-test-1-npu-a3:
|
||||||
needs: [check-changes, pr-gate, set-image-config]
|
needs: [check-changes, pr-gate, set-image-config]
|
||||||
if: needs.check-changes.outputs.main_package == 'true'
|
if: needs.check-changes.outputs.main_package == 'true'
|
||||||
runs-on: linux-aarch64-a3-4
|
runs-on: linux-aarch64-a3-2
|
||||||
container:
|
container:
|
||||||
image: ${{ needs.set-image-config.outputs.CANN_image_a3 }}
|
image: ${{ needs.set-image-config.outputs.CANN_image_a3 }}
|
||||||
steps:
|
steps:
|
||||||
@@ -283,13 +283,13 @@ jobs:
|
|||||||
STREAMS_PER_DEVICE: 32
|
STREAMS_PER_DEVICE: 32
|
||||||
run: |
|
run: |
|
||||||
cd test
|
cd test
|
||||||
python3 run_suite.py --hw npu --suite stage-b-test-4-npu-a3 --timeout-per-file 3600
|
python3 run_suite.py --hw npu --suite stage-b-test-1-npu-a3 --timeout-per-file 3600
|
||||||
|
|
||||||
|
|
||||||
stage-b-test-16-npu-a3:
|
stage-b-test-2-npu-a3:
|
||||||
needs: [check-changes, pr-gate, set-image-config]
|
needs: [check-changes, pr-gate, set-image-config]
|
||||||
if: needs.check-changes.outputs.main_package == 'true'
|
if: needs.check-changes.outputs.main_package == 'true'
|
||||||
runs-on: linux-aarch64-a3-16
|
runs-on: linux-aarch64-a3-2
|
||||||
container:
|
container:
|
||||||
image: ${{ needs.set-image-config.outputs.CANN_image_a3 }}
|
image: ${{ needs.set-image-config.outputs.CANN_image_a3 }}
|
||||||
steps:
|
steps:
|
||||||
@@ -332,6 +332,161 @@ 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: |
|
||||||
|
cd test
|
||||||
|
python3 run_suite.py --hw npu --suite stage-b-test-2-npu-a3 --timeout-per-file 3600
|
||||||
|
|
||||||
|
|
||||||
|
stage-b-test-4-npu-a3:
|
||||||
|
needs: [check-changes, pr-gate, set-image-config]
|
||||||
|
if: needs.check-changes.outputs.main_package == 'true'
|
||||||
|
runs-on: linux-aarch64-a3-4
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
part: [ 0, 1 ]
|
||||||
|
container:
|
||||||
|
image: ${{ needs.set-image-config.outputs.CANN_image_a3 }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref || github.ref }}
|
||||||
|
|
||||||
|
- name: Mark repository safe
|
||||||
|
run: |
|
||||||
|
git config --system --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
TORCH_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/whl/cpu"
|
||||||
|
PYPI_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple"
|
||||||
|
UV_INDEX_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple"
|
||||||
|
GITHUB_PROXY_URL: "https://gh-proxy.test.osinfra.cn/"
|
||||||
|
RUSTUP_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local:8082"
|
||||||
|
run: |
|
||||||
|
# speed up by using infra cache services
|
||||||
|
CACHING_URL="cache-service.nginx-pypi-cache.svc.cluster.local"
|
||||||
|
sed -Ei "s@(ports|archive).ubuntu.com@${CACHING_URL}:8081@g" /etc/apt/sources.list
|
||||||
|
pip config set global.index-url http://${CACHING_URL}/pypi/simple
|
||||||
|
pip config set global.trusted-host "${CACHING_URL}"
|
||||||
|
|
||||||
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
|
# copy required file from our daily cache
|
||||||
|
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
||||||
|
# copy gsm8k dataset
|
||||||
|
cp ~/.cache/modelscope/hub/datasets/tmp/test.jsonl /tmp
|
||||||
|
|
||||||
|
- name: Run test
|
||||||
|
timeout-minutes: 120
|
||||||
|
env:
|
||||||
|
SGLANG_USE_MODELSCOPE: true
|
||||||
|
SGLANG_IS_IN_CI: true
|
||||||
|
HF_ENDPOINT: https://hf-mirror.com
|
||||||
|
TORCH_EXTENSIONS_DIR: /tmp/torch_extensions
|
||||||
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
|
STREAMS_PER_DEVICE: 32
|
||||||
|
run: |
|
||||||
|
pip install sglang_router
|
||||||
|
cd test
|
||||||
|
python3 run_suite.py --hw npu --suite stage-b-test-4-npu-a3 --auto-partition-id ${{ matrix.part }} --auto-partition-size 2 --timeout-per-file 3600
|
||||||
|
|
||||||
|
stage-b-test-8-npu-a3:
|
||||||
|
needs: [check-changes, pr-gate, set-image-config]
|
||||||
|
if: needs.check-changes.outputs.main_package == 'true'
|
||||||
|
runs-on: linux-aarch64-a3-8
|
||||||
|
container:
|
||||||
|
image: ${{ needs.set-image-config.outputs.CANN_image_a3 }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref || github.ref }}
|
||||||
|
|
||||||
|
- name: Mark repository safe
|
||||||
|
run: |
|
||||||
|
git config --system --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
TORCH_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/whl/cpu"
|
||||||
|
PYPI_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple"
|
||||||
|
UV_INDEX_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple"
|
||||||
|
GITHUB_PROXY_URL: "https://gh-proxy.test.osinfra.cn/"
|
||||||
|
RUSTUP_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local:8082"
|
||||||
|
run: |
|
||||||
|
# speed up by using infra cache services
|
||||||
|
CACHING_URL="cache-service.nginx-pypi-cache.svc.cluster.local"
|
||||||
|
sed -Ei "s@(ports|archive).ubuntu.com@${CACHING_URL}:8081@g" /etc/apt/sources.list
|
||||||
|
pip config set global.index-url http://${CACHING_URL}/pypi/simple
|
||||||
|
pip config set global.trusted-host "${CACHING_URL}"
|
||||||
|
|
||||||
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
|
# copy required file from our daily cache
|
||||||
|
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
||||||
|
# copy gsm8k dataset
|
||||||
|
cp ~/.cache/modelscope/hub/datasets/tmp/test.jsonl /tmp
|
||||||
|
|
||||||
|
- name: Run test
|
||||||
|
timeout-minutes: 60
|
||||||
|
env:
|
||||||
|
SGLANG_USE_MODELSCOPE: true
|
||||||
|
SGLANG_IS_IN_CI: true
|
||||||
|
HF_ENDPOINT: https://hf-mirror.com
|
||||||
|
TORCH_EXTENSIONS_DIR: /tmp/torch_extensions
|
||||||
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
|
STREAMS_PER_DEVICE: 32
|
||||||
|
run: |
|
||||||
|
cd test
|
||||||
|
python3 run_suite.py --hw npu --suite stage-b-test-8-npu-a3 --timeout-per-file 3600
|
||||||
|
|
||||||
|
stage-b-test-16-npu-a3:
|
||||||
|
needs: [check-changes, pr-gate, set-image-config]
|
||||||
|
if: needs.check-changes.outputs.main_package == 'true'
|
||||||
|
runs-on: linux-aarch64-a3-16
|
||||||
|
container:
|
||||||
|
image: ${{ needs.set-image-config.outputs.CANN_image_a3 }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref || github.ref }}
|
||||||
|
|
||||||
|
- name: Mark repository safe
|
||||||
|
run: |
|
||||||
|
git config --system --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
TORCH_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/whl/cpu"
|
||||||
|
PYPI_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple"
|
||||||
|
UV_INDEX_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple"
|
||||||
|
GITHUB_PROXY_URL: "https://gh-proxy.test.osinfra.cn/"
|
||||||
|
RUSTUP_CACHE_URL: "http://cache-service.nginx-pypi-cache.svc.cluster.local:8082"
|
||||||
|
run: |
|
||||||
|
# speed up by using infra cache services
|
||||||
|
CACHING_URL="cache-service.nginx-pypi-cache.svc.cluster.local"
|
||||||
|
sed -Ei "s@(ports|archive).ubuntu.com@${CACHING_URL}:8081@g" /etc/apt/sources.list
|
||||||
|
pip config set global.index-url http://${CACHING_URL}/pypi/simple
|
||||||
|
pip config set global.trusted-host "${CACHING_URL}"
|
||||||
|
|
||||||
|
bash scripts/ci/npu/npu_ci_install_dependency.sh a3
|
||||||
|
# copy required file from our daily cache
|
||||||
|
cp ~/.cache/modelscope/hub/datasets/otavia/ShareGPT_Vicuna_unfiltered/ShareGPT_V3_unfiltered_cleaned_split.json /tmp
|
||||||
|
# copy gsm8k dataset
|
||||||
|
cp ~/.cache/modelscope/hub/datasets/tmp/test.jsonl /tmp
|
||||||
|
|
||||||
|
- name: Run test
|
||||||
|
timeout-minutes: 120
|
||||||
|
env:
|
||||||
|
SGLANG_USE_MODELSCOPE: true
|
||||||
|
SGLANG_IS_IN_CI: true
|
||||||
|
HF_ENDPOINT: https://hf-mirror.com
|
||||||
|
TORCH_EXTENSIONS_DIR: /tmp/torch_extensions
|
||||||
|
PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True"
|
||||||
|
STREAMS_PER_DEVICE: 32
|
||||||
|
run: |
|
||||||
|
apt-get install -y libssl-dev
|
||||||
|
pip install sglang_router
|
||||||
cd test
|
cd test
|
||||||
python3 run_suite.py --hw npu --suite stage-b-test-16-npu-a3 --timeout-per-file 3600
|
python3 run_suite.py --hw npu --suite stage-b-test-16-npu-a3 --timeout-per-file 3600
|
||||||
|
|
||||||
@@ -537,27 +692,27 @@ jobs:
|
|||||||
# NPU accuracy tests
|
# NPU accuracy tests
|
||||||
- name: glm4_7_flash_1p_gsm8k
|
- name: glm4_7_flash_1p_gsm8k
|
||||||
runner: linux-aarch64-a3-2
|
runner: linux-aarch64-a3-2
|
||||||
test_case: test/registered/ascend/accuracy/glm4_7_flash/test_npu_glm4_7_flash_1p_gsm8k.py
|
test_case: test/registered/npu/accuracy/glm4_7_flash/test_npu_glm4_7_flash_1p_gsm8k.py
|
||||||
test_type: 'accuracy'
|
test_type: 'accuracy'
|
||||||
- name: qwen3_vl_30b_a3b_bf16_2p_gsm8k
|
- name: qwen3_vl_30b_a3b_bf16_2p_gsm8k
|
||||||
runner: linux-aarch64-a3-4
|
runner: linux-aarch64-a3-4
|
||||||
test_case: test/registered/ascend/accuracy/qwen3_vl_30b_a3b/test_npu_qwen3_vl_30b_a3b_bf16_2p_gsm8k.py
|
test_case: test/registered/npu/accuracy/qwen3_vl_30b_a3b/test_npu_qwen3_vl_30b_a3b_bf16_2p_gsm8k.py
|
||||||
test_type: 'accuracy'
|
test_type: 'accuracy'
|
||||||
- name: glm5_top64_pruned_bf16_8p_gsm8k
|
- name: glm5_top64_pruned_bf16_8p_gsm8k
|
||||||
runner: linux-aarch64-a3-16
|
runner: linux-aarch64-a3-16
|
||||||
test_case: test/registered/ascend/accuracy/glm5_top64_pruned/test_npu_glm5_top64_pruned_bf16_8p_gsm8k.py
|
test_case: test/registered/npu/accuracy/glm5_top64_pruned/test_npu_glm5_top64_pruned_bf16_8p_gsm8k.py
|
||||||
test_type: 'accuracy'
|
test_type: 'accuracy'
|
||||||
- name: moonshotai_moonlight_16b_a3b_bf16_1p_gsm8k
|
- name: moonshotai_moonlight_16b_a3b_bf16_1p_gsm8k
|
||||||
runner: linux-aarch64-a3-2
|
runner: linux-aarch64-a3-2
|
||||||
test_case: test/registered/ascend/accuracy/moonshotai_moonlight_16b_a3b/test_npu_moonlight_16b_a3b_bf16_1p_gsm8k.py
|
test_case: test/registered/npu/accuracy/moonshotai_moonlight_16b_a3b/test_npu_moonlight_16b_a3b_bf16_1p_gsm8k.py
|
||||||
test_type: 'accuracy'
|
test_type: 'accuracy'
|
||||||
- name: qwen3_5_9b_bf16_1p_gsm8k
|
- name: qwen3_5_9b_bf16_1p_gsm8k
|
||||||
runner: linux-aarch64-a3-2
|
runner: linux-aarch64-a3-2
|
||||||
test_case: test/registered/ascend/accuracy/qwen3_5_9b/test_npu_qwen3_5_9b_bf16_1p_gsm8k.py
|
test_case: test/registered/npu/accuracy/qwen3_5_9b/test_npu_qwen3_5_9b_bf16_1p_gsm8k.py
|
||||||
test_type: 'accuracy'
|
test_type: 'accuracy'
|
||||||
- name: qwen3_vl_8b_bf16_2p_gsm8k
|
- name: qwen3_vl_8b_bf16_2p_gsm8k
|
||||||
runner: linux-aarch64-a3-4
|
runner: linux-aarch64-a3-4
|
||||||
test_case: test/registered/ascend/accuracy/qwen3_vl_8b/test_npu_qwen3_vl_8b_bf16_2p_gsm8k.py
|
test_case: test/registered/npu/accuracy/qwen3_vl_8b/test_npu_qwen3_vl_8b_bf16_2p_gsm8k.py
|
||||||
test_type: 'accuracy'
|
test_type: 'accuracy'
|
||||||
uses: ./.github/workflows/nightly-test-npu-e2e-single-node.yml
|
uses: ./.github/workflows/nightly-test-npu-e2e-single-node.yml
|
||||||
with:
|
with:
|
||||||
@@ -578,8 +733,11 @@ jobs:
|
|||||||
|
|
||||||
stage-a-unit-test-npu,
|
stage-a-unit-test-npu,
|
||||||
stage-b-test-1-npu-a2,
|
stage-b-test-1-npu-a2,
|
||||||
|
stage-b-test-1-npu-a3,
|
||||||
stage-b-test-2-npu-a2,
|
stage-b-test-2-npu-a2,
|
||||||
|
stage-b-test-2-npu-a3,
|
||||||
stage-b-test-4-npu-a3,
|
stage-b-test-4-npu-a3,
|
||||||
|
stage-b-test-8-npu-a3,
|
||||||
stage-b-test-16-npu-a3,
|
stage-b-test-16-npu-a3,
|
||||||
|
|
||||||
multimodal-gen-test-1-npu-a3,
|
multimodal-gen-test-1-npu-a3,
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""NPU accuracy test mixin.
|
||||||
|
|
||||||
|
Wraps :class:`~sglang.test.kits.eval_accuracy_kit.GSM8KMixin` with:
|
||||||
|
|
||||||
|
* 1% accuracy tolerance (threshold * 0.99)
|
||||||
|
* Automatic retry (up to 3 attempts)
|
||||||
|
|
||||||
|
Delegates to ``super().test_gsm8k()`` so community-side changes to eval
|
||||||
|
setup, backend selection, or parameters are inherited automatically.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
from sglang.test.ascend.npu_eval_accuracy_kit import NPUGSM8KMixin
|
||||||
|
|
||||||
|
class TestFoo(CustomTestCase, NPUGSM8KMixin):
|
||||||
|
gsm8k_accuracy_thres = 0.6
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sglang.test.kits.eval_accuracy_kit import GSM8KMixin
|
||||||
|
|
||||||
|
_NPU_ACCURACY_TOLERANCE = 0.99
|
||||||
|
_NPU_MAX_ACCURACY_ATTEMPTS = 3
|
||||||
|
|
||||||
|
|
||||||
|
class NPUGSM8KMixin(GSM8KMixin):
|
||||||
|
"""NPU GSM8K accuracy mixin — 1% tolerance + up to 3 retries."""
|
||||||
|
|
||||||
|
def test_gsm8k(self):
|
||||||
|
threshold = self.gsm8k_score_threshold
|
||||||
|
if threshold != threshold: # NaN → legacy alias
|
||||||
|
threshold = self.gsm8k_accuracy_thres
|
||||||
|
relaxed = threshold * _NPU_ACCURACY_TOLERANCE
|
||||||
|
|
||||||
|
# Patch both so the parent picks up the relaxed threshold.
|
||||||
|
self.gsm8k_score_threshold = relaxed
|
||||||
|
self.gsm8k_accuracy_thres = relaxed
|
||||||
|
|
||||||
|
for attempt in range(_NPU_MAX_ACCURACY_ATTEMPTS):
|
||||||
|
try:
|
||||||
|
super().test_gsm8k()
|
||||||
|
return
|
||||||
|
except AssertionError:
|
||||||
|
if attempt == _NPU_MAX_ACCURACY_ATTEMPTS - 1:
|
||||||
|
raise
|
||||||
|
print(
|
||||||
|
f"[{type(self).__name__}] GSM8K attempt "
|
||||||
|
f"{attempt + 1}/{_NPU_MAX_ACCURACY_ATTEMPTS} failed, "
|
||||||
|
f"retrying..."
|
||||||
|
)
|
||||||
@@ -212,7 +212,7 @@ QWEN3_NEXT_80B_A3B_INSTRUCT_WEIGHTS_PATH = os.path.join(
|
|||||||
MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Next-80B-A3B-Instruct"
|
MODEL_WEIGHTS_DIR, "Qwen/Qwen3-Next-80B-A3B-Instruct"
|
||||||
)
|
)
|
||||||
QWEN3_32B_EAGLE3_WEIGHTS_PATH = os.path.join(
|
QWEN3_32B_EAGLE3_WEIGHTS_PATH = os.path.join(
|
||||||
MODEL_WEIGHTS_DIR, "Zjcxy-SmartAI/Qwen3-32B-Eagle3"
|
MODEL_WEIGHTS_DIR, "Zjcxy-SmartAI/Eagle3-Qwen3-32B-zh"
|
||||||
)
|
)
|
||||||
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH = os.path.join(
|
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH = os.path.join(
|
||||||
MODEL_WEIGHTS_DIR, "aleoyang/Qwen3-32B-w8a8-MindIE"
|
MODEL_WEIGHTS_DIR, "aleoyang/Qwen3-32B-w8a8-MindIE"
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.lang.chat_template import get_chat_template_by_model_path
|
||||||
|
from sglang.srt.environ import envs
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from sglang.test.ascend.npu_eval_accuracy_kit import NPUGSM8KMixin
|
||||||
|
from sglang.test.ascend.test_ascend_utils import (
|
||||||
|
DEEPSEEK_V2_LITE_W8A8_WEIGHTS_PATH,
|
||||||
|
KIMI_VL_A3B_INSTRUCT_WEIGHTS_PATH,
|
||||||
|
QWEN3_32B_WEIGHTS_PATH,
|
||||||
|
)
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
from sglang.test.kits.ebnf_constrained_kit import EBNFConstrainedMixin
|
||||||
|
from sglang.test.kits.json_constrained_kit import JSONConstrainedMixin
|
||||||
|
from sglang.test.kits.radix_cache_server_kit import run_radix_attention_test
|
||||||
|
from sglang.test.kits.regex_constrained_kit import RegexConstrainedMixin
|
||||||
|
from sglang.test.test_utils import (
|
||||||
|
DEFAULT_IMAGE_URL,
|
||||||
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
CustomTestCase,
|
||||||
|
popen_launch_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
register_npu_ci(est_time=400, suite="stage-b-test-4-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-4-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDPAttentionDP2TP2(
|
||||||
|
CustomTestCase,
|
||||||
|
NPUGSM8KMixin,
|
||||||
|
JSONConstrainedMixin,
|
||||||
|
EBNFConstrainedMixin,
|
||||||
|
RegexConstrainedMixin,
|
||||||
|
):
|
||||||
|
gsm8k_accuracy_thres = 0.6
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.model = QWEN3_32B_WEIGHTS_PATH
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
cls._env_override = envs.SGLANG_DISABLE_CONSECUTIVE_PREFILL_OVERLAP.override(
|
||||||
|
True
|
||||||
|
)
|
||||||
|
cls._env_override.__enter__()
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
cls.model,
|
||||||
|
cls.base_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=[
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--tp",
|
||||||
|
"4",
|
||||||
|
"--enable-dp-attention",
|
||||||
|
"--dp",
|
||||||
|
"2",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--enable-torch-compile",
|
||||||
|
"--torch-compile-max-bs",
|
||||||
|
"2",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
cls._env_override.__exit__(None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDPAttentionMixedChunk(
|
||||||
|
CustomTestCase,
|
||||||
|
NPUGSM8KMixin,
|
||||||
|
):
|
||||||
|
gsm8k_accuracy_thres = 0.35
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.model = DEEPSEEK_V2_LITE_W8A8_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",
|
||||||
|
"--tp",
|
||||||
|
"2",
|
||||||
|
"--enable-dp-attention",
|
||||||
|
"--dp",
|
||||||
|
"2",
|
||||||
|
"--enable-mixed-chunk",
|
||||||
|
"--chunked-prefill-size",
|
||||||
|
"256",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDPRetract(
|
||||||
|
CustomTestCase,
|
||||||
|
JSONConstrainedMixin,
|
||||||
|
EBNFConstrainedMixin,
|
||||||
|
RegexConstrainedMixin,
|
||||||
|
):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.model = DEEPSEEK_V2_LITE_W8A8_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",
|
||||||
|
"--tp",
|
||||||
|
"2",
|
||||||
|
"--enable-dp-attention",
|
||||||
|
"--dp",
|
||||||
|
"2",
|
||||||
|
"--max-total-tokens",
|
||||||
|
"4500",
|
||||||
|
"--max-running-requests",
|
||||||
|
"128",
|
||||||
|
"--chunked-prefill-size",
|
||||||
|
"256",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
def test_radix_attention(self):
|
||||||
|
with envs.SGLANG_TEST_RETRACT.override(True):
|
||||||
|
run_radix_attention_test(self.base_url)
|
||||||
|
self.assertIsNone(self.process.poll())
|
||||||
|
|
||||||
|
|
||||||
|
class TestDPAttentionDP2TP2VLM(CustomTestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.model = KIMI_VL_A3B_INSTRUCT_WEIGHTS_PATH
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
cls.image_url = DEFAULT_IMAGE_URL
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
cls.model,
|
||||||
|
cls.base_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=[
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--tp",
|
||||||
|
"2",
|
||||||
|
"--enable-dp-attention",
|
||||||
|
"--dp",
|
||||||
|
"2",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
def test_vlm_generate(self):
|
||||||
|
chat_template = get_chat_template_by_model_path(self.model)
|
||||||
|
prompt = f"{chat_template.image_token}What is in this image?"
|
||||||
|
response = requests.post(
|
||||||
|
self.base_url + "/generate",
|
||||||
|
json={
|
||||||
|
"text": prompt,
|
||||||
|
"image_data": [self.image_url],
|
||||||
|
"sampling_params": {
|
||||||
|
"temperature": 0,
|
||||||
|
"max_new_tokens": 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
response_json = response.json()
|
||||||
|
print(response_json)
|
||||||
|
self.assertIn("output_ids", response_json)
|
||||||
|
self.assertGreater(len(response_json["output_ids"]), 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
import random
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from sglang.test.ascend.test_ascend_utils import DEEPSEEK_R1_0528_W8A8_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_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
CustomTestCase,
|
||||||
|
is_in_ci,
|
||||||
|
popen_launch_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
register_npu_ci(est_time=700, suite="stage-b-test-16-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=700, suite="nightly-16-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDPAttentionRoundBinLoadBalance(CustomTestCase):
|
||||||
|
"""Testcase:Verify that the model accuracy did not decrease when --load-balance-method is set to round_robin, auto,
|
||||||
|
total_requests or total_tokens in PD mixed scenario
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --load-balance-method
|
||||||
|
"""
|
||||||
|
|
||||||
|
mode = "round_robin"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.model_path = DEEPSEEK_R1_0528_W8A8_WEIGHTS_PATH
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
cls.url = urlparse(cls.base_url)
|
||||||
|
other_args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--tp",
|
||||||
|
"16",
|
||||||
|
"--enable-dp-attention",
|
||||||
|
"--dp",
|
||||||
|
"2",
|
||||||
|
"--enable-torch-compile",
|
||||||
|
"--torch-compile-max-bs",
|
||||||
|
"2",
|
||||||
|
"--load-balance-method",
|
||||||
|
cls.mode,
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.75",
|
||||||
|
]
|
||||||
|
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
cls.model_path,
|
||||||
|
cls.base_url,
|
||||||
|
timeout=3 * DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=other_args,
|
||||||
|
env={
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@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=f"http://{self.url.hostname}",
|
||||||
|
port=int(self.url.port),
|
||||||
|
)
|
||||||
|
|
||||||
|
metrics = run_eval_few_shot_gsm8k(args)
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
metrics["accuracy"],
|
||||||
|
0.95,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_server_info(self):
|
||||||
|
response = requests.get(f"{self.base_url}/get_server_info")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn(self.mode, response.text)
|
||||||
|
|
||||||
|
|
||||||
|
class _TestDPAttentionAutoLoadBalance(TestDPAttentionRoundBinLoadBalance):
|
||||||
|
mode = "auto"
|
||||||
|
|
||||||
|
|
||||||
|
class _TestDPAttentionTotalRequestsLoadBalance(TestDPAttentionRoundBinLoadBalance):
|
||||||
|
mode = "total_requests"
|
||||||
|
|
||||||
|
|
||||||
|
class _TestDPAttentionTotalTokensLoadBalance(TestDPAttentionRoundBinLoadBalance):
|
||||||
|
mode = "total_tokens"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# To reduce the CI execution time.
|
||||||
|
if is_in_ci():
|
||||||
|
loader = unittest.TestLoader()
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
RUN_FLAG = [
|
||||||
|
TestDPAttentionRoundBinLoadBalance,
|
||||||
|
_TestDPAttentionAutoLoadBalance,
|
||||||
|
_TestDPAttentionTotalRequestsLoadBalance,
|
||||||
|
_TestDPAttentionTotalTokensLoadBalance,
|
||||||
|
]
|
||||||
|
suite.addTests(loader.loadTestsFromTestCase(random.choice(RUN_FLAG)))
|
||||||
|
runner = unittest.TextTestRunner()
|
||||||
|
runner.run(suite)
|
||||||
|
else:
|
||||||
|
unittest.main()
|
||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
import itertools
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import unittest
|
||||||
|
from time import sleep
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from typing import List, Type
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.test.ascend.disaggregation_utils import TestDisaggregationBase
|
||||||
|
from sglang.test.ascend.test_ascend_utils import (
|
||||||
|
QWEN3_30B_A3B_INSTRUCT_2507_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_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
is_in_ci,
|
||||||
|
popen_launch_pd_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
register_npu_ci(est_time=3600, suite="stage-b-test-16-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=3600, suite="nightly-16-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
load_balance_method_options = [
|
||||||
|
"auto",
|
||||||
|
"round_robin",
|
||||||
|
"total_requests",
|
||||||
|
"total_tokens",
|
||||||
|
"follow_bootstrap_room",
|
||||||
|
]
|
||||||
|
all_params = list(itertools.product(load_balance_method_options, repeat=2))
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestNPULoadBalanceMethodDPDisaggregation(TestDisaggregationBase):
|
||||||
|
"""Testcase:Verify that the model accuracy did not decrease when --load-balance-method is set to round_robin, auto,
|
||||||
|
total_requests, total_tokens or follow_bootstrap_room in PD disaggregation scenario
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --load-balance-method
|
||||||
|
"""
|
||||||
|
|
||||||
|
params = ("auto", "auto")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.prefill_load_balance_method, cls.decode_load_balance_method = cls.params
|
||||||
|
cls.model = QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_PATH
|
||||||
|
os.environ["ASCEND_MF_STORE_URL"] = "tcp://127.0.0.1:24666"
|
||||||
|
|
||||||
|
# Non blocking start servers
|
||||||
|
cls.start_prefill()
|
||||||
|
cls.start_decode()
|
||||||
|
|
||||||
|
# Block until both
|
||||||
|
cls.wait_server_ready(cls.prefill_url + "/health")
|
||||||
|
cls.wait_server_ready(cls.decode_url + "/health")
|
||||||
|
|
||||||
|
cls.launch_lb()
|
||||||
|
cls.url = urlparse(cls.lb_url)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def start_prefill(cls):
|
||||||
|
prefill_args = [
|
||||||
|
"--disaggregation-mode",
|
||||||
|
"prefill",
|
||||||
|
"--tp-size",
|
||||||
|
"2",
|
||||||
|
"--enable-dp-attention",
|
||||||
|
"--dp",
|
||||||
|
"2",
|
||||||
|
"--load-balance-method",
|
||||||
|
cls.prefill_load_balance_method,
|
||||||
|
"--disaggregation-transfer-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
0.8,
|
||||||
|
"--dist-init-addr",
|
||||||
|
"127.0.0.1:10100",
|
||||||
|
"--base-gpu-id",
|
||||||
|
4,
|
||||||
|
]
|
||||||
|
|
||||||
|
cls.process_prefill = popen_launch_pd_server(
|
||||||
|
cls.model,
|
||||||
|
cls.prefill_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=prefill_args,
|
||||||
|
env={
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def start_decode(cls):
|
||||||
|
decode_args = [
|
||||||
|
"--disaggregation-mode",
|
||||||
|
"decode",
|
||||||
|
"--base-gpu-id",
|
||||||
|
2,
|
||||||
|
"--tp-size",
|
||||||
|
"2",
|
||||||
|
"--enable-dp-attention",
|
||||||
|
"--dp",
|
||||||
|
"2",
|
||||||
|
"--load-balance-method",
|
||||||
|
cls.decode_load_balance_method,
|
||||||
|
"--disaggregation-transfer-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
0.8,
|
||||||
|
"--dist-init-addr",
|
||||||
|
"127.0.0.1:10000",
|
||||||
|
]
|
||||||
|
|
||||||
|
cls.process_decode = popen_launch_pd_server(
|
||||||
|
cls.model,
|
||||||
|
cls.decode_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=decode_args,
|
||||||
|
env={
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_gsm8k(self):
|
||||||
|
args = SimpleNamespace(
|
||||||
|
num_shots=5,
|
||||||
|
data_path=None,
|
||||||
|
num_questions=200,
|
||||||
|
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"],
|
||||||
|
# 0.95 with 0.02 tolerable fluctuation
|
||||||
|
0.93,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_server_info(self):
|
||||||
|
response = requests.get(f"{self.lb_url}/get_server_info")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
data = json.loads(response.text)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
"follow_bootstrap_room"
|
||||||
|
if self.prefill_load_balance_method == "auto"
|
||||||
|
else self.prefill_load_balance_method
|
||||||
|
),
|
||||||
|
data.get("prefill")[0].get("load_balance_method"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
"round_robin"
|
||||||
|
if self.decode_load_balance_method == "auto"
|
||||||
|
else self.decode_load_balance_method
|
||||||
|
),
|
||||||
|
data.get("decode")[0].get("load_balance_method"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
os.environ.pop("ASCEND_MF_STORE_URL")
|
||||||
|
super().tearDownClass()
|
||||||
|
# wait for server release source
|
||||||
|
sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
TestClassType = Type[BaseTestNPULoadBalanceMethodDPDisaggregation]
|
||||||
|
all_test_classes: List[TestClassType] = [BaseTestNPULoadBalanceMethodDPDisaggregation]
|
||||||
|
for index, param_tuple in enumerate(all_params):
|
||||||
|
if param_tuple == BaseTestNPULoadBalanceMethodDPDisaggregation.params:
|
||||||
|
continue
|
||||||
|
|
||||||
|
prefill_load_balance_method, decode_load_balance_method = param_tuple
|
||||||
|
class_name = f"Test_{index:02d}_prefill_{prefill_load_balance_method}_decode_{decode_load_balance_method}"
|
||||||
|
new_class = type(
|
||||||
|
class_name,
|
||||||
|
(BaseTestNPULoadBalanceMethodDPDisaggregation,),
|
||||||
|
{"params": param_tuple},
|
||||||
|
)
|
||||||
|
|
||||||
|
all_test_classes.append(new_class)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if is_in_ci():
|
||||||
|
RUN_COUNT = 3
|
||||||
|
loader = unittest.TestLoader()
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
|
||||||
|
selected_classes = random.sample(
|
||||||
|
all_test_classes, min(RUN_COUNT, len(all_test_classes))
|
||||||
|
)
|
||||||
|
|
||||||
|
for cls in selected_classes:
|
||||||
|
suite.addTests(loader.loadTestsFromTestCase(cls))
|
||||||
|
|
||||||
|
runner = unittest.TextTestRunner(verbosity=2)
|
||||||
|
runner.run(suite)
|
||||||
|
else:
|
||||||
|
unittest.main()
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
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_30B_A3B_W8A8_WEIGHTS_PATH
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
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=400, suite="stage-b-test-8-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-8-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEplbMinRebalancingUtilizationThresholdBase(CustomTestCase):
|
||||||
|
"""
|
||||||
|
Testcase:Validates that rebalancing operations are triggered or skipped based on the configured
|
||||||
|
--eplb-min-rebalancing-utilization-threshold value and current load balance.
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --eplb-min-rebalancing-utilization-threshold, --eplb-rebalance-layers-per-chunk
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = QWEN3_30B_A3B_W8A8_WEIGHTS_PATH
|
||||||
|
accuracy = 0.86
|
||||||
|
common_args = [
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--chunked-prefill-size",
|
||||||
|
"1024",
|
||||||
|
"--tp-size",
|
||||||
|
"8",
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--moe-a2a-backend",
|
||||||
|
"deepep",
|
||||||
|
"--deepep-mode",
|
||||||
|
"normal",
|
||||||
|
"--enable-eplb",
|
||||||
|
"--ep-num-redundant-experts",
|
||||||
|
16,
|
||||||
|
"--eplb-rebalance-num-iterations",
|
||||||
|
50,
|
||||||
|
"--expert-distribution-recorder-buffer-size",
|
||||||
|
50,
|
||||||
|
"--enable-expert-distribution-metrics",
|
||||||
|
"--eplb-rebalance-layers-per-chunk",
|
||||||
|
"1",
|
||||||
|
]
|
||||||
|
|
||||||
|
out_file_path = "./rebalance_out_log.txt"
|
||||||
|
err_file_path = "./rebalance_err_log.txt"
|
||||||
|
log_info = "Skipped ep rebalancing: current GPU utilization"
|
||||||
|
test_args = ["--eplb-min-rebalancing-utilization-threshold", 0.05]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
if hasattr(cls, "out_file_path"):
|
||||||
|
cls.out_file = open(cls.out_file_path, "w+", encoding="utf-8")
|
||||||
|
if hasattr(cls, "err_file_path"):
|
||||||
|
cls.err_file = open(cls.err_file_path, "w+", encoding="utf-8")
|
||||||
|
|
||||||
|
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=cls.common_args + cls.test_args,
|
||||||
|
env={
|
||||||
|
"SGLANG_ENABLE_JIT_DEEPGEMM": "0",
|
||||||
|
"SGLANG_EXPERT_LOCATION_UPDATER_CANARY": "1",
|
||||||
|
"HCCL_BUFFSIZE": "1024",
|
||||||
|
"SGLANG_DEEPEP_BF16_DISPATCH": "1",
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
"DEEP_NORMAL_MODE_USE_INT8_QUANT": "1",
|
||||||
|
**os.environ,
|
||||||
|
},
|
||||||
|
return_stdout_stderr=(cls.out_file, cls.err_file),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
cls.out_file.close()
|
||||||
|
cls.err_file.close()
|
||||||
|
os.remove("./rebalance_out_log.txt")
|
||||||
|
os.remove("./rebalance_err_log.txt")
|
||||||
|
|
||||||
|
def test_gsm8k(self):
|
||||||
|
args = SimpleNamespace(
|
||||||
|
max_tokens=512,
|
||||||
|
base_url=DEFAULT_URL_FOR_TEST,
|
||||||
|
model=self.model,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
api="completion",
|
||||||
|
num_examples=200,
|
||||||
|
num_threads=128,
|
||||||
|
num_shots=5,
|
||||||
|
)
|
||||||
|
metrics = run_eval(args)
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
metrics["score"],
|
||||||
|
self.accuracy,
|
||||||
|
f'Accuracy of {self.model} is {str(metrics["score"])}, is lower than {self.accuracy}',
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Testcase:When the configuration --eplb-min-rebalancing-utilization-threshold is set to 0.05, if the load balance
|
||||||
|
exceeds this threshold, rebalancing operations are skipped.
|
||||||
|
"""
|
||||||
|
self.err_file.seek(0)
|
||||||
|
content = self.err_file.read()
|
||||||
|
self.assertIn(self.log_info, content)
|
||||||
|
self.assertIn("[EPLBManager] rebalance start", content)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEplbMinRebalancingUtilizationThreshold095(
|
||||||
|
TestEplbMinRebalancingUtilizationThresholdBase
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Testcase:When the configuration --eplb-min-rebalancing-utilization-threshold is set to 0.95, if load balancing
|
||||||
|
is less than or equal to this threshold, rebalancing operations are triggered.
|
||||||
|
"""
|
||||||
|
|
||||||
|
log_info = "rebalance end"
|
||||||
|
test_args = ["--eplb-min-rebalancing-utilization-threshold", 0.95]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
if hasattr(cls, "out_file_path"):
|
||||||
|
cls.out_file = open(cls.out_file_path, "w+", encoding="utf-8")
|
||||||
|
if hasattr(cls, "err_file_path"):
|
||||||
|
cls.err_file = open(cls.err_file_path, "w+", encoding="utf-8")
|
||||||
|
|
||||||
|
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=cls.common_args + cls.test_args,
|
||||||
|
env={
|
||||||
|
"SGLANG_ENABLE_JIT_DEEPGEMM": "0",
|
||||||
|
"SGLANG_EXPERT_LOCATION_UPDATER_CANARY": "1",
|
||||||
|
"HCCL_BUFFSIZE": "1024",
|
||||||
|
"SGLANG_NPU_DISABLE_ACL_FORMAT_WEIGHT": "1",
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
"DEEP_NORMAL_MODE_USE_INT8_QUANT": "1",
|
||||||
|
**os.environ,
|
||||||
|
},
|
||||||
|
return_stdout_stderr=(cls.out_file, cls.err_file),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main(verbosity=2)
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from sglang.test.ascend.test_ascend_utils import (
|
||||||
|
QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_PATH,
|
||||||
|
run_command,
|
||||||
|
)
|
||||||
|
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="stage-b-test-2-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-2-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpertDistributionRecorderModeStatic(CustomTestCase):
|
||||||
|
"""Testcase: Verify set the parameter --expert-distribution-recorder-mode,
|
||||||
|
will generate .pt file and the inference request successfully.
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --expert-distribution-recorder-mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
expert_distribution_recorder_mode = "stat"
|
||||||
|
|
||||||
|
path = "/tmp/pt"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
QWEN3_30B_A3B_INSTRUCT_2507_WEIGHTS_PATH,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=[
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.8",
|
||||||
|
"--tp-size",
|
||||||
|
"2",
|
||||||
|
"--expert-parallel-size",
|
||||||
|
"2",
|
||||||
|
"--enable-eplb",
|
||||||
|
"--moe-a2a-backend",
|
||||||
|
"deepep",
|
||||||
|
"--deepep-mode",
|
||||||
|
"normal",
|
||||||
|
"--expert-distribution-recorder-mode",
|
||||||
|
cls.expert_distribution_recorder_mode,
|
||||||
|
],
|
||||||
|
env={
|
||||||
|
"SGLANG_NPU_DISABLE_ACL_FORMAT_WEIGHT": "1",
|
||||||
|
"HCCL_BUFFSIZE": "1024",
|
||||||
|
"SGLANG_EXPERT_DISTRIBUTION_RECORDER_DIR": f"{cls.path}",
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
run_command(f"rm -rf {cls.path}")
|
||||||
|
|
||||||
|
def test_recorder_mode(self):
|
||||||
|
# Start recording
|
||||||
|
requests.post(f"{DEFAULT_URL_FOR_TEST}/start_expert_distribution_record")
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{DEFAULT_URL_FOR_TEST}/generate",
|
||||||
|
json={
|
||||||
|
"text": "The capital of France is",
|
||||||
|
"sampling_params": {
|
||||||
|
"temperature": 0,
|
||||||
|
"max_new_tokens": 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
response.status_code, 200, "The request status code is not 200."
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"Paris", response.text, "The inference result does not include Paris."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stop recording
|
||||||
|
requests.post(f"{DEFAULT_URL_FOR_TEST}/stop_expert_distribution_record")
|
||||||
|
|
||||||
|
# Export the .pt file
|
||||||
|
requests.post(f"{DEFAULT_URL_FOR_TEST}/dump_expert_distribution_record")
|
||||||
|
|
||||||
|
# Check distribution_recorder_files
|
||||||
|
distribution_recorder_suffixes = ["*.pt"]
|
||||||
|
distribution_recorder_files = []
|
||||||
|
for suffix in distribution_recorder_suffixes:
|
||||||
|
distribution_recorder_files.extend(
|
||||||
|
glob.glob(os.path.join(self.path, "**", suffix), recursive=True)
|
||||||
|
)
|
||||||
|
self.assertGreater(
|
||||||
|
len(distribution_recorder_files),
|
||||||
|
0,
|
||||||
|
msg=f"No distribution recorder",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpertDistributionRecorderModeStatApprox(
|
||||||
|
TestExpertDistributionRecorderModeStatic
|
||||||
|
):
|
||||||
|
expert_distribution_recorder_mode = "stat_approx"
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpertDistributionRecorderPerPass(TestExpertDistributionRecorderModeStatic):
|
||||||
|
expert_distribution_recorder_mode = "per_pass"
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpertDistributionRecorderPerToken(TestExpertDistributionRecorderModeStatic):
|
||||||
|
expert_distribution_recorder_mode = "per_token"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from sglang.test.ascend.test_ascend_utils import DEEPSEEK_CODER_V2_LITE_WEIGHTS_PATH
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
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=400, suite="stage-b-test-2-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-2-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAscendMoeDenseTPSize(CustomTestCase):
|
||||||
|
"""Testcase: Verify that the model accuracy remains uncompromised when the parameter --moe-dense-tp-size is configured to 1.
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --moe-dense-tp-size, --eplb-algorithm
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.model = DEEPSEEK_CODER_V2_LITE_WEIGHTS_PATH
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
cls.model,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=[
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.5",
|
||||||
|
"--tp-size",
|
||||||
|
"2",
|
||||||
|
"--expert-parallel-size",
|
||||||
|
"2",
|
||||||
|
"--enable-eplb",
|
||||||
|
"--moe-a2a-backend",
|
||||||
|
"deepep",
|
||||||
|
"--deepep-mode",
|
||||||
|
"normal",
|
||||||
|
"--eplb-algorithm",
|
||||||
|
"deepseek",
|
||||||
|
"--moe-dense-tp-size",
|
||||||
|
"1",
|
||||||
|
],
|
||||||
|
env={
|
||||||
|
"HCCL_BUFFSIZE": "1024",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
def test_gsm8k(self):
|
||||||
|
args = SimpleNamespace(
|
||||||
|
max_tokens=512,
|
||||||
|
base_url=DEFAULT_URL_FOR_TEST,
|
||||||
|
model=self.model,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
api="completion",
|
||||||
|
num_examples=200,
|
||||||
|
num_threads=128,
|
||||||
|
num_shots=5,
|
||||||
|
)
|
||||||
|
metrics = run_eval(args)
|
||||||
|
self.assertGreater(metrics["score"], 0.79)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from sglang.test.ascend.test_ascend_utils import DEEPSEEK_CODER_V2_LITE_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="stage-b-test-2-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-2-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMoreRunnerBackendTriton(CustomTestCase):
|
||||||
|
"""Testcase:Verify set --moe-runner-backend, the inference request is successfully processed.
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --ep, --enable-eplb, --moe-runner-backend, --deepep-mode, --ep-num-redundant-experts, --moe-a2a-backend,
|
||||||
|
--expert-distribution-recorder-buffer-size
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = DEEPSEEK_CODER_V2_LITE_WEIGHTS_PATH
|
||||||
|
moe_runner_backend = "triton"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
cls.model,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=[
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.5",
|
||||||
|
"--tp-size",
|
||||||
|
"2",
|
||||||
|
"--ep",
|
||||||
|
"2",
|
||||||
|
"--enable-eplb",
|
||||||
|
"--moe-a2a-backend",
|
||||||
|
"deepep",
|
||||||
|
"--deepep-mode",
|
||||||
|
"normal",
|
||||||
|
"--ep-num-redundant-experts",
|
||||||
|
"4",
|
||||||
|
"--expert-distribution-recorder-buffer-size",
|
||||||
|
"50",
|
||||||
|
"--moe-runner-backend",
|
||||||
|
cls.moe_runner_backend,
|
||||||
|
],
|
||||||
|
env={
|
||||||
|
"HCCL_BUFFSIZE": "1024",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
def test_moe_runner_backend(self):
|
||||||
|
response = requests.post(
|
||||||
|
f"{DEFAULT_URL_FOR_TEST}/generate",
|
||||||
|
json={
|
||||||
|
"text": "The capital of France is",
|
||||||
|
"sampling_params": {
|
||||||
|
"temperature": 0,
|
||||||
|
"max_new_tokens": 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
response.status_code, 200, "The request status code is not 200."
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"Paris", response.text, "The inference result does not include Paris."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMoreRunnerBackendTritonDefault(TestMoreRunnerBackendTriton):
|
||||||
|
moe_runner_backend = "auto"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.bench_serving import get_tokenizer
|
||||||
|
from sglang.test.ascend.test_ascend_utils import QWEN3_32B_WEIGHTS_PATH
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
from sglang.test.server_fixtures.disaggregation_fixture import (
|
||||||
|
PDDisaggregationServerBase,
|
||||||
|
)
|
||||||
|
from sglang.test.test_utils import (
|
||||||
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
popen_launch_pd_server,
|
||||||
|
popen_with_error_check,
|
||||||
|
)
|
||||||
|
|
||||||
|
register_npu_ci(est_time=400, suite="stage-b-test-16-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-16-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class DisaggregationHiCacheBase(PDDisaggregationServerBase):
|
||||||
|
"""Testcase: All parameters for testing the PD_disaggregation feature were configured, inference was successful, and cache_tokens continued to grow..
|
||||||
|
|
||||||
|
[Test Category] Functional
|
||||||
|
[Test Target] PD disaggregatio on NPU
|
||||||
|
--disaggregation-mode; --disaggregation-transfer-backend; --disaggregation-decode-polling-interval;
|
||||||
|
--disaggregation-decode-enable-offload-kvcache; --num-reserved-decode-tokens; --disaggregation-bootstrap-port;
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(DisaggregationHiCacheBase, cls).setUpClass()
|
||||||
|
|
||||||
|
cls.model = QWEN3_32B_WEIGHTS_PATH
|
||||||
|
|
||||||
|
cls.tokenizer = get_tokenizer(cls.model)
|
||||||
|
cls.temp_dir = tempfile.mkdtemp()
|
||||||
|
cls.bootstrap_port = "8996"
|
||||||
|
cls.start_prefill()
|
||||||
|
cls.start_decode()
|
||||||
|
|
||||||
|
# Block until both
|
||||||
|
cls.wait_server_ready(cls.prefill_url + "/health")
|
||||||
|
cls.wait_server_ready(cls.decode_url + "/health")
|
||||||
|
|
||||||
|
cls.launch_router()
|
||||||
|
cls.wait_server_ready(cls.lb_url + "/health")
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def start_prefill(cls):
|
||||||
|
# Prefill with HiCache enabled
|
||||||
|
prefill_args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disaggregation-mode",
|
||||||
|
"prefill",
|
||||||
|
"--disaggregation-transfer-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disaggregation-bootstrap-port",
|
||||||
|
cls.bootstrap_port,
|
||||||
|
"--tp-size",
|
||||||
|
"2",
|
||||||
|
"--enable-hierarchical-cache",
|
||||||
|
"--hicache-io-backend",
|
||||||
|
"kernel_ascend",
|
||||||
|
"--hicache-mem-layout",
|
||||||
|
"page_first_direct",
|
||||||
|
"--hicache-storage-backend",
|
||||||
|
"file",
|
||||||
|
"--hicache-storage-prefetch-policy",
|
||||||
|
"wait_complete",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.9",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
]
|
||||||
|
env = {
|
||||||
|
**os.environ,
|
||||||
|
"SGLANG_HICACHE_FILE_BACKEND_STORAGE_DIR": cls.temp_dir,
|
||||||
|
"ASCEND_MF_STORE_URL": "tcp://127.0.0.1:24667",
|
||||||
|
}
|
||||||
|
cls.process_prefill = popen_launch_pd_server(
|
||||||
|
cls.model,
|
||||||
|
cls.prefill_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=prefill_args,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def start_decode(cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def launch_router(cls):
|
||||||
|
lb_command = [
|
||||||
|
"python3",
|
||||||
|
"-m",
|
||||||
|
"sglang_router.launch_router",
|
||||||
|
"--pd-disaggregation",
|
||||||
|
"--prefill",
|
||||||
|
cls.prefill_url,
|
||||||
|
cls.bootstrap_port,
|
||||||
|
"--decode",
|
||||||
|
cls.decode_url,
|
||||||
|
"--host",
|
||||||
|
cls.base_host,
|
||||||
|
"--port",
|
||||||
|
cls.lb_port,
|
||||||
|
]
|
||||||
|
cls.process_lb = popen_with_error_check(lb_command)
|
||||||
|
cls.wait_server_ready(cls.lb_url + "/health")
|
||||||
|
|
||||||
|
def gen_prompt(self, token_num: int) -> str:
|
||||||
|
# Generate a string consisting of random tokens.
|
||||||
|
all_available_tokens = list(self.tokenizer.get_vocab().values())
|
||||||
|
selected_tokens = random.choices(all_available_tokens, k=token_num)
|
||||||
|
return self.tokenizer.decode(selected_tokens)
|
||||||
|
|
||||||
|
def send_request(
|
||||||
|
self, prompt: str, max_tokens: int = 100, temperature: float = 0.0
|
||||||
|
) -> Dict:
|
||||||
|
# Send a generate request and return response
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.lb_url}/generate",
|
||||||
|
json={
|
||||||
|
"text": prompt,
|
||||||
|
"sampling_params": {
|
||||||
|
"temperature": temperature,
|
||||||
|
"max_new_tokens": max_tokens,
|
||||||
|
"ignore_eos": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
response.status_code,
|
||||||
|
200,
|
||||||
|
f"Request failed: {response.status_code} - {response.text}",
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def trigger_offloading_and_flush(self):
|
||||||
|
# Helper method to trigger offloading and flush cache
|
||||||
|
# Trigger offloading
|
||||||
|
self.send_request(self.gen_prompt(1), max_tokens=150)
|
||||||
|
|
||||||
|
# Flush device cache to force remote storage access
|
||||||
|
time.sleep(2)
|
||||||
|
requests.post(self.prefill_url + "/flush_cache")
|
||||||
|
|
||||||
|
|
||||||
|
class TestDisaggregationDecodeWithHiCache(DisaggregationHiCacheBase):
|
||||||
|
"""Decode startup parameters"""
|
||||||
|
|
||||||
|
ascend_devices = os.environ.get("ASCEND_RT_VISIBLE_DEVICES", "0,1,2,3")
|
||||||
|
base_gpu_id = (
|
||||||
|
ascend_devices.split(",")[2] if len(ascend_devices.split(",")) >= 3 else "2"
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def start_decode(cls):
|
||||||
|
decode_args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disaggregation-mode",
|
||||||
|
"decode",
|
||||||
|
"--disaggregation-transfer-backend",
|
||||||
|
"ascend",
|
||||||
|
"--tp-size",
|
||||||
|
2,
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.9",
|
||||||
|
"--base-gpu-id",
|
||||||
|
cls.base_gpu_id,
|
||||||
|
"--disaggregation-decode-enable-offload-kvcache",
|
||||||
|
"--hicache-io-backend",
|
||||||
|
"kernel_ascend",
|
||||||
|
"--hicache-mem-layout",
|
||||||
|
"page_first_direct",
|
||||||
|
"--hicache-storage-backend",
|
||||||
|
"file",
|
||||||
|
"--hicache-storage-prefetch-policy",
|
||||||
|
"wait_complete",
|
||||||
|
"--num-reserved-decode-tokens",
|
||||||
|
128,
|
||||||
|
"--disaggregation-decode-polling-interval",
|
||||||
|
2,
|
||||||
|
]
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**os.environ,
|
||||||
|
"SGLANG_HICACHE_FILE_BACKEND_STORAGE_DIR": cls.temp_dir,
|
||||||
|
"ASCEND_MF_STORE_URL": "tcp://127.0.0.1:24667",
|
||||||
|
}
|
||||||
|
cls.process_decode = popen_launch_pd_server(
|
||||||
|
cls.model,
|
||||||
|
cls.decode_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=decode_args,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_prefill_cache_hit(self):
|
||||||
|
"""Test that prefill cache works with repeated queries"""
|
||||||
|
repeated_prompt = self.gen_prompt(800)
|
||||||
|
self.send_request(repeated_prompt, max_tokens=100)
|
||||||
|
# Flush cache
|
||||||
|
self.trigger_offloading_and_flush()
|
||||||
|
|
||||||
|
# Second request - should hit cache (faster)
|
||||||
|
response2 = self.send_request(repeated_prompt, max_tokens=100)
|
||||||
|
# Assert cached tokens cnt
|
||||||
|
self.assertGreater(response2["meta_info"]["cached_tokens"], 700)
|
||||||
|
|
||||||
|
def test_multi_turn_conversation_cache(self):
|
||||||
|
# Test multi-turn conversation scenario with cache hit improvement
|
||||||
|
logging.warning("====================Testing request=======================")
|
||||||
|
initial_prompt = self.gen_prompt(300)
|
||||||
|
response1 = self.send_request(initial_prompt, max_tokens=200, temperature=0.1)
|
||||||
|
current_context = initial_prompt + response1["text"]
|
||||||
|
|
||||||
|
previous_cached_tokens = 0
|
||||||
|
|
||||||
|
for turn in range(2, 5):
|
||||||
|
logging.warning(f"\nTurn {turn}: Continuing from previous context")
|
||||||
|
|
||||||
|
response = self.send_request(
|
||||||
|
current_context, max_tokens=200, temperature=0.1
|
||||||
|
)
|
||||||
|
cached_tokens = response["meta_info"]["cached_tokens"]
|
||||||
|
|
||||||
|
logging.warning(f"Turn {turn} cached tokens: {cached_tokens}")
|
||||||
|
logging.warning(
|
||||||
|
f"Improvement: {cached_tokens - previous_cached_tokens} tokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert cache improvement
|
||||||
|
self.assertGreater(
|
||||||
|
cached_tokens,
|
||||||
|
previous_cached_tokens,
|
||||||
|
f"Turn {turn} should have more cached tokens than turn {turn - 1}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update context and cached tokens for next iteration
|
||||||
|
current_context += response["text"]
|
||||||
|
previous_cached_tokens = cached_tokens
|
||||||
|
|
||||||
|
# Flush prefill cache
|
||||||
|
self.trigger_offloading_and_flush()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
super().tearDownClass()
|
||||||
|
if os.path.exists(cls.temp_dir):
|
||||||
|
shutil.rmtree(cls.temp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
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.kits.basic_api_contract_kit import BasicAPIContractMixin
|
||||||
|
from sglang.test.kits.basic_decode_correctness_kit import BasicDecodeCorrectnessMixin
|
||||||
|
from sglang.test.kits.basic_scheduler_stress_kit import BasicSchedulerStressMixin
|
||||||
|
from sglang.test.kits.eval_accuracy_kit import GSM8KMixin
|
||||||
|
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="stage-b-test-1-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=200, suite="nightly-1-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBasicSanityEagle3(
|
||||||
|
BasicAPIContractMixin,
|
||||||
|
BasicDecodeCorrectnessMixin,
|
||||||
|
BasicSchedulerStressMixin,
|
||||||
|
GSM8KMixin,
|
||||||
|
CustomTestCase,
|
||||||
|
):
|
||||||
|
served_model_name = QWEN3_8B_WEIGHTS_PATH
|
||||||
|
|
||||||
|
model = QWEN3_8B_WEIGHTS_PATH
|
||||||
|
gsm8k_num_questions = 1400
|
||||||
|
gsm8k_accuracy_thres = 0.74
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
QWEN3_8B_WEIGHTS_PATH,
|
||||||
|
cls.base_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
||||||
|
other_args=[
|
||||||
|
"--dtype",
|
||||||
|
"float16",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--speculative-algorithm",
|
||||||
|
"EAGLE3",
|
||||||
|
"--speculative-draft-model-path",
|
||||||
|
QWEN3_8B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
"--speculative-num-steps",
|
||||||
|
"1",
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
"1",
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
"2",
|
||||||
|
"--cuda-graph-max-bs",
|
||||||
|
"4",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.7",
|
||||||
|
"--disable-piecewise-cuda-graph",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -9,6 +9,7 @@ from sglang.test.ascend.test_ascend_utils import (
|
|||||||
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="stage-b-test-1-npu-a3", nightly=False)
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
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_32B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
)
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
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=400,
|
||||||
|
suite="stage-b-test-4-npu-a3",
|
||||||
|
nightly=False,
|
||||||
|
)
|
||||||
|
register_npu_ci(
|
||||||
|
est_time=400,
|
||||||
|
suite="nightly-4-npu-a3",
|
||||||
|
nightly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNpuSpeculativeAttentionMode(CustomTestCase):
|
||||||
|
"""Testcase: Verify that model inference accuracy remains uncompromised when launching the server
|
||||||
|
with --speculative-attention-mode set to 'decode' and 'prefill' respectively.
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --speculative-attention-mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _run_gsm8k_eval(self):
|
||||||
|
"""Helper method to run GSM8K evaluation and return metrics."""
|
||||||
|
eval_args = SimpleNamespace(
|
||||||
|
base_url=DEFAULT_URL_FOR_TEST,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
api="completion",
|
||||||
|
model=QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
num_examples=1319,
|
||||||
|
num_threads=128,
|
||||||
|
max_tokens=512,
|
||||||
|
num_shots=5,
|
||||||
|
temperature=0.0,
|
||||||
|
)
|
||||||
|
return run_eval(eval_args)
|
||||||
|
|
||||||
|
def test_speculative_attention_mode_decode(self):
|
||||||
|
"""Test --speculative-attention-mode decode without PD disaggregation."""
|
||||||
|
args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--device",
|
||||||
|
"npu",
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--disable-radix-cache",
|
||||||
|
"--speculative-draft-model-quantization",
|
||||||
|
"unquant",
|
||||||
|
"--speculative-algorithm",
|
||||||
|
"EAGLE3",
|
||||||
|
"--speculative-draft-model-path",
|
||||||
|
QWEN3_32B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
"--speculative-num-steps",
|
||||||
|
"4",
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
"1",
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
"5",
|
||||||
|
"--speculative-attention-mode",
|
||||||
|
"decode",
|
||||||
|
"--tp-size",
|
||||||
|
"4",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.7",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--dtype",
|
||||||
|
"bfloat16",
|
||||||
|
]
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.update(
|
||||||
|
{
|
||||||
|
"SGLANG_ENABLE_OVERLAP_PLAN_STREAM": "1",
|
||||||
|
"SGLANG_ENABLE_SPEC_V2": "1",
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
process = popen_launch_server(
|
||||||
|
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH * 3,
|
||||||
|
other_args=args,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
metrics = self._run_gsm8k_eval()
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
metrics["score"],
|
||||||
|
0.83,
|
||||||
|
f"GSM8K score {metrics['score']} below threshold 0.83",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
kill_process_tree(process.pid)
|
||||||
|
|
||||||
|
def test_speculative_attention_mode_prefill(self):
|
||||||
|
"""Test --speculative-attention-mode prefill without PD disaggregation."""
|
||||||
|
args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--device",
|
||||||
|
"npu",
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--disable-radix-cache",
|
||||||
|
"--speculative-draft-model-quantization",
|
||||||
|
"unquant",
|
||||||
|
"--speculative-algorithm",
|
||||||
|
"EAGLE3",
|
||||||
|
"--speculative-draft-model-path",
|
||||||
|
QWEN3_32B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
"--speculative-num-steps",
|
||||||
|
"4",
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
"1",
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
"5",
|
||||||
|
"--speculative-attention-mode",
|
||||||
|
"prefill",
|
||||||
|
"--tp-size",
|
||||||
|
"4",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.7",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--dtype",
|
||||||
|
"bfloat16",
|
||||||
|
]
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.update(
|
||||||
|
{
|
||||||
|
"SGLANG_ENABLE_OVERLAP_PLAN_STREAM": "1",
|
||||||
|
"SGLANG_ENABLE_SPEC_V2": "1",
|
||||||
|
"TRANSFORMERS_VERBOSITY": "error",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
process = popen_launch_server(
|
||||||
|
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH * 3,
|
||||||
|
other_args=args,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
metrics = self._run_gsm8k_eval()
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
metrics["score"],
|
||||||
|
0.83,
|
||||||
|
f"GSM8K score {metrics['score']} below threshold 0.83",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
kill_process_tree(process.pid)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
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 (
|
||||||
|
DEEPSEEK_R1_0528_W4A8_PER_CHANNEL_WEIGHTS_PATH,
|
||||||
|
)
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
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="stage-b-test-16-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-16-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
MODEL_PATH = DEEPSEEK_R1_0528_W4A8_PER_CHANNEL_WEIGHTS_PATH
|
||||||
|
|
||||||
|
|
||||||
|
class TestAscendSpeculativeDraftAttentionAndMoeRunner(CustomTestCase):
|
||||||
|
"""Testcase: Test configuration '--speculative-draft-attention-backend' and '--speculative-moe-runner-backend' on the GSM8K dataset is no less than 0.9.
|
||||||
|
|
||||||
|
[Test Category] Parameter
|
||||||
|
[Test Target] --speculative-draft-attention-backend; --speculative-moe-runner-backend
|
||||||
|
"""
|
||||||
|
|
||||||
|
os.environ["DEEP_NORMAL_MODE_USE_INT8_QUANT"] = "1"
|
||||||
|
os.environ["HCCL_BUFFSIZE"] = "2048"
|
||||||
|
os.environ["SGLANG_ENABLE_OVERLAP_PLAN_SITEAM"] = "1"
|
||||||
|
os.environ["SGLANG_ENABLE_SPEC_V2"] = "1"
|
||||||
|
env = os.environ.copy()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.models = MODEL_PATH
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
cls.url = urlparse(DEFAULT_URL_FOR_TEST)
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
|
||||||
|
cls.common_args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
0.7,
|
||||||
|
"--disable-radix-cache",
|
||||||
|
"--chunked-prefill-size",
|
||||||
|
32768,
|
||||||
|
"--tp-size",
|
||||||
|
16,
|
||||||
|
"--speculative-algorithm",
|
||||||
|
"NEXTN",
|
||||||
|
"--speculative-num-steps",
|
||||||
|
1,
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
1,
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
2,
|
||||||
|
"--moe-a2a-backend",
|
||||||
|
"deepep",
|
||||||
|
"--deepep-mode",
|
||||||
|
"auto",
|
||||||
|
"--max-running-requests",
|
||||||
|
64,
|
||||||
|
"--speculative-draft-attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--speculative-moe-runner-backend",
|
||||||
|
"auto",
|
||||||
|
]
|
||||||
|
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
MODEL_PATH,
|
||||||
|
cls.base_url,
|
||||||
|
timeout=1500,
|
||||||
|
other_args=cls.common_args,
|
||||||
|
env=cls.env,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
def test_a_gsm8k(self):
|
||||||
|
args = SimpleNamespace(
|
||||||
|
base_url=self.base_url,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
api="completion",
|
||||||
|
num_examples=1319,
|
||||||
|
num_threads=128,
|
||||||
|
max_tokens=512,
|
||||||
|
num_shots=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
metrics = run_eval(args)
|
||||||
|
score = metrics["score"]
|
||||||
|
print(f"GSM8K score for {MODEL_PATH}: {score:.4f}")
|
||||||
|
self.assertIsNotNone(score, "GSM8K evaluation returned no score")
|
||||||
|
self.assertIsInstance(score, float, "Score should be a float")
|
||||||
|
self.assertGreaterEqual(score, 0.9, f"GSM8K score {score} below threshold 0.9")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
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 (
|
||||||
|
DEEPSEEK_R1_0528_W8A8_WEIGHTS_PATH,
|
||||||
|
)
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
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="stage-b-test-16-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-16-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAscendDistTimeout(CustomTestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.model = DEEPSEEK_R1_0528_W8A8_WEIGHTS_PATH
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
cls.accuracy = 0.95
|
||||||
|
cls.url = urlparse(DEFAULT_URL_FOR_TEST)
|
||||||
|
os.environ["HCCL_BUFFSIZE"] = "2048"
|
||||||
|
os.environ["SGLANG_ENABLE_OVERLAP_PLAN_STREAM"] = "1"
|
||||||
|
os.environ["SGLANG_ENABLE_SPEC_V2"] = "1"
|
||||||
|
os.environ["SGLANG_NPU_FUSED_MOE_MODE"] = "1"
|
||||||
|
os.environ["TRANSFORMERS_VERBOSITY"] = "error"
|
||||||
|
cls.env = os.environ.copy()
|
||||||
|
cls.common_args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
0.8,
|
||||||
|
"--disable-radix-cache",
|
||||||
|
"--chunked-prefill-size",
|
||||||
|
2048,
|
||||||
|
"--tp-size",
|
||||||
|
16,
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--speculative-moe-a2a-backend",
|
||||||
|
"ascend_fuseep",
|
||||||
|
"--speculative-num-steps",
|
||||||
|
1,
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
1,
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
2,
|
||||||
|
"--moe-a2a-backend",
|
||||||
|
"ascend_fuseep",
|
||||||
|
"--deepep-mode",
|
||||||
|
"auto",
|
||||||
|
"--speculative-draft-model-quantization",
|
||||||
|
"unquant",
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_a_gsm8k(self):
|
||||||
|
print(f"##=== Testing accuracy: {self.model} ===##")
|
||||||
|
process = popen_launch_server(
|
||||||
|
self.model,
|
||||||
|
self.base_url,
|
||||||
|
timeout=1500,
|
||||||
|
other_args=self.common_args,
|
||||||
|
env=self.env,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
args = SimpleNamespace(
|
||||||
|
base_url=self.base_url,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
api="completion",
|
||||||
|
num_examples=1319,
|
||||||
|
num_threads=128,
|
||||||
|
max_tokens=512,
|
||||||
|
num_shots=5,
|
||||||
|
temperature=0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
metrics = run_eval(args)
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
metrics["score"],
|
||||||
|
self.accuracy,
|
||||||
|
f"GSM8K score {metrics['score']} below threshold {self.accuracy}",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
kill_process_tree(process.pid)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from sglang.test.ascend.test_ascend_utils import (
|
||||||
|
QWEN3_32B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
QWEN3_32B_W8A8_MINDIE_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="stage-b-test-4-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=400, suite="nightly-4-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
_ASCEND_BACKEND = "ascend"
|
||||||
|
|
||||||
|
|
||||||
|
_COMMON_ARGS = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
_ASCEND_BACKEND,
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--disable-radix-cache",
|
||||||
|
"--speculative-draft-model-quantization",
|
||||||
|
"unquant",
|
||||||
|
"--speculative-algorithm",
|
||||||
|
"EAGLE3",
|
||||||
|
"--speculative-draft-model-path",
|
||||||
|
QWEN3_32B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
"--speculative-num-steps",
|
||||||
|
"4",
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
"1",
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
"5",
|
||||||
|
"--speculative-attention-mode",
|
||||||
|
"decode",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.7",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--dtype",
|
||||||
|
"bfloat16",
|
||||||
|
]
|
||||||
|
|
||||||
|
# --speculative-draft-load-format dummy: initializes draft weights with random values.
|
||||||
|
# Valid options: auto (default), dummy.
|
||||||
|
# dummy is used for profiling; output quality is not guaranteed.
|
||||||
|
# --speculative-draft-model-revision main: specifies a branch/tag/commit for the
|
||||||
|
# draft model. Valid range: any valid git ref string; None means default branch.
|
||||||
|
_SERVER_ARGS = _COMMON_ARGS + [
|
||||||
|
"--tp-size",
|
||||||
|
"4",
|
||||||
|
"--speculative-draft-load-format",
|
||||||
|
"dummy",
|
||||||
|
"--speculative-draft-model-revision",
|
||||||
|
"main",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TestNpuSpeculativeDraftParams(CustomTestCase):
|
||||||
|
"""Test --speculative-draft-load-format and --speculative-draft-model-revision
|
||||||
|
with 4-card TP.
|
||||||
|
|
||||||
|
[Test Category] Parameter & Multi-NPU
|
||||||
|
[Test Target]
|
||||||
|
--tp-size 4;
|
||||||
|
--speculative-draft-load-format dummy;
|
||||||
|
--speculative-draft-model-revision main
|
||||||
|
[Model]
|
||||||
|
Target: aleoyang/Qwen3-32B-w8a8-MindIE
|
||||||
|
Draft: Qwen/Qwen3-32B-Eagle3 (dummy weights, revision=main)
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.update(
|
||||||
|
{
|
||||||
|
"SGLANG_ENABLE_OVERLAP_PLAN_STREAM": "1",
|
||||||
|
"SGLANG_ENABLE_SPEC_V2": "1",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.process = popen_launch_server(
|
||||||
|
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
cls.base_url,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH * 5,
|
||||||
|
other_args=_SERVER_ARGS,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls) -> None:
|
||||||
|
kill_process_tree(cls.process.pid)
|
||||||
|
|
||||||
|
def test_draft_params_via_server_info(self):
|
||||||
|
"""Verify draft load format and revision are set correctly via /server_info."""
|
||||||
|
# 1. Health check
|
||||||
|
health_resp = requests.get(f"{self.base_url}/health", timeout=10)
|
||||||
|
self.assertEqual(health_resp.status_code, 200)
|
||||||
|
|
||||||
|
# 2. Get server info and assert parameter values
|
||||||
|
# Note: /get_server_info is deprecated, use /server_info instead.
|
||||||
|
info_resp = requests.get(f"{self.base_url}/server_info", timeout=10)
|
||||||
|
self.assertEqual(info_resp.status_code, 200)
|
||||||
|
info = info_resp.json()
|
||||||
|
|
||||||
|
load_format = info.get("speculative_draft_load_format")
|
||||||
|
revision = info.get("speculative_draft_model_revision")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
load_format,
|
||||||
|
"dummy",
|
||||||
|
"speculative_draft_load_format should be 'dummy'",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
revision,
|
||||||
|
"main",
|
||||||
|
"speculative_draft_model_revision should be 'main'",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Simple generation test to ensure service is functional
|
||||||
|
prompt = "What is the capital of France?"
|
||||||
|
resp = requests.post(
|
||||||
|
f"{self.base_url}/v1/chat/completions",
|
||||||
|
json={
|
||||||
|
"model": QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
"max_tokens": 64,
|
||||||
|
"temperature": 0,
|
||||||
|
},
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
data = resp.json()
|
||||||
|
self.assertIn("choices", data)
|
||||||
|
content = data["choices"][0]["message"]["content"]
|
||||||
|
self.assertGreater(len(content.strip()), 0)
|
||||||
|
|
||||||
|
# With dummy draft weights, the main model still produces correct answer.
|
||||||
|
self.assertIn(
|
||||||
|
"paris",
|
||||||
|
content.lower(),
|
||||||
|
f"Expected 'Paris' in response, but got: {content[:200]}",
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Q: {prompt}")
|
||||||
|
print(f"A: {content}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from sglang.test.ascend.test_ascend_utils import (
|
||||||
|
FR_SPEC_TOKEN_MAP_PATH,
|
||||||
|
LLAMA_3_8B_EAGLE_WEIGHTS_PATH,
|
||||||
|
LLAMA_3_8B_INSTRUCT_WEIGHTS_PATH,
|
||||||
|
QWEN3_32B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
)
|
||||||
|
from sglang.test.ci.ci_register import register_npu_ci
|
||||||
|
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=300, suite="stage-b-test-4-npu-a3", nightly=False)
|
||||||
|
register_npu_ci(est_time=300, suite="nightly-4-npu-a3", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNpuSpeculativeTokenMap(CustomTestCase):
|
||||||
|
"""Test --speculative-token-map with EAGLE3 (ignored) and EAGLE (enabled).
|
||||||
|
|
||||||
|
Both cases run GSM8K evaluation to ensure accuracy does not degrade.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_eagle3_ignores_token_map_gsm8k(self):
|
||||||
|
"""EAGLE3 ignores token map; GSM8K accuracy should meet threshold."""
|
||||||
|
args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--quantization",
|
||||||
|
"modelslim",
|
||||||
|
"--disable-radix-cache",
|
||||||
|
"--speculative-algorithm",
|
||||||
|
"EAGLE3",
|
||||||
|
"--speculative-draft-model-path",
|
||||||
|
QWEN3_32B_EAGLE3_WEIGHTS_PATH,
|
||||||
|
"--speculative-draft-model-quantization",
|
||||||
|
"unquant",
|
||||||
|
"--speculative-num-steps",
|
||||||
|
"4",
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
"1",
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
"5",
|
||||||
|
"--speculative-attention-mode",
|
||||||
|
"decode",
|
||||||
|
"--speculative-token-map",
|
||||||
|
"/nonexistent/token_map.pt", # ignored
|
||||||
|
"--tp-size",
|
||||||
|
"4",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.7",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--dtype",
|
||||||
|
"bfloat16",
|
||||||
|
]
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.update(
|
||||||
|
{
|
||||||
|
"SGLANG_ENABLE_OVERLAP_PLAN_STREAM": "1",
|
||||||
|
"SGLANG_ENABLE_SPEC_V2": "1",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
process = popen_launch_server(
|
||||||
|
QWEN3_32B_W8A8_MINDIE_WEIGHTS_PATH,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH * 3,
|
||||||
|
other_args=args,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
eval_args = SimpleNamespace(
|
||||||
|
base_url=DEFAULT_URL_FOR_TEST,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
api="completion",
|
||||||
|
num_examples=1319,
|
||||||
|
num_threads=128,
|
||||||
|
max_tokens=512,
|
||||||
|
num_shots=5,
|
||||||
|
temperature=0.0,
|
||||||
|
)
|
||||||
|
metrics = run_eval(eval_args)
|
||||||
|
self.assertGreaterEqual(metrics["score"], 0.83)
|
||||||
|
finally:
|
||||||
|
kill_process_tree(process.pid)
|
||||||
|
|
||||||
|
def test_eagle_with_valid_token_map_gsm8k(self):
|
||||||
|
"""EAGLE (EAGLE-2) with valid token map; GSM8K accuracy should meet threshold."""
|
||||||
|
|
||||||
|
args = [
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--attention-backend",
|
||||||
|
"ascend",
|
||||||
|
"--disable-radix-cache",
|
||||||
|
"--speculative-algorithm",
|
||||||
|
"EAGLE",
|
||||||
|
"--speculative-draft-model-path",
|
||||||
|
LLAMA_3_8B_EAGLE_WEIGHTS_PATH,
|
||||||
|
"--speculative-num-steps",
|
||||||
|
"5",
|
||||||
|
"--speculative-eagle-topk",
|
||||||
|
"1",
|
||||||
|
"--speculative-num-draft-tokens",
|
||||||
|
"8",
|
||||||
|
"--speculative-token-map",
|
||||||
|
FR_SPEC_TOKEN_MAP_PATH,
|
||||||
|
"--tp-size",
|
||||||
|
"4",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.7",
|
||||||
|
"--disable-cuda-graph",
|
||||||
|
"--dtype",
|
||||||
|
"float16",
|
||||||
|
]
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.update(
|
||||||
|
{
|
||||||
|
"SGLANG_ENABLE_OVERLAP_PLAN_STREAM": "1",
|
||||||
|
"SGLANG_ENABLE_SPEC_V2": "1",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
process = popen_launch_server(
|
||||||
|
LLAMA_3_8B_INSTRUCT_WEIGHTS_PATH,
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH * 3,
|
||||||
|
other_args=args,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
eval_args = SimpleNamespace(
|
||||||
|
base_url=DEFAULT_URL_FOR_TEST,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
api="completion",
|
||||||
|
num_examples=1319,
|
||||||
|
num_threads=128,
|
||||||
|
max_tokens=512,
|
||||||
|
num_shots=5,
|
||||||
|
temperature=0.0,
|
||||||
|
)
|
||||||
|
metrics = run_eval(eval_args)
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
metrics["score"], 0.75
|
||||||
|
) # adjust threshold as needed
|
||||||
|
finally:
|
||||||
|
kill_process_tree(process.pid)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user