feat: implement single-process mode on npu device.#1894
Conversation
a416ffa to
a6384a5
Compare
|
Does the service currently support multi-machine startup? How to start it? |
one machine one process. I think the only difference between multi-process and single-process is that we don't need |
Previously, the master node's address was required for synchronization; you can check that again. |
|
here is the torch-npu-ops pr: xLLM-AI/torch-npu-ops#1 |
Add single-node single-process serving for NPU: one OS process drives multiple NPUs (one Worker thread per device) instead of one process per device wired together over the network. Selected by leaving --master_node_addr unset; DistManager::setup_single_node_workers builds the in-process worker fleet and their process groups. Tensor-parallel collectives run through a new HcclProcessGroup, an HcclCommInitAll-backed process group that issues raw HCCL collectives on each device's current NPU stream. It deliberately bypasses c10d_npu::ProcessGroupHCCL because two ProcessGroupHCCL instances in one process can dispatch collectives in mismatched orders and deadlock the local_tp_group under load; a single HcclCommInitAll bootstrap sidesteps that. HcclCommInitAll needs a device bound on the calling thread, set via c10_npu::SetDevice (not raw aclrtSetDevice, which desyncs torch_npu's thread-local device cache and leaves worker streams in the wrong context). Single-process multi-device requires the TORCH kernel backend; the ATB backend routes tensor-parallel comms through a process-global comm manager that cannot rendezvous two ranks in one process. setup_single_node_workers CHECKs for --npu_kernel_backend=TORCH and fails fast with guidance to use the multi-process launcher for ATB. Bump the torch_npu_ops submodule to the device-aware ATB context/operation caches the TORCH path's custom ops need. Verified on NPU (910B, 2 cards, Qwen3-8B, TP=2): single-process and multi-process produce byte-identical greedy output, and throughput matches across the two topologies (single 5253 tok/s vs multi 5242 tok/s total, at 96 prompts / concurrency 48 / 1024-in / 512-out). Co-Authored-By: Claude <noreply@anthropic.com>
…ingle_process Single-process serving (one OS process driving multiple local NPUs through a shared HcclCommInitAll world) was selected implicitly by leaving master_node_addr empty. That conflated two orthogonal axes: the in-process parallelism model (single-process multi-device vs one process per device) and whether the deployment spans nodes (which requires master_node_addr for rendezvous). The conflation made "multi-node, one process per machine driving its local cards" inexpressible: setting master_node_addr always forced the one-process-per-device path. Introduce an independent boolean --enable_single_process (DistributedConfig, defaulting false) and drive the single-process decision off it instead of inferring from master_node_addr. Thread it end-to-end: both Options structs (common + runtime), create_options, and all five runtime::Options builders in master.cpp. DistManager forks on options.enable_single_process(); with neither that flag nor master_node_addr set it now fails fast with actionable guidance. xllm.cpp derives is_local from the new flag and keys master-vs-assistant purely on node_rank (single-process always runs at node_rank 0). Scope: single-node single-process only. Multi-node single-process (nnodes > 1) is explicitly rejected in setup_single_node_workers with a "not yet supported" message, leaving the seam for a future intra-node HcclCommInitAll + inter-node CollectiveService two-level group build. BEHAVIOR CHANGE: single-node single-process now requires --enable_single_process=true (previously triggered by omitting --master_node_addr). Start scripts updated; the master_node_addr flag help text no longer claims empty implies single-process. Verified on NPU (910B, 2 cards, Qwen3-8B, TP=2): single-process (new flag) and multi-process both serve and produce byte-identical greedy output; both misconfig guards (neither flag; enable_single_process with nnodes>1) fail fast with clear messages. Co-Authored-By: Claude <noreply@anthropic.com>
…env var Replace the --enable_single_process gflag with the XLLM_ENABLE_SINGLE_PROCESS environment variable so single-process serving can be toggled from the launch environment without threading a CLI flag through every entrypoint. The DistributedConfig field is now populated from the env var (via util::get_bool_env) in from_flags(); the gflag DEFINE, its JSON binding, and its option-category entry are removed. All downstream consumers are unchanged — they still read options.enable_single_process(). Fail-fast and help messages updated to reference XLLM_ENABLE_SINGLE_PROCESS instead of --enable_single_process. Start scripts export the env var. Verified on NPU (910B, 2 cards, Qwen3-8B, TP=2): XLLM_ENABLE_SINGLE_PROCESS=1 serves single-process (Single-node serving world_size=2, coherent output); unset with no master_node_addr fails fast pointing to the env var; the removed --enable_single_process flag is now rejected as unknown; multi-process (master_node_addr set) regression unchanged with byte-identical output. Co-Authored-By: Claude <noreply@anthropic.com>
11128c7 to
9d00af5
Compare
|
在910C上 TP=2 上补测了这个 PR:Qwen3-8B 的单进程模式能跑通,但 Qwen3.5-27B 会在第一条生成请求上崩掉。 测试结果:
环境: Atlas 800T A3 / 测试代码是 merge preview 子模块问题当前 PR 把 复现时我把 Qwen3.5-27B 复现方法权重可以从 ModelScope 下载: env -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY \
-u http_proxy -u https_proxy -u all_proxy \
modelscope download \
--model Qwen/Qwen3.5-27B \
--local_dir /path/to/Qwen3.5-27B \
--max-workers 8切换torch_npu_ops: git -C third_party/torch_npu_ops fetch \
https://github.com/xLLM-AI/torch-npu-ops.git \
refs/pull/1/head
git -C third_party/torch_npu_ops checkout \
b65c7918d70e3f1d46eac11bed751d23eb95ba46
MAX_JOBS=32 CTEST_PARALLEL=16 SKIP_TEST=1 SKIP_EXPORT=1 \
python3 setup.py build --device npu --tilelang-jobs 16单进程 TP2 启动命令: source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/nnal/atb/set_env.sh
export ASCEND_RT_VISIBLE_DEVICES=2,3
export HCCL_IF_BASE_PORT=43432
export XLLM_ENABLE_SINGLE_PROCESS=1
./build/xllm/core/server/xllm \
--model=/path/to/Qwen3.5-27B \
--port=29000 \
--devices=npu:0,npu:1 \
--nnodes=1 \
--node_rank=0 \
--npu_kernel_backend=TORCH \
--block_size=32 \
--max_tokens_per_batch=81920 \
--max_seqs_per_batch=2048 \
--max_memory_utilization=0.8 \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_schedule_overlap=true \
--enable_graph=false \
--enable_prefill_piecewise_graph=false \
--use_contiguous_input_buffer=false \
> /tmp/xllm-qwen35-single.log 2>&1 &等 MODEL_ID=$(curl -fsS http://127.0.0.1:29000/v1/models | jq -r '.data[0].id')
jq -n --arg model "$MODEL_ID" '{
model: $model,
messages: [{role:"user", content:"请只回答:北京是中国的首都。"}],
temperature: 0,
max_tokens: 32,
stream: false,
chat_template_kwargs: {enable_thinking:false}
}' | curl -fsS --max-time 180 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer EMPTY' \
--data-binary @- \
http://127.0.0.1:29000/v1/chat/completions我这里 curl 收到 empty reply,服务随后退出。最早的一组错误是两个线程同时注册 LayerNorm kernel: 整个失败请求里一共出现:
直接原因CANN 头文件里, 当前
相关代码在:
Qwen3-8B 能跑和这个结论并不冲突。Qwen3-8B 的成功运行里没有任何 Triton kernel registration 日志,它没有进入 Qwen3.5 这条懒注册路径。我只验证了 Qwen3-8B 的功能可用,没有复测 PR 描述中的单/多进程吞吐数字。 建议的修复和回归门槛这个问题不能靠忽略 我顺手看了 建议至少补下面几项:
|
… fix Serializes the process-global triton KernelRegistry with a shared_mutex so single-process multi-device (e.g. Qwen3.5) no longer crashes on concurrent kernel registration (ACL_ERROR_RT_KERNEL_DUPLICATE 507028). Co-Authored-By: Claude <noreply@anthropic.com>
fixed in xLLM-AI/torch-npu-ops#1 |
|
补充验证了 测试环境仍是上一条评论中的机器:Atlas 800T A3 / 测试和构建方式如下: git -C third_party/torch_npu_ops fetch \
https://github.com/xLLM-AI/torch-npu-ops.git \
refs/pull/1/head
git -C third_party/torch_npu_ops checkout \
bebc9fe1623e89742576905c80c4593f1604c635
# setup.py 会检查父仓库 gitlink;测试 lane 需要先把 gitlink 对齐到 bebc9fe
git add third_party/torch_npu_ops
MAX_JOBS=32 CTEST_PARALLEL=16 SKIP_TEST=1 SKIP_EXPORT=1 \
python3 setup.py build --device npu --tilelang-jobs 16启动参数与上一条评论完全相同,关键配置是: export ASCEND_RT_VISIBLE_DEVICES=2,3
export XLLM_ENABLE_SINGLE_PROCESS=1
./build/xllm/core/server/xllm \
--model=/home/data/weights/Qwen35-27B \
--port=29200 \
--devices=npu:0,npu:1 \
--nnodes=1 \
--node_rank=0 \
--npu_kernel_backend=TORCH \
--enable_graph=false \
--enable_chunked_prefill=false \
--enable_schedule_overlap=true稳定性方面,做了 3 次冷启动。每次 ready 后先发 1 个请求,再发 16 个请求、并发 8:三轮共 51/51 成功,服务在请求完成后仍然存活。每轮日志都是: 日志里可以看到一个 worker 完成首次注册,另一个 worker 命中 精度使用与上次相同的 C-Eval 57 题、5-shot、temperature=0:
single 与两组 multi 的 57 条模型输出逐条一致。 性能 workload 也与上次一致:parallel=48,warmup=48,measured=96,随机输入目标 1024 tokens,输出固定 512 tokens,temperature=0,
single 相比 PR multi 的输出吞吐高 0.53%,相比 main multi 低 0.98%。这个差异在 1% 左右,而且 multi 数据来自 7 月 11 日,不是当天 ABBA 配对,因此只能判断“没有明显性能损失”,不能认为单进程带来了稳定性能收益。 所以目前的判断是:
|
…ocess mode Single-node single-process mode has no transport between worker and engine, so forward_output_to_raw no longer routes through the forward_output_to_proto -> proto_to_forward_output serialize/deserialize round-trip. It now decodes the host-side tensors straight into the raw vectors, reusing build_token (the primitive the brpc path calls internally) so output remains byte-identical to the multi-node path. Co-Authored-By: Claude <noreply@anthropic.com>
|
In single-process mode,
There is no forward_input_to_packed_proto, no input_write, no shm on this path. The ForwardInput is just copied as a struct (shallow — the tensors are refcounted handles, so it's a pointer/refcount bump, not a data copy). Compare multi-process, where exactly the transport you describe happens:
|
Description
Add single-node single-process serving for NPU: one OS process drives multiple NPUs (one Worker thread per device) instead of one process per device wired together over the network. Selected by leaving --master_node_addr unset; DistManager::setup_single_node_workers builds the in-process worker fleet and their process groups.
Tensor-parallel collectives run through a new HcclProcessGroup, an HcclCommInitAll-backed process group that issues raw HCCL collectives on each device's current NPU stream. It deliberately bypasses c10d_npu::ProcessGroupHCCL because two ProcessGroupHCCL instances in one process can dispatch collectives in mismatched orders and deadlock the local_tp_group under load; a single HcclCommInitAll bootstrap sidesteps that. HcclCommInitAll needs a device bound on the calling thread, set via c10_npu::SetDevice (not raw aclrtSetDevice, which desyncs torch_npu's thread-local device cache and leaves worker streams in the wrong context).
Single-process multi-device requires the TORCH kernel backend; the ATB backend routes tensor-parallel comms through a process-global comm manager that cannot rendezvous two ranks in one process. setup_single_node_workers CHECKs for --npu_kernel_backend=TORCH and fails fast with guidance to use the multi-process launcher for ATB. Bump the torch_npu_ops submodule to the device-aware ATB context/operation caches the TORCH path's custom ops need.
Verified on NPU (910B, 2 cards, Qwen3-8B, TP=2): single-process and multi-process produce byte-identical greedy output, and throughput matches across the two topologies (single 5253 tok/s vs multi 5242 tok/s total, at 96 prompts / concurrency 48 / 1024-in / 512-out).
Performance (96 prompts, concurrency 48, 1024 in / 512 out):
Related Issues
Change Type
Pull Request Checklist
Thank you for contributing to xLLM. Before requesting review, please make sure the following items are complete.
PR Title and Commit Messages
<type>: <subject>.Pre-commit Checks
pre-commitby runningpip install pre-commitor an equivalent command.pre-commit install.pre-commit run --all-filesand fixed any reported issues.Self Review
.agents/skills/code-review/references/custom-code-style.md, especially code written or assisted by AI.mainbranch.Build and Test Coverage
python setup.py build testhas passed on a CUDA machine.python setup.py build testhas passed on an NPU machine.python setup.py build testhas passed on an MLU machine.Reviewer Notes