Skip to content

[Bug] Flaky ST: TestL3RemoteStore silently drops a peer remote_store under concurrent dist-system-tests runs #2158

Description

@Hzfengsy

Component

Other (please specify in description) — distributed system tests (tests/st/distributed/) + the dist-system-tests CI job (.github/workflows/ci.yml). The failure is a silent numeric mismatch in the L3 pld.tile.remote_store path; it is not yet established whether the defect is in the distributed runtime or in CI job isolation.

Description

tests/st/distributed/test_l3_remote_store.py::TestL3RemoteStore intermittently fails with a data mismatch instead of an infra error — the ring/subview cross-rank remote_store produces wrong values rather than crashing.

It reproduces on unrelated branches at roughly the same wall-clock time and hits a different test method per run, which points at cross-run interference on the shared 2-card NPU host (HCCL ring state or device memory not isolated between concurrent dist-system-tests jobs) rather than a defect in either branch under test.

A silent wrong-value failure is worse than a crash: if it ever flakes the other way it could mask a real correctness regression in the L3 remote-store path.

Failure signature: a dropped write, not corrupted values

With SIZE = 64 and HALF = 32 (test_l3_remote_store.py:40-41), both recorded max diff values are exactly the maximum element of the expected tensor, which is the signature of a destination buffer left at its torch.zeros init value:

Run Test Expected slab max(expected) Reported max diff
30246833082 test_ring_shuffle outputs[0] = inputs[1] = arange(100, 164) 163.0 163.0
30244762056 test_subview_halves outputs[1] = cat(arange(0,32), arange(32,64)) 63.0 63.0

In the test_subview_halves run the diff is 63.0 and not 163.0 — so rank 0's destination (expected_0 = arange(100, 164), max 163) did land correctly while rank 1's destination did not. That is one rank's peer write going missing entirely, not partial numeric corruption.

Caveat: this is inferred from the assertion's max diff arithmetic; the raw output tensors were not captured in the CI logs. Dumping outputs on failure would confirm it directly.

Why concurrent runs are possible

The workflow-level guard only serializes runs on the same ref:

# .github/workflows/ci.yml:10-12
concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Two different branches therefore run dist-system-tests concurrently. The job is currently in TEST MODE and anchors on the runner's own card rather than borrowing free cards via --device auto:

# .github/workflows/ci.yml:605-607
task-submit --device "$DEVICE_ID" --device-num 2 --timeout 3600 --max-time 900 \
  --run "cd $GITHUB_WORKSPACE/dist-checkout && source activate.sh && python -m pytest tests/st/distributed -v --device=\$TASK_DEVICE --ignore=tests/st/distributed/test_l2_multi_orch.py"

So there is no cross-branch mutual exclusion for the 2-card NPU set that these HCCL tests initialize a communicator over.

Steps to Reproduce

Observed twice on CI, ~25 min apart, on two unrelated branches, in the same test file:

  1. Run 30246833082 (branch chore/ci-gate-jobs-behind-lint):

    FAILED tests/st/distributed/test_l3_remote_store.py::TestL3RemoteStore::test_ring_shuffle
      - AssertionError: ring remote_store mismatch: max diff = 163.0
    
  2. Run 30244762056 (branch issue-2134-ir-lower-trace):

    FAILED tests/st/distributed/test_l3_remote_store.py::TestL3RemoteStore::test_subview_halves
      - AssertionError: subview remote_store mismatch: max diff = 63.0
    

Both runs were otherwise 1 failed, 56 passed, 22 skipped. Re-running the failed job alone passed, confirming the failure is not branch-related.

To reproduce deliberately, run two dist-system-tests jobs concurrently against the same NPU pair (or run the file in a loop while a second HCCL job holds the same cards):

source activate.sh
python -m pytest tests/st/distributed/test_l3_remote_store.py -v --device=<dev0>,<dev1> --count=10

Expected Behavior

TestL3RemoteStore::test_ring_shuffle and ::test_subview_halves pass deterministically on every run. Concurrent dist-system-tests jobs on the same host must not observe each other's HCCL ring state or device memory.

If cross-run interference is genuinely unavoidable on a shared host, the tests should fail loudly (comm-init / device-busy error) rather than silently returning a destination buffer that never received its peer write.

Actual Behavior

The cross-rank remote_store silently does not land on one rank; the destination stays at its zero-initialized value and only the final torch.allclose assertion catches it:

FAILED tests/st/distributed/test_l3_remote_store.py::TestL3RemoteStore::test_ring_shuffle
  - AssertionError: ring remote_store mismatch: max diff = 163.0

FAILED tests/st/distributed/test_l3_remote_store.py::TestL3RemoteStore::test_subview_halves
  - AssertionError: subview remote_store mismatch: max diff = 63.0

No HCCL error, no device error, no crash — only the numeric assertion.

Git Commit ID

460f856

NPU Kind

Ascend 910B

Host Platform

Linux (aarch64)

Additional Context

  • Test source: tests/st/distributed/test_l3_remote_store.py (TestL3RemoteStore at :212, test_ring_shuffle at :215, test_subview_halves at :246; SIZE/HALF at :40-41).
  • CI job: dist-system-tests at .github/workflows/ci.yml:541; run step at :597-607.
  • Both tests build their program via ir.compile(..., distributed_config=DistributedConfig(device_ids=device_ids[:2], num_sub_workers=0)) and skip when fewer than 2 devices are available.
  • Suggested next steps, roughly in order of cost:
    1. Dump the actual outputs tensor on assertion failure so the drop-vs-corrupt question is answered directly from CI logs rather than inferred.
    2. Add a concurrency guard so only one HCCL/NPU job touches a given card set at a time (e.g. a dedicated concurrency.group for dist-system-tests that is not keyed on github.ref, or flip the job to --device auto so task-submit lends disjoint cards).
    3. If the flake survives full isolation, the defect is in the L3 remote_store completion/ordering path rather than in CI.

Related: #1840 (flaky ST on a2a3 — different failure mode: hard AICore 507018 crash with device force-reset, single-card, test_dynamic_paged_attention).

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

Status
Ready

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions