UMBP Runtime-Tunable Environment Variables

September 7, 2026 · View on GitHub

Single source of truth for every UMBP_* env var consumed by the Mori UMBP stack at runtime — both the in-process timing/retry knobs parsed by the C++ library and the deployment knobs read by Python launcher scripts and the SGLang/hicache integration layer.

See also:

  • design-master-control-plane.md — what each knob actually affects.
  • src/umbp/include/umbp/common/env_time.h — parser helpers (GetEnvSeconds / GetEnvMilliseconds / GetEnvMicroseconds / GetEnvUint32).
  • src/umbp/include/umbp/common/config.h::UMBPConfig::FromEnvironment — the env overlay applied to per-process UMBPConfig defaults.

Resolution semantics

  • Unset or empty env value → default is used, no log.
  • Non-numeric value, negative number, trailing garbage (e.g. "10abc"), uint32 overflow, or a value below the parameter's min_allowed threshold → default is used, and one WARN per env name per process is emitted on stderr via UMBP_LOG_WARN.
  • Parsing uses std::strtoll with base 10. This means:
    • Leading whitespace is skipped (" 123" parses as 123).
    • An explicit sign prefix is accepted ("+123" OK; "-5" fails the non-negative check on all current params and falls back).
    • Trailing whitespace or any non-digit suffix ("123 ", "0x10") is rejected, falling back to the default.
  • Every production call site caches the resolved value in a function-local static const auto on first use (distributed/SPDK-proxy consumers). ClientRegistryConfig::FromEnvironment() / EvictionConfig::FromEnvironment() themselves do not cache, but the MasterServerConfig they produce is built once at master startup, so the net effect is the same: env changes after first touch have no effect within the same process. To exercise a different value, fork a fresh binary.
  • std::getenv and the logger are NOT async-signal-safe. First use must happen on a normal thread, not inside a signal handler.

When master starts, bin/master_main.cpp prints one line [Master] Resolved timing: ... after MasterServerConfig::FromEnvironment() so operators can audit the effective values.


Master / client registry

Read by the master process (bin/master_main.cpp via MasterServerConfig::FromEnvironment()).

Env varDefaultUnitDescription
UMBP_HEARTBEAT_TTL_SEC10secRegistry entry TTL; client is evicted if no heartbeat arrives within heartbeat_ttl × max_missed_heartbeats.
UMBP_REAPER_INTERVAL_SEC5secReaper wake-up period inside ClientRegistry.
UMBP_MAX_MISSED_HEARTBEATS3countConsecutive misses before a client is considered dead.
UMBP_EVICTION_CHECK_INTERVAL_SEC5secEvictionManager loop period.
UMBP_LEASE_DURATION_SEC2secMaster-side read-lease length granted by Router::RouteGet: IsLeased() keys are skipped by the eviction scan, keeping a key alive from the moment the master returns its location until the reader connects to the owning peer. Only needs to cover the master→reader gRPC round trip + reach the peer (the actual RDMA transfer is covered peer-side by UMBP_DRAM_READ_LEASE_MS), so seconds is already generous; larger values pin actively-read (hot) keys against eviction.
UMBP_HEARTBEAT_INTERVAL_DIVISOR2countRecommended client heartbeat interval = heartbeat_ttl / divisor. min_allowed=1 guards against div-by-zero. Read by the master and echoed in RegisterClientResponse.heartbeat_interval_ms.
UMBP_EVICTKEY_DEADLINE_MS1000msPer-call gRPC deadline applied to outbound EvictKey RPCs from MasterPeerStubPool.
UMBP_HIT_INDEX_TTL_SEC7200secExternal KV hit-count entry TTL. A hash with no counted match for longer than this is removed from the hit index.
UMBP_HIT_INDEX_GC_INTERVAL_SEC60secExternal KV hit-count GC sweep interval.
UMBP_HIT_QUERY_MAX_BATCH4096countMaximum hashes accepted by one GetExternalKvHitCounts request. Oversized requests return gRPC INVALID_ARGUMENT; the server does not truncate.
UMBP_ROUTE_PUT_SELECT_ALGOmost_availableenumBase RoutePut placement algorithm over eligible node entry pools. most_available = pick the node with the most projected free space; random = capacity-weighted random (probability proportional to projected available_bytes, never picks a node that cannot fit). Unknown value → default + one WARN.
UMBP_ROUTE_PUT_NODE_AFFINITYnoneenumNode-affinity bias layered on top of the base algorithm. none = pure base algorithm; same = try to place the whole batch on one node that fits the non-dedup total, else per-key sticky to the first picked node; local = per-key prefer the requester's local node. All three fall back to the base algorithm so affinity never makes a key fail that the base algorithm could route. Unknown value → default + one WARN.
UMBP_MASTER_INDEX_SHARDS32countNumber of independently-locked, key-hashed shards backing the block-location index inside InMemoryMasterMetadataStore (the block lock domain, separate from the single meta_mutex_ that guards client records + external-KV). A heartbeat's event batch only takes the exclusive lock on the shards its keys hash into, so unrelated RoutePut / BatchLookup readers on other shards don't block behind a large apply; full-sync likewise becomes N small critical sections instead of one giant one. Read once at store construction via std::strtol; unset / unparseable / < 1 → default 32 (a WARN is logged on unparseable input), clamped to a max of 4096. 1 reproduces the old single-lock block index. Production guidance for heavy heartbeat fan-out (hundreds of clients): 64.

Peer / pool client

Read by each pool client process (typically an SGLang/vLLM worker that has loaded libmori_pybinds.so).

Env varDefaultUnitDescription
UMBP_DRAM_READ_LEASE_MS500msPeer-side DRAM/HBM read lease: how long a single PeerDramAllocator::Resolve protects its key's pages from concurrent local Evict, covering one RDMA read of those pages. Only needs to exceed one DRAM RDMA round trip (sub-ms), so 500 ms is ~100x margin. Read once at PoolClient::Init; min_allowed=1.
UMBP_SSD_READ_LEASE_MS3000msPeer-side SSD read-staging slot lease: how long a claimed staging slot is reserved before the peer reclaims it by TTL (the fallback when the reader's best-effort ReleaseSsdLease is lost), and, echoed back in PrepareSsdReadResponse.lease_ttl_ms, the reader's validity window anchored at t_send. Must exceed one SSD read + RDMA (slower than DRAM), but too long pins a configured staging slot on a lost release. Also the fallback for the PrepareSsdRead RPC deadline when UMBP_SSD_PREPARE_TIMEOUT_MS is unset. Read once at PoolClient::Init; min_allowed=1. Also the read lease of a policy-declared SsdBackend, where it governs how long a contiguous staging span stays claimed and so sets that backend's read concurrency against a fixed arena — see staging_slots.
UMBP_RESOLVE_BUSY_TIMEOUT_MS30000msOverall deadline for retrying a peer-local or remote batch resolve that returned BUSY because SSD staging was temporarily full. Retries discard the whole response and use exponential backoff capped at 50 ms. A batch whose own working set exceeds the arena returns a permanent failure immediately and is not retried. Values above 300000 ms are clamped.
UMBP_RPC_SHUTDOWN_TIMEOUT_MS3000msDeadline for UnregisterClient and the last Heartbeat in ~MasterClient. Bounds ~MasterClient worst-case at ≤ 2 × this value.
UMBP_GRPC_SHUTDOWN_DEADLINE_SEC3secserver_->Shutdown(deadline) budget, shared by master and peer service.
UMBP_METRICS_REPORT_INTERVAL_MS1000msCadence at which the pool client's MasterClient flushes buffered counters/gauges/histograms via ReportMetrics.
UMBP_RELEASE_LEASE_MAX_RETRIES2countReleaseSsdLease RPC attempt cap on the SSD read path. min_allowed=1.
UMBP_SSD_GET_MAX_ATTEMPTS1countTotal remote SSD get attempts per key. 1 = no retry. Only NO_SLOT and a reader-local lease expiry retry; rpc failure / NOT_FOUND do not. Raise to absorb staging-slot contention. min_allowed=1.
UMBP_SSD_GET_RETRY_BACKOFF_MS2msSleep between remote SSD get retries (only applied when another attempt follows). min_allowed=1.
UMBP_RELEASE_LEASE_TIMEOUT_MS1000msPer-attempt gRPC deadline for the best-effort ReleaseSsdLease RPC so a slow peer can't stall the reader. min_allowed=1.
UMBP_SSD_PREPARE_TIMEOUT_MS0msPer-call gRPC deadline for PrepareSsdRead so a hung/slow peer can't stall the serial batch. 0 = fall back to UMBP_SSD_READ_LEASE_MS (cluster-homogeneous). A timed-out / failed prepare is a hard not-served outcome (NOT retried, and never a miss). min_allowed=0.
UMBP_AUTO_FLUSH_EVENT_THRESHOLD128countPeer-side unshipped KvEvent outbox size at which a completed batch of puts auto-triggers a heartbeat flush (FlushHeartbeat), so the ADDs become visible at the master without waiting for the heartbeat interval or an explicit Flush(). Counted on PeerDramAllocator only (SSD events still wait for the interval). Parsed via std::strtoull (no WARN on bad input); unset / unparseable -> default 128; 0 disables size-based auto-flush entirely (ADDs then ship only on the heartbeat interval or an explicit Flush()); set to a very large value to keep auto-flush armed but effectively never fire on size. Cached on first use in MasterClient::SetPeerDramAllocator.
UMBP_WORKLOAD_TRACE_PATHemptypathRecords successful production PUT/GET calls in the benchmark trace format. Empty disables recording.
UMBP_WORKLOAD_TRACE_CLIENT_ID0idLogical client stream written into production trace events.
UMBP_WORKLOAD_TRACE_SEED0seedPayload seed stored in the trace header for deterministic replay.
UMBP_PEER_MIN_POLLERS8countMinimum gRPC sync poller threads on the peer service. The default pool is small, and every peer RPC that stages SSD bytes holds one of those threads for the whole device read — on a loopback read the pool, not the drive, is the binding constraint. Same head-of-line-blocking fix as UMBP_MASTER_MIN_POLLERS.
UMBP_PEER_MAX_POLLERS64countMaximum gRPC sync poller threads on the peer service. Clamped up to UMBP_PEER_MIN_POLLERS if set lower.
UMBP_DISTRIBUTED_SSD_STAGING_USE_HUGEPAGES0boolBack the SSD staging arena with hugetlbfs pages. Every byte the SSD backend moves crosses that arena twice (device <-> arena, arena <-> wire), so its TLB behaviour is on the critical path in a way an ordinary buffer's is not. Falls back to 4 KiB pages when no hugetlb pages are free — a node must still come up. Standalone server binary only; the SGLang path uses the pybind ssd_staging_use_hugepages field.
UMBP_DISTRIBUTED_SSD_STAGING_HUGEPAGE_SIZE2 MiBbytesHugepage size requested for the SSD staging arena.
UMBP_DISTRIBUTED_RANGED_SCRATCH_BYTES0bytesSize of the registered host arena backing ranged (sub-object) multi-buffer I/O, and the entire opt-in for the feature: at 0 the client allocates and registers nothing and SupportsRangedIO() reports false, so a caller that asks — the SGLang tree connector does — falls back to whole-object I/O. Purely a remote-path resource: remote gets are staged in it and remote puts assembled in it, while ranged I/O served by this node's own medium never touches it. Must exceed the largest object a ranged batch will move remotely; an object bigger than the arena fails that key with an error rather than the batch. Applies on every medium including SSD (that exclusion was lifted — see doc/design-ssd-ranged-io.md). Standalone server binary only; the SGLang path uses the pybind ranged_scratch_size field.
UMBP_DISTRIBUTED_MEDIUMDRAMenumThe one medium this node serves when no backend policy is given: DRAM, HBM or SSD. Selecting SSD is the whole opt-in for a storage node — there is no separate pure-SSD flag. An unknown value is rejected at startup rather than silently defaulting. Ignored once UMBP_BACKEND_POLICY names a policy, which describes the backends directly. Standalone server binary only; the SGLang path uses the pybind medium field.

SPDK proxy

Read by the spdk_proxy daemon (for intervals it emits) and by the pool client process via SpdkProxyTier (for stale / poll checks).

Env varDefaultUnitDescription
UMBP_SPDK_PROXY_HEARTBEAT_STALE_MS5000msThreshold after which the SHM-header heartbeat is considered stale. Consumed independently by proxy daemon, SpdkProxyTier, and probe code in spdk_proxy_shm.cpp.
UMBP_SPDK_PROXY_HEARTBEAT_INTERVAL_MS500msHow often the proxy daemon's PollLoop writes proxy_heartbeat_ms.
UMBP_SPDK_PROXY_REAP_INTERVAL_SEC5secPeriod of dead-channel reap + SyncTelemetry in PollLoop.
UMBP_SPDK_PROXY_POLL_INTERVAL_MS100msSpdkProxyTier::WaitForProxy poll step.
UMBP_SPDK_PROXY_INIT_FAIL_SLEEP_SEC2secSleep before detach when SpdkEnv::Init fails during daemon startup.
UMBP_SPDK_PROXY_BUSY_YIELD_MS1msYield step used by writeback / batch-drain busy waits.
UMBP_SPDK_PROXY_TIMEOUT_MS30000msMax time SpdkProxyTier waits for the proxy to reach READY.
UMBP_SPDK_PROXY_IDLE_EXIT_TIMEOUT_MS30000msDaemon self-exits after this much idle time with zero active sessions.
UMBP_SPDK_PROXY_TENANT_GRACE_MS30000msGrace period before forcibly reaping an inactive tenant.
UMBP_SPDK_PROXY_WRITE_BACK0boolSet non-zero to enable proxy write-back caching.
UMBP_SPDK_PROXY_DEFAULT_TENANT_QUOTA_BYTES0bytesPer-tenant SHM data-region quota. 0 = no per-tenant cap.
UMBP_SPDK_PROXY_CACHE_MB / UMBP_SPDK_RING_MB—MBSPDK ring buffer size in MB. UMBP_SPDK_RING_MB is the canonical name; UMBP_SPDK_PROXY_CACHE_MB is the legacy alias.
UMBP_SPDK_RAID_STRIP_KB128KBRAID strip size when constructing a SPDK RAID bdev across multiple NVMe controllers.

UMBPConfig overlay (FromEnvironment)

UMBPConfig::FromEnvironment() overlays these on top of the struct defaults. Set them before constructing the C++ client (or letting the Python wrapper construct one) — they are read once.

Env varDefaultDescription
UMBP_DRAM_CAPACITY4 GiBdram.capacity_bytes.
UMBP_DRAM_HIGH_WM / UMBP_DRAM_LOW_WM0.9 / 0.7DRAM tier eviction watermarks.
UMBP_SSD_ENABLED10 to disable the SSD tier entirely. This no longer selects a medium: a single-medium SSD node is chosen with UMBP_DISTRIBUTED_MEDIUM=SSD (or distributed.medium), or named as a backend in UMBP_BACKEND_POLICY, and umbp_standalone_server refuses to start if this is set while neither named one, rather than quietly serving DRAM.
UMBP_EMBEDDED_DRAM_PAGE_SIZE2 MiBAllocation granularity of the pool an embedded deployment gets when the config names no deployment at all. The pool is paged, so a value smaller than a page still occupies one: a caller storing KV pages should set this to the page's exact byte size, and one storing small values wants it smaller. Ignored once distributed is configured — set distributed.dram_page_size there instead.
UMBP_SSD_DIR/tmp/umbp_ssdPOSIX backend root(s). Comma-separated for multi-drive, one directory per physical drive (e.g. /mnt/nvme0,/mnt/nvme1). More than one turns the tier into a ShardedSsdTier: keys are placed on the drive with the most free space and batch IO runs on every drive at once, so the tier delivers their aggregate bandwidth.
UMBP_SSD_CAPACITY32 GiBssd.capacity_bytes. With multiple UMBP_SSD_DIR entries this is the TOTAL budget, split evenly across the drives. Charged in padded on-disk bytes (see UMBP_SSD_DIRECT_IO), not raw value bytes.
UMBP_SSD_DIRECT_IO1ssd.direct_io — 1 opens segments O_DIRECT, bypassing the page cache. On by default; set 0 only to deliberately measure or exploit the page cache. Buffered can serve an entire working set from page cache and move zero bytes to the device, which makes drive-count and DRAM-vs-SSD comparisons meaningless — that is why the default is direct. Requires record format v3 (already unconditional), so a directory reads back either way.
UMBP_SSD_VERIFY_CRC1Compute checksums on write, verify on read. Off isolates how much of the SSD path is integrity work rather than storage — the DRAM tier does no checksumming, so an unqualified comparison conflates the two. Records written with it off carry kFlagNoCrc and stay readable either way.
UMBP_SSD_TIER_IO_THREADS4Worker threads for the CPU-bound phases inside one drive's batch (checksum verify on read, checksum + record assembly on write). Matches the DRAM tier's default so a DRAM-vs-SSD comparison is not silently 4 threads against 1.
UMBP_SSD_SHARD_IO_THREADS0Worker threads for the multi-drive fan-out. 0 = one per drive, which is what saturates N drives. Ignored with a single UMBP_SSD_DIR.
UMBP_SSD_SINGLE_FLIGHT1Coalesce concurrent reads of the same key: the first requester touches the drive, the rest are served by memcpy from its buffer. Exists for MLA + TP, where every attention-TP rank GETs a byte-identical key and one page is otherwise read tp_size times. MHA keys carry a per-rank suffix and never collide, so this is a no-op there. Reaches only configs built by UMBPConfig::FromEnvironment() — sglang's UMBPStore needs the pybind single_flight_reads field instead.
UMBP_SSD_DURABILITYstrictstrict fdatasync()s every batch write; relaxed leaves it to the kernel. Relaxed is safe for a pure cache — the bytes are re-fetchable and the peer discards leftover segments at startup — so the flush buys nothing there while costing a large share of write time. Unknown values leave the configured mode untouched.
UMBP_SSD_TIMINGunsetDiagnostic. Prints a stage-by-stage [SsdPerf/tier], [SsdPerf/shard] and [SsdPerf/peer] breakdown per batch (index lookup / device IO / CRC / fan-out, with implied GB/s), which is what answers "device or CPU?" without a profiler. Several lines per batch — turn it off again after investigating.
UMBP_SSD_BACKENDfilefile or spdk. Implicitly upgraded to spdk if UMBP_SPDK_NVME_PCI is set.
UMBP_EVICTION_POLICYlruForwarded to eviction.policy.
UMBP_ROLE(empty)leader / follower / standalone. If unset, falls back to LOCAL_RANK / OMPI_COMM_WORLD_LOCAL_RANK / SLURM_LOCALID / MPI_LOCALRANKID: rank 0 → leader, others → follower.
UMBP_SPDK_BDEV(empty)SPDK bdev name (e.g. Malloc0, NVMe0n1).
UMBP_SPDK_REACTOR_MASK0x1SPDK reactor CPU mask.
UMBP_SPDK_MEM_MB256DPDK hugepage limit (MB).
UMBP_SPDK_NVME_PCI(empty)NVMe PCI BDF (e.g. 0000:47:00.0).
UMBP_SPDK_NVME_CTRLNVMe0SPDK NVMe controller name.
UMBP_SPDK_IO_WORKERS4Internal I/O worker threads for SpdkSsdTier batch ops.
UMBP_SPDK_PROXY_SHM/umbp_spdk_proxySHM segment name.
UMBP_SPDK_PROXY_TENANT_ID0Tenant id for this client.
UMBP_SPDK_PROXY_TENANT_QUOTA_BYTES0Per-tenant quota, 0 = unlimited.
UMBP_SPDK_PROXY_MAX_CHANNELS (alias UMBP_SPDK_PROXY_MAX_RANKS)8Channel count.
UMBP_SPDK_PROXY_DATA_PER_CHANNEL_MB (alias UMBP_SPDK_PROXY_DATA_MB)32MB of SHM data region per channel.
UMBP_SPDK_PROXY_BIN(auto)Path to the spdk_proxy binary. The Python mori.umbp package auto-fills this from the packaged binary.
UMBP_SPDK_PROXY_AUTO_START1Auto-spawn the proxy daemon if not already running.
UMBP_SPDK_PROXY_ALLOW_BORROW0Allow tenants to borrow capacity from the shared pool.
UMBP_SPDK_PROXY_RESERVED_SHARED_BYTES0Reserved shared bytes that cannot be borrowed.

Deployment / launcher env vars

Not parsed by the C++ library directly. These are consumed by the SGLang / hicache wrappers, src/umbp/scripts/run_umbp_single_node_hicache.sh, and src/umbp/scripts/test_umbp_inner.sh to construct the UMBPDistributedConfig plumbed into the C++ side. Listed here so operators can find them in one place.

Env varDescription
UMBP_MASTER_ADDRESShost:port of the master to connect to (e.g. 10.0.0.1:15558). Optional. Without it the same client is an embedded deployment: same backends and transfer engine, nothing routed, registered or heartbeated, and a local miss is the final answer. It is also what decides whether the backend evicts for itself and whether it records heartbeat events at all.
UMBP_BACKEND_POLICYJSON backend/tier policy used by a distributed-backed standalone server.
UMBP_MASTER_LISTENhost:port the master should listen on (when starting it locally).
UMBP_MASTER_AUTO_STARTtrue/false: auto-spawn umbp_master on this node before connecting.
UMBP_MASTER_BINPath to the umbp_master binary. The Python mori.umbp package auto-fills this from the packaged binary; override to point at a custom build.
UMBP_NODE_ADDRESSThis node's address as advertised to peers. Must be reachable from every other node. Required only with a master: with none, nothing registers and no peer can dial in, so umbp_standalone_server synthesizes it (as it does UMBP_NODE_ID) and omitting UMBP_IO_ENGINE_HOST simply builds no RDMA engine.
UMBP_IO_ENGINE_HOSTmori::io::IOEngine listener host (typically 127.0.0.1).
UMBP_IO_ENGINE_PORT / UMBP_IO_ENGINE_PORTSIO engine port (single port, or comma-separated list for multi-engine deployments).
UMBP_PEER_SERVICE_PORTPort PeerServiceServer should bind.
UMBP_CACHE_REMOTE_FETCHEStrue/false: locally re-cache blocks fetched from a remote peer. Set to false for clean throughput benchmarks where you want to measure raw remote-fetch cost.

Pre-existing / unrelated knobs

Env varDefaultDescription
UMBP_LOG_LEVEL1 (WARN)0=INFO, 1=WARN, 2=ERROR; see umbp/common/log.h. Both MORI_UMBP_LOG_LEVEL=DEBUG and UMBP_LOG_LEVEL=0 route through the same logger.

MORI_IO_SQ_BACKOFF_TIMEOUT_US is not in the UMBP namespace; it is owned by MORI-IO (src/io/rdma/common.cpp).


Testing

  • tests/cpp/umbp/distributed/test_env_time.cpp covers the parser helpers (default / valid / empty / non-numeric / trailing garbage / negative / below-min / zero-when-allowed / uint32 overflow / multiple independent names).
  • Business-path tests that require exercising multiple values of the same env within one test suite must fork — the function-local static const caches cannot be reset mid-process.
  • CI environments that export any UMBP_* globally must strip those variables before running UMBP test targets, otherwise the first test to touch a given name will freeze the CI-injected value for the entire process.