Skip to content

Multi-host (>1 client) object-storage training is broken: (A) S3 env not forwarded to remote MPI ranks, and (B) DLIO deadlocks during initialize() before the first training step #592

Description

@gaikwadabhishek

A multi-client ... training <model> run object run over an S3 backend (NVIDIA
AIStore here, but any S3/MinIO target applies) never starts training. There
are two independent failures, hit in sequence:

  • (A) — mlcommons/storage, fix included below. The S3 credentials/endpoint/
    tunables that s3dlio reads from each rank's environment are not forwarded to
    remote MPI ranks
    . OpenMPI does not propagate env to remote ranks, and the
    mpirun prefix only opts-in DLIO_DROP_CACHES_TIMEOUT. So ranks on every host
    except the launcher have no AWS_ENDPOINT_URL/creds/S3DLIO_* and cannot reach
    the backend. Single-host runs hide this (all ranks inherit the launcher's shell).

  • (B) — deeper blocker, appears to be in the DLIO engine
    (mlcommons/DLIO_local_changes).
    Even after (A) is fixed and every rank can
    reach S3, the run deadlocks in DLIO's initialize(): the launcher-host rank
    reaches the step-count allreduce and blocks forever, while the remote-host ranks
    are stuck earlier in initialize() and never reach that collective.

Single-host runs of the same workload work correctly. This makes multi-client
object-mode training (the headline scale-out path) currently unusable.

Environment

  • mlpstorage 3.0.25 (origin/main),
  • s3dlio 0.9.102, DLIO mlcommons/DLIO_local_changes rev 252a54b18d113541e1e8e24832921fac0a7f2b96
  • OpenMPI over TCP: OMPI_MCA_pml=ob1 btl=tcp,self,vader mtl=^ofi btl_tcp_if_include=ens300np0
  • Backend: NVIDIA AIStore S3 endpoint http://<ip>:51080/s3/

Reproduction

# .env: BUCKET, AWS_ENDPOINT_URL, AWS creds, STORAGE_LIBRARY=s3dlio, S3DLIO_FOLLOW_REDIRECTS=1
mlpstorage open training unet3d run object \
  --accelerator-type b200 --num-accelerators 3 \
  --params dataset.num_files_train=56000 \
  --client-host-memory-in-gb 500 --data-dir data/unet3d \
  --hosts <3 IPs> --num-client-hosts 3 \
  --dlio-bin-path <venv>/bin --systemname <name> --results-dir <init'd-dir> \
  --skip-validation

(1 rank/host = the minimal multi-host case; 4/host etc. behave identically.)


Problem A — S3 env not forwarded to remote ranks

Root cause

mlpstorage_py/benchmarks/dlio.py, where the mpirun prefix is assembled (~L575-585):

mpi_prefix = generate_mpi_prefix_cmd(...)
# Forward DLIO_DROP_CACHES_TIMEOUT to ranks ... (mlcommons/storage #487)
if 'DLIO_DROP_CACHES_TIMEOUT' in os.environ:
    mpi_prefix += " -x DLIO_DROP_CACHES_TIMEOUT"
cmd = f"{mpi_prefix} {cmd}"

Only DLIO_DROP_CACHES_TIMEOUT is opted in via -x. But the same file documents
that the S3 backend reads its config straight from each rank's env (~L136):

Credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) and the endpoint
(AWS_ENDPOINT_URL) are read directly from the environment by obj_store_lib.py

OpenMPI does not forward arbitrary env to remote ranks, so on a multi-host run
the ranks on every non-launcher host start with no AWS_* / S3DLIO_* /
STORAGE_LIBRARY / BUCKET, and their first S3 op fails/hangs.

Symptom

The remote-host ranks have none of the storage env; inspecting a remote rank's
/proc/<pid>/environ shows AWS_ENDPOINT_URL etc. absent. (After the fix below,
the same inspection shows them present, and the generated mpirun line carries the
-x AWS_… -x S3DLIO_… -x STORAGE_LIBRARY -x BUCKET flags.)

Proposed fix (mlcommons/storage, verified locally)

Opt-in the storage env vars present in the launcher environment, mirroring the
existing DLIO_DROP_CACHES_TIMEOUT idiom:

if 'DLIO_DROP_CACHES_TIMEOUT' in os.environ:
    mpi_prefix += " -x DLIO_DROP_CACHES_TIMEOUT"
# Object storage: s3dlio reads creds/endpoint/tunables from each rank's env, but
# OpenMPI does not forward env to remote ranks, so a multi-host object run would
# hang on the remote ranks' first S3 op. Opt-in the storage vars from the launcher.
for _v in sorted(os.environ):
    if (_v.startswith('AWS_') or _v.startswith('S3DLIO_')
            or _v in ('STORAGE_LIBRARY', 'BUCKET')):
        mpi_prefix += f" -x {_v}"

This is harmless for local-fs runs (the vars are simply absent) and only opts-in
present vars. Verified: with the patch, -x AWS_… -x S3DLIO_… appear in the
mpirun command and remote ranks receive the full S3 env.


Problem B — DLIO deadlocks in initialize() on multi-host (the real blocker)

With (A) fixed, the run gets further but deadlocks before the first training
step
, every time, on every multi-host config. The log freezes right after:

Streamed file sharding: 56000 train files across 3 ranks via round-robin (rank 0 shard: 18666 files)

and 0 MB/s is observed on all client NICs indefinitely.

What the stacks show (py-spy, default config)

Launcher-host rank (rank 0): blocked in the step-count allreduce, waiting for
all ranks that never arrive —

allreduce_min   (dlio_benchmark/utils/utility.py:371)   # self.comm().allreduce(value, op=MPI.MIN)
derive_configurations (dlio_benchmark/utils/config.py:762)  # self.training_steps = allreduce_min(local_train_steps)
initialize      (dlio_benchmark/main.py:505)

Remote-host rank: stuck earlier in initialize(), in the
streamed-sharding/listing region, and never reaches allreduce_min

__tz_convert    (libc-2.28.so)          # native; py-spy --native
initialize      (dlio_benchmark/main.py:270)
wrapper         (python/common.py:504)
run_benchmark   (dlio_benchmark/main.py:517)

The remote rank is in state R at ~100–115% CPU, the Python line is pinned at
main.py:270 across the entire observation window
(5/5 samples over 25 s), with
0 connections to the S3 endpoint and 0 MPI traffic. So it is busy-spinning
in initialize() — not doing S3 I/O, not in a collective — and never reaches the
allreduce_min rendezvous the other rank is blocked on → classic collective
deadlock.

What it is NOT (ruled out)

  • Not MPI/fabric. A standalone mpi4py test across all 3 hosts (manual
    MPI.Init(), then Barrier, an 8 MB Allreduce, and lowercase
    bcast/allreduce(MIN)/gather/allgather) completes in <0.05 s, exit 0.
  • Not rank/identity detection. Remote ranks have correct
    OMPI_COMM_WORLD_RANK/SIZE (e.g. 1/3), and DLIOMPI's own init
    (Split_type/Split/bcast) completes.
  • Not config-dependent. Reproduces identically with reader.read_threads 0
    vs 4, dataset.skip_listing True vs False (False hangs even earlier, right
    after "Running DLIO with N processes"), and MPI4PY_RC_INITIALIZE 0 vs 1.
  • Not env (Problem A). Reproduces with the full S3 env confirmed present on
    every rank.
  • Not the data. The same dataset trains fine on a single host (reads sustain
    at ~NIC line rate, valid AU).

Where to look

files_pre_sharded is set unconditionally for do_train (main.py ~L260), so the
run always takes the streamed-sharding path that ends in the
derive_configurationsallreduce_min rendezvous. The remote ranks stall
somewhere in main.py initialize() between the sharding loop (~L260) and that
rendezvous (~L505) on multi-host only. Suggested next step for a DLIO maintainer:
add flush-prints through initialize() 260–505 to locate the exact line where
remote ranks stall before allreduce_min. (The persistent __tz_convert native
frame may be a profiling/log-timestamp hot path — python/common.py:504 wrapper is
a decorator around initialize() — but that is unconfirmed.)

This likely belongs in mlcommons/DLIO_local_changes, not mlpstorage.


Related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Fields

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions