[NPU] refactor(ci): device-aware test harness for GPU/NPU + Qwen3 NPU smoke tests#349
[NPU] refactor(ci): device-aware test harness for GPU/NPU + Qwen3 NPU smoke tests#349Meihan-chen wants to merge 4 commits into
Conversation
2a02b8f to
dd0b1e0
Compare
Documentation build overview
47 files changed ·
|
There was a problem hiding this comment.
Code Review
This pull request introduces NPU test configurations for Qwen3 models and refactors the hardware-platform abstraction into a platform-agnostic registry to support both CUDA and NPU accelerators. The review feedback highlights a potential crash in NPU detection if a RuntimeError is raised, recommends using shlex.quote to safely escape environment variables and JSON strings in shell commands, and points out a potential typo or redundant argument (--kl-coef vs --kl-loss-coef) in the GRPO configuration for the Qwen3 4B NPU test.
| def _detect_npu() -> bool: | ||
| try: | ||
| from vime.utils.common import is_npu | ||
| except ImportError: | ||
| return False | ||
| return is_npu() |
There was a problem hiding this comment.
The is_npu() function in vime/utils/common.py explicitly raises a RuntimeError if torch_npu is installed but no NPU device is available or visible. Since _detect_npu() only catches ImportError, any such RuntimeError will propagate and crash current_platform(), preventing fallback to CUDA or CPU even on non-NPU systems where the package might be present.
We should catch RuntimeError as well to ensure robust platform detection.
| def _detect_npu() -> bool: | |
| try: | |
| from vime.utils.common import is_npu | |
| except ImportError: | |
| return False | |
| return is_npu() | |
| def _detect_npu() -> bool: | |
| try: | |
| from vime.utils.common import is_npu | |
| return is_npu() | |
| except (ImportError, RuntimeError): | |
| return False |
| import json | ||
| import os | ||
| from collections.abc import Callable | ||
| from dataclasses import dataclass, field |
There was a problem hiding this comment.
Import shlex to safely escape environment variables and JSON strings when constructing shell commands.
| import json | |
| import os | |
| from collections.abc import Callable | |
| from dataclasses import dataclass, field | |
| import json | |
| import os | |
| import shlex | |
| from collections.abc import Callable | |
| from dataclasses import dataclass, field |
| if not external_ray: | ||
| # Export the device env BEFORE `ray start` so the raylet and the actors it spawns | ||
| # (e.g. vLLM engines) inherit it; a job's runtime_env does not reach those actors. | ||
| exports = "".join(f'export {k}="{v}" && ' for k, v in all_env.items()) |
There was a problem hiding this comment.
Use shlex.quote to safely escape environment variable values when constructing the export commands, preventing potential shell injection or parsing issues if values contain special characters.
| exports = "".join(f'export {k}="{v}" && ' for k, v in all_env.items()) | |
| exports = "".join(f"export {k}={shlex.quote(v)} && " for k, v in all_env.items()) |
| cmds.append( | ||
| f"export no_proxy=127.0.0.1 && export PYTHONUNBUFFERED=1 && {src}" | ||
| f'ray job submit --address="http://127.0.0.1:8265" ' | ||
| f"--runtime-env-json='{json.dumps(runtime_env)}' " | ||
| f"-- python3 {train_script} {model_args} {train_args}" | ||
| ) |
There was a problem hiding this comment.
Use shlex.quote to safely escape the JSON string for --runtime-env-json. This avoids syntax errors or shell parsing issues if any environment variable or argument contains single quotes or other special characters.
| cmds.append( | |
| f"export no_proxy=127.0.0.1 && export PYTHONUNBUFFERED=1 && {src}" | |
| f'ray job submit --address="http://127.0.0.1:8265" ' | |
| f"--runtime-env-json='{json.dumps(runtime_env)}' " | |
| f"-- python3 {train_script} {model_args} {train_args}" | |
| ) | |
| cmds.append( | |
| f"export no_proxy=127.0.0.1 && export PYTHONUNBUFFERED=1 && {src}" | |
| f'ray job submit --address="http://127.0.0.1:8265" ' | |
| f"--runtime-env-json={shlex.quote(json.dumps(runtime_env))} " | |
| f"-- python3 {train_script} {model_args} {train_args}" | |
| ) |
| grpo_args = ( | ||
| "--advantage-estimator grpo " | ||
| "--kl-loss-coef 0.0 " | ||
| "--kl-loss-type low_var_kl " | ||
| "--kl-coef 0.00 " | ||
| "--entropy-coef 0.0 " | ||
| "--eps-clip 0.2 " | ||
| "--eps-clip-high 0.28 " | ||
| ) |
There was a problem hiding this comment.
There is both --kl-loss-coef 0.0 and --kl-coef 0.00 specified in grpo_args. Please verify if --kl-coef is a typo or redundant, as --kl-loss-coef is typically used (as seen in test_qwen3_30B_A3B_npu.py). If it is unrecognized by the training script, it might cause the test to fail.
grpo_args = (
"--advantage-estimator grpo "
"--kl-loss-coef 0.0 "
"--kl-loss-type low_var_kl "
"--entropy-coef 0.0 "
"--eps-clip 0.2 "
"--eps-clip-high 0.28 "
)… tests Add a hardware-platform abstraction so one tests/test_*.py runs on GPU or NPU: execute_train() dispatches by device (VIME_TEST_DEVICE, else is_npu()). The CUDA path stays byte-identical to upstream; non-CUDA delegates to a vime-owned launcher. - platforms.py: Platform registry — ray resource args, device runtime env, checkpoint mode, unsupported features, detection. Adding an accelerator (XPU/TPU/ROCm) is one register(Platform(...)); the launcher and seam do not change. - command_utils.py: platform seam in execute_train; convert_checkpoint no-ops on bridge platforms (NPU); removed the dead execute_train_npu. - tests/test_qwen3_4B_npu.py, tests/test_qwen3_30B_A3B_npu.py: NPU GRPO smoke tests (dense npu-8, MoE npu-16) driven through U.execute_train, faithful to the existing run-qwen3-*-npu.sh parameters. NPU specifics: Ray advertises the custom NPU resource (placement groups request NPU bundles), device env is exported before ray start so vLLM actors inherit it, and checkpoints load via --megatron-to-hf-mode bridge (torch_dist conversion is unavailable on Ascend). Refs vllm-project#315. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Meihan-chen <zr010426ztt@outlook.com>
fc944f6 to
42a6817
Compare
PR vllm-project#316 landed the NPU pipeline with an empty SUITES map, so the pipeline ran no tests. Wire the two smoke tests this PR adds into the registry: smk runs Qwen3-4B (npu-8); nightly adds Qwen3-30B-A3B (npu-16). Each entry sets VIME_TEST_DEVICE=npu so the shared harness takes the NPU path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Meihan-chen <zr010426ztt@outlook.com>
7834bd0 to
a22e076
Compare
5c904ba to
c32c50a
Compare
…harden
- Rename platforms.py -> launch.py; it is a launch-policy layer for the test/
example harness, not a vime-core hardware abstraction. Soften the docstring so
registering a Platform is not read as full core support for a new accelerator.
- Migrate geo3k_vlm_multi_turn grpo/ppo NPU examples off the deleted
execute_train_npu onto the unified execute_train (num_gpus_per_node=16), which
is the whole point of this PR; nothing imports execute_train_npu anymore.
- npu_suites.py: emit Buildkite command-step env as a key/value map (the
[{name,value}] list form is only valid for k8s podSpec env).
- launch.py: shlex.quote exported env values and the runtime-env-json argument so
values with shell metacharacters can't break the command boundary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Meihan-chen <zr010426ztt@outlook.com>
c32c50a to
f9e91f7
Compare
Build vllm-project#41 proved the step-level env map never reaches the k8s command container: runtime ASCEND_RT_VISIBLE_DEVICES was the device-plugin order, not the 0..15 we set. So the image's preset HF_HUB_OFFLINE=1 stayed in effect and blocked downloading Qwen3-30B (4B passed only because it was already cached). Export HF_HUB_OFFLINE=0 inside the command, which the test process inherits; drop the ineffective step-env entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Meihan-chen <zr010426ztt@outlook.com>
db49038 to
633d2a2
Compare
What
Introduce a hardware-platform abstraction so a single
tests/test_*.pyruns on bothGPU and NPU.
execute_train()dispatches by device —VIME_TEST_DEVICE(set by CI),else
is_npu(). The GPU/CUDA path is byte-identical to upstream and needs no config;non-CUDA delegates to a vime-owned launcher.
Part of the NPU CI effort — RFC #315.
Changes
vime/utils/external_utils/platforms.py(new) —Platformregistry: Ray resourceargs, device runtime env, checkpoint mode, unsupported features, and detection. Adding
an accelerator (XPU/TPU/ROCm) is one
register(Platform(...)); the resolver,launcher, and seam stay platform-agnostic.
command_utils.py— platform seam inexecute_train(non-CUDA →launch_commands);convert_checkpointno-ops on bridge platforms; removed the deadexecute_train_npu.tests/test_qwen3_4B_npu.py,tests/test_qwen3_30B_A3B_npu.py— NPU GRPO smoketests (dense
npu-8, MoEnpu-16) viaU.execute_train, faithful to the existingrun-qwen3-*-npu.shparameters.