UMBP storage and transfer simulation

Simulation keeps allocation, placement, commit/abort, lookup, LRU, leases, events, tier policies and control RPCs. Payload storage/transfers are virtual; sim is off by default.

Enable

Keep existing capacities, policies and framework buffers; add the sim settings:

from mori.umbp import UMBPClient, UMBPConfig

config = UMBPConfig.from_environment()
config.sim.enabled = True
config.sim.profile_path = "/path/to/profile.json"
config.sim.trace_path = "/tmp/umbp-worker-0.jsonl"
client = UMBPClient(config)
client.put_from_ptr(key, ptr, nbytes)
client.get_into_ptr(key, ptr, nbytes)

With UMBPConfig.from_environment(), the equivalent settings are UMBP_SIM_ENABLE=1, UMBP_SIM_PROFILE=/path/to/profile.json and UMBP_SIM_TRACE=/tmp/umbp-worker-0.jsonl. Existing APIs and return types remain available. Use a separate trace file per client/run. DRAM, HBM and file-backed SSD tiers are supported. Embedded mode defaults to DRAM; select SSD through the existing backend policy or distributed medium configuration; ssd.enabled alone does not select SSD. Virtual GPU callers require location/device metadata via C++ RegisterMemory; unregistered real pointers use existing classification. Virtual HBM needs no GPU, but normal build dependencies, including HIP, still apply.

SSD uses the existing SsdBackend, PeerSsdManager, segment index and multi-drive placement. Its files and staging payloads are virtual: storage_dir identifies shards but no directories/files are created. Capacity includes record headers, 4 KiB padding and dead records; deleting a key frees disk capacity when its whole segment becomes reclaimable. Commit/abort, LRU, staging limits, read leases and single-flight reads keep their normal behavior. Simulation starts with an empty tier and does not restore persisted data. SPDK backends are outside this file-tier implementation.

Standalone / CI

Run one server per node; its capacity, page size, LRU and policies apply to all ranks:

export UMBP_SIM_ENABLE=1 UMBP_SIM_PROFILE=/path/to/profile.json
export UMBP_SIM_TRACE=/tmp/umbp-server.jsonl
umbp_standalone_server unix:/tmp/umbp.sock

Ranks keep their standalone_process config and socket, enable config.sim.enabled, and use distinct trace files. Forwarding ranks need no profile. For auto_start=True, set the profile in the client; the server receives it and uses trace suffix .server. Both sides need the updated build and matching sim mode. Range checks remain active; closing a rank releases registrations while other ranks retain the shared KV tier. Rank timings include IPC and server work; do not add server trace times to rank times.

Profile and model

sim_profile.json contains illustrative, uncalibrated estimates for cpu_cpu, cpu_gpu, gpu_cpu, gpu_gpu, remote_read, remote_write, file_cpu, cpu_file and file_gpu. Copy it and adjust the coefficients for your platform. Each path names its source/destination memory type, except the remote paths, which use the initiator’s read/write direction. Configure every path your workload uses; a missing path rejects the transfer. Unused paths may be omitted.

This first version uses manually supplied parameters. Automatic calibration belongs in a follow-up PR covering the actual engines (LocalCopyEngine, HbmCopyEngine and MoriIoEngine), with reusable profiles for CPU, GPU and RDMA transfers. SSD coefficients also need measurements of the actual configured storage path.

For staged SSD load/offload, optional file_cpu_staged / cpu_file_staged describe storage-path cost; normal CPU/GPU/network transfers add their own costs. Fit those SSD coefficients to SSDTier device I/O plus its skipped payload work (record building, CRC, bounce copies and the configured durability mode), excluding control and external GPU/RDMA copies that the simulator already counts. Local direct file transfers use file_cpu / cpu_file; profiles without staged variants fall back to those curves. file_gpu models the opt-in GDS read path (UMBP_ENABLE_GDS=1); GPU offload remains GPU→DRAM→SSD, matching the real engines. Checksum-bearing whole reads use file_cpu_staged. Checksum-free records and partial reads prefer file_cpu_staged_no_crc, which models the same SSDTier I/O backend without CRC work. Older profiles fall back to file_cpu, then the staged curve for these reads; whole reads fall back to file_cpu. With CRC disabled, calibrate the staged profile with the same setting. The example SSD values are estimates, not calibration results.

Field

Meaning

channel

Same names share a service queue within one backend client/server.

latency_ns

Nonnegative fixed cost per submitted batch/path.

copy_latency_ns

Nonnegative cost per coalesced copy; default 0.

registration_latency_ns

Optional nonnegative first-FD cost for file_gpu; default 0.

log_size_latency_ns

Optional nonnegative SSD read cost per copy × log2(max(1, mean copy bytes / 4096)); default 0.

batch_log_size_latency_ns

Optional nonnegative SSD read cost per batch × the same size term; default 0.

serialize_latency

Optional SSD read flag to queue latency as well as bytes, for mutually exclusive I/O batches; default false.

bandwidth_bytes_per_second

Positive effective bandwidth.

cpu_cpu coefficients may be arrays indexed by copy threads minus one, clamped to the last entry; other paths use scalars. Existing thread selection/thresholds still apply. Channel capacity is the maximum measured bandwidth; independent callers can use more cores than one call’s copy/write thread limit. Each call retains its thread curve.

size_term = log2(max(1, bytes / copies / 4096))
overhead = latency_ns[p] + copies * copy_latency_ns[p]
         + size_term * (copies * log_size_latency_ns + batch_log_size_latency_ns)
private_ns = overhead + bytes * 1e9 / bandwidth[p]
service_ns = overhead + bytes * 1e9 / channel_capacity
channel_available = max(submitted, channel_available) + service_ns
finish = max(submitted + private_ns, channel_available)

Shared paths sum service costs; zero bytes add no delay and missing paths abort normally. For independent SSD reads, service_ns includes only the byte cost so request latency can overlap, unless serialize_latency is true. Writes and mixed read/write submits retain the full service cost.

Pure file_gpu submits with serialize_latency=false share channel capacity among active synchronous copies. Copy/log latency stays private; fixed batch/log latency is paid once, using the same five coefficients. Ordinary reservations on that channel form a barrier; mixed submits retain the formulas above. File registration timing is unchanged.

Native CRC reads with a matching serialized no-CRC curve on the same channel queue the smaller of the two costs as I/O, then finish the remaining CRC cost outside that queue. Missing or incompatible curves retain full serialization.

cpu_file_staged uses the complete payload curve: record preparation, I/O, durability and record-buffer cleanup are included in its measured total cost. The basic model reserves that combined cost on the SSD channel; it does not model preparation or cleanup overlap between callers separately.

Cross-peer sharing

For share, keep normal Master/peer configuration, distinct node IDs and reachable addresses/ports. Use a dedicated Master, enable sim on every peer, and configure remote_read/remote_write from the initiator’s perspective. Separate channel names allow independent directions. Fit target-link Submit+Wait, not API totals or ping RTT when replacing estimates with measurements. The example GPU/network coefficients remain uncalibrated; use the separate calibration workflow for platform measurements. Local copies finish in Submit; registered remote copies finish in Wait. Unregistered CPU callers retain RDMA bounce geometry and serial staging inside Submit. Ranged scratch and re-cache payloads are virtual; a re-cached replica can serve other peers. Staging uses optional scalar cpu_staging / cpu_staging_read coefficients calibrated from MoriIo’s serial copyin/copyout geometry. Reads fall back to cpu_staging, then the single-thread cpu_cpu estimate. Validate staged timings separately from local copies. Sim/real engines reject each other’s handshake. Distributed ranged I/O still needs scratch. Remote GPU callers retain real registration restrictions; standalone GPU IPC is local-copy-only.

Timing and limits

JSONL fields are api, key_count, requested bytes (range bytes for ranged calls), monotonic start_ns/elapsed_ns, result_count and exception. Results count successes/ matches; void calls and exceptions use -1. Records cover scalar/batch/depth/ranged put/get, existence, register/deregister, clear, flush and close. Completion order may differ. Files append and flush on flush/close/destruction; an empty path uses INFO logging, and disabling INFO skips formatting.

elapsed_ns includes real control code, RPC/locks, queueing and modeled transfers, ending before trace serialization; whole-API timing also includes trace output. Workers/timers use wall time; background work counts when it delays an API/channel.

  • Queues belong to one backend client/server, shared by its ranks. Remote-NIC aggregation, switch congestion and dynamic fair sharing are not modeled.

  • Mixed transfer paths on different channels may overlap in the model; real inline engines can serialize them. Validate mixed-tier batches separately.

  • Real control RPC failures remain possible; modeled payload-transfer failures are not synthesized. Same-host peers measure that host’s RPC cost.

  • Scheduling/control overhead limits short-operation accuracy. Profiles depend on platform, concurrency and workload.

  • Virtual addresses and page/key metadata consume resources. Framework buffers, external copies and model execution remain real; use trace/dummy execution, not generated-token correctness checks.

  • GPU/network accuracy and full SGLang performance validation require separate work.

Build test_umbp_sim and run ctest -R '^umbp_sim$'; these functional tests need no GPUs.