Skip to content

[NPU] refactor(ci): device-aware test harness for GPU/NPU + Qwen3 NPU smoke tests#349

Open
Meihan-chen wants to merge 4 commits into
vllm-project:ascendfrom
Meihan-chen:refactor/npu-test-harness
Open

[NPU] refactor(ci): device-aware test harness for GPU/NPU + Qwen3 NPU smoke tests#349
Meihan-chen wants to merge 4 commits into
vllm-project:ascendfrom
Meihan-chen:refactor/npu-test-harness

Conversation

@Meihan-chen

Copy link
Copy Markdown
Collaborator

What

Introduce a hardware-platform abstraction so a single tests/test_*.py runs on both
GPU 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) — Platform registry: Ray resource
    args, 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 in execute_train (non-CUDA → launch_commands);
    convert_checkpoint no-ops on bridge platforms; 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) via U.execute_train, faithful to the existing
    run-qwen3-*-npu.sh parameters.

@Meihan-chen
Meihan-chen force-pushed the refactor/npu-test-harness branch from 2a02b8f to dd0b1e0 Compare July 14, 2026 08:16

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +55 to +60
def _detect_npu() -> bool:
try:
from vime.utils.common import is_npu
except ImportError:
return False
return is_npu()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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

Comment on lines +13 to +16
import json
import os
from collections.abc import Callable
from dataclasses import dataclass, field

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import shlex to safely escape environment variables and JSON strings when constructing shell commands.

Suggested change
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

Comment thread vime/utils/external_utils/platforms.py Outdated
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())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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())

Comment on lines +160 to +165
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}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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}"
)

Comment on lines +67 to +75
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 "
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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

@Meihan-chen Meihan-chen mentioned this pull request Jul 16, 2026
4 tasks
… 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>
@Meihan-chen
Meihan-chen force-pushed the refactor/npu-test-harness branch 2 times, most recently from fc944f6 to 42a6817 Compare July 16, 2026 12:35
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>
@Meihan-chen
Meihan-chen force-pushed the refactor/npu-test-harness branch 3 times, most recently from 7834bd0 to a22e076 Compare July 17, 2026 07:36
@Meihan-chen Meihan-chen added bug Something isn't working and removed bug Something isn't working labels Jul 17, 2026
@Meihan-chen
Meihan-chen force-pushed the refactor/npu-test-harness branch 2 times, most recently from 5c904ba to c32c50a Compare July 17, 2026 08:00
…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>
@Meihan-chen
Meihan-chen force-pushed the refactor/npu-test-harness branch from c32c50a to f9e91f7 Compare July 17, 2026 08:24
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>
@Meihan-chen
Meihan-chen force-pushed the refactor/npu-test-harness branch from db49038 to 633d2a2 Compare July 17, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant