Skip to content

[Bug] Multi-rank comm_init fails in os.fork()'d chip worker — rank 0 dies during HCCL bring-up, version-invariant #1018

Closed

Description

@csy0225

Repository: hw-native-sys/simpler

Re-filed after the previous issue was closed. This version isolates the
failure to simpler's own in-tree example (no pypto-lib / model code) and
adds a version-invariance matrix that rules out CANN, simpler, Python, and
PTOAS versions as the cause. Net: the bug tracks the os.fork() + raw
HcclGetRootInfo chip-worker bootstrap
, not any dependency version.

Summary

Any multi-rank Worker(level=3) dispatch that reaches _ensure_comm_base
crashes during HCCL base-communicator bootstrap:

  • rank 0 (the rootinfo writer) dies inside comm_init
    (simpler/task_interface.py:589 → C++ ChipWorker::comm_init →
    comm_hccl.cpp HcclGetRootInfo) before it writes the rootinfo
    handshake file.
  • every other rank then reports
    [ERROR] comm_init: [comm_hccl.cpp:286] [comm rank N] Timeout waiting for rootinfo
    after HCCL_CONNECT_TIMEOUT, because rank 0 never produced the file.

This blocks every multi-rank L3 deployment in our environment.

It reproduces with simpler's own l3/allreduce_distributed example — no
model, no pypto-lib, and the parent process does no NPU/ACL work before
forking the chips.

Minimal reproducer (simpler in-tree example only)

# venv with simpler installed, pto-isa available, >=2 NPUs, ptoas on PATH
export PTO_ISA_ROOT=/path/to/pto-isa
cd /path/to/simpler/examples/workers/l3/allreduce_distributed   # in-tree example
PYTHONFAULTHANDLER=1 python -X faulthandler main.py -p a2a3 -d 0-1

Observed:

[chip_process pid=14062 dev=0] ready
[chip_process pid=14064 dev=1] ready
Fatal Python error: Segmentation fault
Current thread (most recent call first):
  File ".../simpler/task_interface.py", line 589 in comm_init
  File ".../simpler/worker.py", line 732 in _handle_ctrl_comm_init
  File ".../simpler/worker.py", line 937 in _run_chip_main_loop
  File ".../simpler/worker.py", line 999 in _chip_process_loop
  File ".../simpler/worker.py", line 1959 in _start_hierarchical
  File ".../simpler/worker.py", line 2573 in run
  ...
  File ".../examples/workers/l3/allreduce_distributed/main.py", line 208 in run
[ERROR] comm_init: [comm_hccl.cpp:286] [comm rank 1] Timeout waiting for rootinfo

In main.py, the parent only builds CPU torch tensors and compiles kernels
before worker.init() forks the chips (the base comm is lazy). Verified that
import torch does not load libhccl/libascendcl in the parent
(checked /proc/self/maps) — so the parent is clean at fork time.

Crash locus (rank-0 Ascend plog)

The forked rank-0 chip sets its device and brings up ACL/runtime cleanly, then
dies right at the start of HCCL bring-up — last lines before SIGSEGV:

DRV ... Setup device succeeded. (logical_devid=0; devid=8; ...)
RUNTIME ... StarsEngine: Constructor.
ASCENDCL ... GetAllPackageVersion: Version of hccl package is 9.0.0.beta1
ASCENDCL ... GetAllPackageVersion: Version of driver package is not found   <-- last line, then SIGSEGV

No HcclGetRootInfo completion; no rootinfo file written. The crash is inside
the HCCL library call path, in the os.fork()'d child.

Version-invariance matrix (the decisive evidence)

The same minimal repro was run across four independent version axes. Every
combination fails identically
(rank 1 Timeout waiting for rootinfo, rank 0
gone):

CANN simpler Python PTOAS Result
9.0.0-beta.1 6e84154d 3.10 v0.43 ❌ rank-1 rootinfo timeout
9.0.0-beta.1 afb5c5a9 (latest main) 3.11 v0.44 ❌
8.5.1 afb5c5a9 (latest main) 3.11 v0.44 ❌

For the 8.5.1 row we confirmed the chip actually loaded the 8.5.1 HCCL (not a
stale 9.0.0 one) via the live process map:

$ grep libhccl /proc/<rank1-chip-pid>/maps
/usr/local/Ascend/cann-8.5.1/x86_64-linux/lib64/libhccl.so

So CANN version, simpler version, Python version, and PTOAS version are all
ruled out
. The only invariant is the chip-worker bootstrap: a process created
by os.fork() (worker.py:1935/1955/2002, lazily at Worker.init()) calling
raw HcclGetRootInfo from comm_hccl.cpp.

Control: vLLM (spawn) works on the identical hardware/CANN

On the same pod, same 8 cards, same CANN 9.0.0-beta.1, same driver 25.5.1,
vllm serve <model> -tp 8 (vLLM-Ascend, which uses torch.distributed
init_process_group("hccl") with spawned workers) initializes HCCL across
all 8 ranks and serves cleanly. So the driver / HCCL / hardware stack is
healthy; the differentiator is how the rank process is created — vLLM
spawns fresh processes, simpler forks.

Root-cause hypothesis

HcclGetRootInfo / HCCL bring-up is not safe to call from an os.fork()'d
child under these CANN builds, even when the parent never touched HCCL/ACL
before the fork. vLLM avoids it by spawning fresh worker processes.

Candidate fixes (all upstream / simpler-side):

  1. Spawn (or fork+exec) chip workers for the HCCL/comm path instead of
    bare os.fork(), so each rank brings up HCCL in a clean process.
  2. Guarantee the chip is forked before any ACL/HCCL/driver state is loaded
    in the parent, and that the child performs a complete clean ACL init before
    HcclGetRootInfo — then document/verify that ordering invariant.
  3. Provide a distributed L3 ST that exercises cw.comm_init end-to-end from a
    forked chip on CANN 8.5.1 / 9.0.0 + driver 25.5.1, so this is covered in CI.

What we ruled out

  • CANN version — fails on both 9.0.0-beta.1 and 8.5.1 (8.5.1 libhccl load verified).
  • simpler version — fails on 6e84154d and latest main afb5c5a9.
  • Python version — fails on 3.10 and 3.11.
  • PTOAS version — fails on v0.43 and v0.44 (PTOAS is not in the comm path).
  • Parent pre-initialized HCCL — the allreduce parent does no NPU work before fork; import torch does not load libhccl/libascendcl.
  • Missing HCCL env — HCCL_CONNECT_TIMEOUT=3600, HCCL_INTRA_ROCE_ENABLE=1, HCCL_INTRA_PCIE_ENABLE=0, HCCL_OP_EXPANSION_MODE=AIV all set; HCCL_SOCKET_IFNAME/HCCL_IF_BASE_PORT overrides change nothing.
  • NPU contention — cards are exclusively assigned (private pod); verified free before each run.

Environment

Item Value
OS Ubuntu 22.04, x86_64 host + Ascend 910B via PCIe
NPU 8 × Ascend 910B2C
Driver npu-smi 25.5.1
CANN 9.0.0-beta.1 (also reproduced on 8.5.1)
Python 3.10.12 (also reproduced on 3.11.14)
simpler 6e84154d and latest main afb5c5a9
pto-isa 109c9f72
ptoas binary v0.44 (also v0.43)

Note on a symbolized C++ backtrace

We could not produce a gdb/core backtrace in our pod: gdb is not installed,
ulimit -c is hard-capped at 0, and core_pattern pipes to apport (not
running in-container). The Ascend ATRACE SIGSEGV handler did not leave a
symbolized stack in plog either. Happy to capture one if you can advise a
symbol build / point us at a gdb-enabled image.

Activity

  1. changed the title [-][Bug] `ChipWorker.comm_init` SIGSEGV during HCCL bootstrap on first L3 multi-rank dispatch[/-] [+][Bug] Multi-rank `comm_init` fails in `os.fork()`'d chip worker — rank 0 dies during HCCL bring-up, version-invariant[/+] on Jun 9, 2026
  2. reopened this on Jun 9, 2026
  3. sunkaixuan2018 commented on Jun 10, 2026

    @sunkaixuan2018
    Contributor

    我重新看了一下这个 issue,之前标题里怀疑的 os.fork() 可能不是根因。

    现在更像是 HCCL 弱符号没有在运行期解析到,导致 rank0 在 comm_init 里调用 HcclGetRootInfo 时跳到 NULL 地址。

    链路大概是:

    1. comm_hccl.cpp 调用了 HcclGetRootInfo / HcclCommInitRootInfo。
    2. CANN 的 hccl/hccl_comm.h 里,这些 HCCL API 是 weak symbol。
    3. host_runtime.so 虽然在 CMake 里找到了 libhcomm.so,但 x86 Ubuntu/conda gcc 链接时默认可能带 --as-needed。
    4. --as-needed 只会因为 strong undefined reference 保留库,weak undefined reference 不会让 libhcomm.so 被保留。
    5. 结果 libhcomm.so 没进 DT_NEEDED,运行期 HcclGetRootInfo 解析成 NULL。
    6. rank0 第一次调用 HcclGetRootInfo 就段错,没写 rootinfo,其他 rank 后面就等握手文件超时。

    这也能解释为什么 x86 会崩、ARM host 不一定崩:CANN 头里弱符号声明两边一样,差别更可能来自构建工具链是否默认启用了 --as-needed。

    建议修复是在 onboard host runtime 的 CMake 里,强制让 libhcomm.so 留在 DT_NEEDED 中:

    target_link_libraries(host_runtime
        PRIVATE
            runtime
            ascendcl
            -Wl,--push-state,--no-as-needed ${HCCL_LINK_TARGETS} -Wl,--pop-state
            dl
    )

    这里用 --push-state/--pop-state,只影响 ${HCCL_LINK_TARGETS} 这一段,不改变后面其他库的链接状态。

    可以这样验证:

    pip install --no-build-isolation -e .
    readelf -d build/lib/a2a3/onboard/tensormap_and_ringbuffer/libhost_runtime.so | grep hcomm

    期望能看到:

    (NEEDED) Shared library: [libhcomm.so]
    

    如果 libhcomm.so 出现在依赖里,HcclGetRootInfo / HcclCommInitRootInfo 就会由 libhcomm.so 提供,不会再因为 weak symbol 未解析成 NULL 而在 comm_init 段错。

  4. added 2 commits that reference this issue on Jun 11, 2026
    f6a16da
    4447b06
  5. ChaoWao commented on Jul 11, 2026

    @ChaoWao
    Collaborator

    Resolved by #1158 — the weak-link root cause this issue settled on (libhcomm dropped from DT_NEEDED under --as-needed because the HCCL entry points are weak symbols in CANN's hccl_comm.h → HcclGetRootInfo resolves to NULL → rank 0 SIGSEGV in comm_init before writing the rootinfo handshake) is fixed for both a2a3 and a5 onboard host runtimes (-Wl,--no-as-needed ${HCCL_LINK_TARGETS} -Wl,--as-needed).

    Verified on a current main build — libhcomm.so is in DT_NEEDED of all four libhost_runtime.so variants:

    build/lib/a2a3/onboard/host_build_graph/libhost_runtime.so          (NEEDED) [libhcomm.so]
    build/lib/a2a3/onboard/tensormap_and_ringbuffer/libhost_runtime.so  (NEEDED) [libhcomm.so]
    build/lib/a5/onboard/host_build_graph/libhost_runtime.so            (NEEDED) [libhcomm.so]
    build/lib/a5/onboard/tensormap_and_ringbuffer/libhost_runtime.so    (NEEDED) [libhcomm.so]
    

    #1032 proposed the same fix (--push-state/--pop-state variant) and is closed as duplicate. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions