Skip to content

firefly/ember/hermes/merlin: add NetworkIO storage subsystem - #2699

Open
deanchester wants to merge 2 commits into
sstsimulator:develfrom
deanchester:io-storage-merlin
Open

firefly/ember/hermes/merlin: add NetworkIO storage subsystem#2699
deanchester wants to merge 2 commits into
sstsimulator:develfrom
deanchester:io-storage-merlin

Conversation

@deanchester

Copy link
Copy Markdown
Contributor

NetworkIO: file-like storage I/O over the SST fabric

Model storage I/O that contends with MPI traffic on a shared fabric. A
subset of nodes act as dedicated I/O nodes, serving file-like
open/read/write/close traffic over the same merlin interconnect as
ordinary messages. Because storage and message traffic share links,
buffers, and arbitration, the two can be studied together in a single
simulation — contention that per-resource models hide.

Lands as the consolidated PR1–PR10 series, rebased onto current devel
(base c1014f6). 71 files, +6923 / −21.

Architecture

    compute node                         I/O node (per pool)
  +----------------+                   +----------------------+
  | Ember motif    |                   | firefly NIC (recv)   |
  | (TestNetworkIO)|                   |   permit check  ->   |
  +-------+--------+                   |     AckDeny if absent |
          | hermes::NetworkIO          |   capacity check ->   |
          | open/read_at/iwrite_at/... |     ShortRead/NoSpace |
  +-------v--------+                   +----------+-----------+
  | hadesNetworkIO |                              |
  |  stripe mapper |   Merlin fabric              v
  | Block/RR/Hash  +====[ NIC | router | link ]==>  SimpleSSD
  |  -> target nid |    (shared w/ MPI traffic)   t = lat + B/bw
  +----------------+                              +-----------+

           pool state (SharedArray/SharedSet, published at init):
             IoPool.<name>        : nid membership
             IoPoolPermit.<name>  : senders allowed to reach pool

What's in it

The feature spans four element libraries:

Library Files Adds
hermes 3 networkIOapi.h — the abstract SST::Hermes::NetworkIO::Interface and its types (Status, IOStatus, OpenMode, FileInfo, FileHandle, IORequest).
firefly 27 hadesNetworkIO (compute-side provider), hadesStorageController (pool publisher), Block/RR/Hash striping mappers, storageModel/simpleSSD backing model, NIC send/recv NetworkIO path + statistics.
ember 41 TestNetworkIO reference motif + generator base, libs/networkIOEvents/ (one EmberEvent per API op), 6 test drivers + 6 test suites.
merlin 2 System.allocateIoNodes() / setIoNodeJobFactory() / setIoNetworkInterface() in pymerlin-base.py to reserve I/O nodes from the node pool.

Incidental: .gitignore (test scratch files) and CONTRIBUTORS.TXT.

How it works

Node model. System.allocateIoNodes() reserves endpoints from a
shared pool; setIoNetworkInterface() attaches a NIC to them. Pools are
named, so single-pool (back-compatible) and multi-pool isolation are both
expressible, and name collisions are detected rather than silently
merged. Each pool publishes its membership and a permit set of allowed
senders; the storage receive path rejects unpermitted senders with an
explicit PermissionDenied ack rather than dropping silently.

API (hermes). SST::Hermes::NetworkIO is shaped after MPI-IO so
motif authors reason in familiar terms: open/close yield file handles;
read_at/write_at block; iread_at/iwrite_at complete via
wait/waitall/test/testany with best-effort cancel. Every
completion carries an IOStatus (bytes transferred + error), so short and
denied transfers are observable. A legacy networkIORead/networkIOWrite
shim targets an implicit file, keeping pre-handle motifs working.

Non-blocking completion follows the OpenMPI wait-sync pattern: each
request owns a completion slot signalled on ack; waitall fills statuses
in request order; testany on an empty set completes immediately;
cancel succeeds without a distinct cancelled status (as with
MPI_Cancel). The caller owns request lifetime until a completion call
consumes it, so freeing a completed or cancelled request has no
use-after-free window.

Data placement (firefly). Files stripe across a pool by a
configurable unit (default 1 MiB) over at most MAX_STRIPE_NIDS (64)
targets. The mapper is pluggable: Block maps contiguous ranges,
Round-Robin cycles fixed blocks, Hash scatters to balance load.
Capacity is enforced at the storage node — overruns surface as
ShortRead/NoSpace, never silent clamping. OpenMode::Create is
accepted but ignored (implicit create); handles left open at finish()
are auto-closed with a warning.

Permits must publish after the virtual NIC resolves its real network id
but before pool state locks. This is enforced structurally: permit
setup runs in the storage provider's init phase and the Ember engine
threads the init phase through to the API, so permits can never bind an
unresolved nid.

Storage timing (firefly). A SimpleSSD backend serves each request
in lat + bytes/bandwidth, with independent read/write bandwidth and
overhead, so response time tracks request size and offered load. NIC
statistics expose per-target read/write counts and latencies for
post-run per-I/O-node inspection.

Relationship to MPI-IO

The interface is deliberately shaped after MPI_File_*, so the mapping is
direct:

NetworkIO MPI-IO
open / close MPI_File_open / MPI_File_close
read_at / write_at MPI_File_read_at / MPI_File_write_at
iread_at / iwrite_at MPI_File_iread_at / MPI_File_iwrite_at
wait / waitall MPI_Wait / MPI_Waitall (index-order fill)
test / testany MPI_Test / MPI_Testany
cancel MPI_Cancel (best-effort, no cancelled status)
IOStatus MPI_Status
striping_unit hint ROMIO striping hint (default 1 MiB)

Out of scope for this revision: collective I/O (read_at_all /
write_at_all), file views and derived datatypes, individual/shared file
pointers, typed buffers, and explicit sync/atomicity. The handle- and
status-based model leaves room to add these without breaking callers.

How to try it

import sst

# ... build topology, router, and a network interface factory ...

system.setTopology(topo, 1)

# reserve 2 nodes as I/O targets and give them a NIC
io_nids = system.allocateIoNodes(2, "linear")
system.setIoNetworkInterface(makeNetworkif())

# an MPI job that drives storage I/O through the TestNetworkIO motif
job = sst.EmberMPIJob(0, 2)
job.addMotif("TestNetworkIO messageSize=4096 iterations=2 "
             "op=write fileSize=4294967296")
system.allocateNodes(job, "linear")
job.useNetworkIO(system)          # wire the job to the I/O pool

system.build()

Full driver: src/sst/elements/ember/test/testIO.py. Reference motif:
src/sst/elements/ember/networkIO/motifs/emberTestNetworkIO.cc.

Testing

Adds 24 tests across six suites to demonstrate the implementation:

Suite Coverage Tests
networkIO basic read/write and statistics output 2
networkIO_handles open/close, multiple files, short reads 3
networkIO_stripe Block/RR/Hash placement and balance 6
networkIO_pools isolation, permit/deny, byte budgets, name-collision, back-compat 9
networkIO_async non-blocking issue, waitall, in-flight cancel 3
networkIO_interference compute/I/O contention on a shared fabric 1

All run at np=1 and np=2. Suites use a seeded RNG for deterministic
statistics output.

Model storage I/O that contends with MPI traffic on a shared fabric.
Storage is served by dedicated I/O nodes reached over the same Merlin
network as compute endpoints, so reads and writes traverse the real NIC,
switch, and link models and share links, buffers, and arbitration with
message traffic. Modelling the two together exposes contention that
per-resource models hide.

Architecture
------------

    compute node                         I/O node (per pool)
  +----------------+                   +----------------------+
  | Ember motif    |                   | firefly NIC (recv)   |
  | (TestNetworkIO)|                   |   permit check  ->   |
  +-------+--------+                   |     AckDeny if absent |
          | hermes::NetworkIO          |   capacity check ->   |
          | open/read_at/iwrite_at/... |     ShortRead/NoSpace |
  +-------v--------+                   +----------+-----------+
  | hadesNetworkIO |                              |
  |  stripe mapper |   Merlin fabric              v
  | Block/RR/Hash  +====[ NIC | router | link ]==>  SimpleSSD
  |  -> target nid |    (shared w/ MPI traffic)   t = lat + B/bw
  +----------------+                              +-----------+

           pool state (SharedArray/SharedSet, published at init):
             IoPool.<name>        : nid membership
             IoPoolPermit.<name>  : senders allowed to reach pool

Node model
----------
merlin's System.allocateIoNodes() reserves endpoints from a shared pool
and setIoNetworkInterface() attaches a NIC to them. Pools are named, so
single-pool (back-compatible) and multi-pool isolation are both
expressible; name collisions are detected, not silently merged. Each
pool publishes its membership and a permit set of allowed senders; the
storage receive path rejects unpermitted senders with an explicit
permission-denied ack rather than dropping silently.

API (hermes)
------------
SST::Hermes::NetworkIO is shaped after MPI-IO: open/close yield file
handles; read_at/write_at block; iread_at/iwrite_at complete via
wait/waitall/test/testany with best-effort cancel. Every completion
carries an IOStatus (bytes transferred + error), so short and denied
transfers are observable. A legacy networkIORead/networkIOWrite shim
targets an implicit file, keeping pre-handle motifs working.

Non-blocking completion uses the OpenMPI wait-sync pattern: each request
owns a completion slot signalled on ack; waitall fills statuses in
request order; testany on an empty set completes immediately; cancel
succeeds without a distinct cancelled status (as with MPI_Cancel). The
caller owns request lifetime until a completion call consumes it, so
freeing a completed or cancelled request has no use-after-free window.

Data placement (firefly)
------------------------
Files stripe across a pool by a configurable unit (default 1 MiB) over
at most MAX_STRIPE_NIDS (64) targets. The mapper is pluggable: Block maps
contiguous ranges, Round-Robin cycles fixed blocks, Hash scatters to
balance load. Capacity is enforced at the storage node -- overruns
surface as ShortRead/NoSpace, never silent clamping. OpenMode::Create is
accepted but ignored (implicit create); handles open at finish() are
auto-closed with a warning.

Permits must publish after the virtual NIC resolves its real network id
but before pool state locks. This is enforced structurally: permit setup
runs in the storage provider's init phase and the Ember engine threads
the init phase through to the API, so permits can never bind an
unresolved nid.

Storage timing (firefly)
------------------------
A SimpleSSD backend serves each request in lat + bytes/bandwidth, with
independent read/write bandwidth and overhead, so response time tracks
request size and offered load. NIC statistics expose per-target
read/write counts and latencies for post-run per-I/O-node inspection.

Motif support (ember)
---------------------
EmberNetworkIOGenerator supplies the base plumbing; the TestNetworkIO
motif drives the full surface (read/write, handles, striping, permit/
deny, async batches with cancel) and doubles as a worked example for
storage-aware motif authors.

Scope
-----
Collective I/O, file views and derived datatypes, individual/shared file
pointers, typed buffers, and explicit sync/atomicity are out of scope
here; the handle- and status-based model leaves room to add them without
breaking callers.

Testing
-------
Adds 24 tests across six suites to demonstrate the implementation:

  networkIO               basic read/write and statistics output      (2)
  networkIO_handles       open/close, multiple files, short reads      (3)
  networkIO_stripe        Block/RR/Hash placement and balance          (6)
  networkIO_pools         isolation, permit/deny, byte budgets,
                          name-collision, back-compat                  (9)
  networkIO_async         non-blocking issue, waitall, cancel          (3)
  networkIO_interference  compute/I/O contention on shared fabric      (1)

All run at np=1 and np=2.
@sst-autotester

Copy link
Copy Markdown
Contributor

Status Flag 'Pre-Test Inspection' - - This Pull Request Requires Inspection... The code must be inspected by a member of the Team before Testing/Merging
NO INSPECTION HAS BEEN PERFORMED ON THIS PULL REQUEST! - This PR must be inspected by setting label 'AT: PRE-TEST INSPECTED'.

@sst-autotester

Copy link
Copy Markdown
Contributor

Status Flag 'Pre-Test Inspection' - - This Pull Request Requires Inspection... The code must be inspected by a member of the Team before Testing/Merging
NO INSPECTION HAS BEEN PERFORMED ON THIS PULL REQUEST! - This PR must be inspected by setting label 'AT: PRE-TEST INSPECTED'.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants