Skip to content

[Bug] test_parallel_wall_time: forked workers run near-serially on macOS CI (~2% of runs) #1496

Description

@ChaoWao

Platform

All / Unknown

Runtime Variant

All / Unknown

Description

tests/ut/py/test_hostsub_fork_shm.py::TestParallelExecution::test_parallel_wall_time fails on macOS CI in roughly 2% of runs (4 of 218 sampled ut (macos-latest, 3.10) jobs over Jul 20-26; 0 of 101 Ubuntu jobs).

The test forks 3 workers, dispatches a time.sleep(0.2) callable to each, and asserts the wall time is under 80% of fully-serial.

This is not a marginal threshold. Measured on aarch64 Linux over 7 consecutive runs:

elapsed
ideal parallel 0.200 s
Linux actual median 0.203 s, max 0.204 s
assertion threshold 0.480 s
macOS when failing 0.49 - 0.57 s
fully serial 0.600 s

Linux lands 1.5% above ideal with a 2 ms spread. The macOS failures are 2.5-2.9x that, and 85-95% of fully serial — parallelism essentially did not happen. Raising the threshold would silence exactly the signal this test exists to produce.

Steps to Reproduce

python -m pytest tests/ut/py/test_hostsub_fork_shm.py::TestParallelExecution::test_parallel_wall_time

Only observed on macOS runners, ~1 in 50. I have no macOS box and could not reproduce it; everything below is from CI logs plus controlled Linux experiments.

Expected Behavior

Three children that each only time.sleep(0.2) finish in ~0.2 s wall time, as they do on Linux.

Actual Behavior

expected parallel wall time < 0.48s (serial would be 0.60s), got 0.57s

Git Commit ID

b28bd58

CANN Version

N/A (pure-Python unit test, no NPU)

Driver Version

N/A

Host Platform

macOS (aarch64)

Additional Context

Ruled out, with the evidence:

  • Not the product's worker code. _SubWorkerPool and its _worker_loop are defined locally in the test file; it never imports simpler.worker. Neither Refactor: fold the three child mailbox loops into one state machine #1494 (unifying the child mailbox loops) nor Fix: exit a forked worker child once its parent is gone #1495 (parent-death detection) can affect it.
  • Not a different flake's twin. Fix: give each process its own counter slot in test_l4_sub_and_l3_dispatch #1490 fixed a separate macOS-only failure in test_l4_sub_and_l3_dispatch (a lost update on a shared counter). Unrelated mechanism, unrelated file.
  • Not CPU scarcity, which was my leading hypothesis. The test's _worker_loop is a deliberate tight spin (# tight spin (same as L2 AICPU spin-wait — no yield)), so I expected 3 spinning children plus the parent to oversubscribe a narrow runner and starve the parent between its three sequential dispatch() calls. Pinning the test to 2 and to 3 cores with taskset on Linux gives 5/5 passes at each width. The mechanism does not hold: during the 0.2 s work window the children are inside time.sleep() and consume no CPU, so only the parent needs a core.
  • Not fork cost. The pool is constructed before start = time.monotonic().

Still unknown — and not resolvable without a macOS runner:

  • The runner's actual core count. No CI log line reports it.
  • Whether macOS thread QoS / scheduler behaviour deprioritises a process that has been spinning, delaying the parent's 2nd and 3rd dispatch() by ~0.2 s each. This is the shape the numbers suggest (near-serial = each worker starting a full sleep-period late), but I have no way to observe it.

Please do not "fix" this by relaxing the threshold or adding a retry. At 0.49-0.57 s against a 0.203 s Linux baseline, something is serialising three sleeping processes. A wider bound makes the symptom disappear while leaving whatever does that in place.

A cheap first step for whoever has a macOS machine: print os.cpu_count() and per-worker dispatch/completion timestamps from the test, then read which of the three workers started late.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions