ATOM Environment Variables#
This document describes the environment variables used in the ATOM project.
Metadata H2D#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_H2D_BACKEND |
str |
|
|
Data parallelism#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DP_RANK |
int |
0 |
The rank ID for the current process in data parallelism. |
ATOM_DP_RANK_LOCAL |
int |
0 |
The local rank ID for the current process (used in SPMD mode). |
ATOM_DP_SIZE |
int |
1 |
Total number of data parallel ranks. |
ATOM_DP_MASTER_IP |
str |
127.0.0.1 |
Master IP address for DP ranks coordination. |
ATOM_DP_MASTER_PORT |
int |
29500 |
Master port for DP ranks coordination. |
ATOM_DP_LB_REQ_EQUIV |
int |
512 |
Token-equivalent decode pressure assigned to each in-flight request by |
ATOM_DP_SESSION_AFFINITY |
bool |
false |
Load-place each new session, then keep later turns on the same prefix-cache owner. Reads |
Prefill delayer (TP/DCP and DP attention)#
Coalesces waiting prefills while decode continues. DP attention enables it by
default through ATOM_ENABLE_PREFILL_DELAYER. For a single scheduler (DP=1,
PP=1), including TP/DCP, it is opt-in: set ATOM_PREFILL_DECODE_INTERVAL above
zero and keep the master switch enabled. Interval 0 leaves TP scheduling
unchanged; setting only the master switch does not enable TP coalescing.
On TP, the interval and coalescer are enabled together.
This applies to the standard scheduler, including connector-based P/D roles.
RapidServe’s dedicated PrefillScheduler/DecodeScheduler do not use the delayer.
After each executed prefill, the decode interval runs before all coalescing
bounds. Once it expires, fill, queue age, KV pressure, partial-prefill and stall
bounds decide when to release. MAX_QUEUE_MS stops extra coalescing after that
interval; it does not guarantee end-to-end TTFT. DP decisions reduce local
signals across ranks to keep their phases aligned.
The local fill signal discounts HBM cache hits and uses the admission path’s chunk limits. To bound CPU work, it stops probing fresh requests after their total prompt length reaches one batch budget. It reports only work found so far; it does not treat unseen requests as a full batch. A deep, cache-heavy queue can therefore release through the stall or hold bounds before reaching the fill target. If parked transfers exhaust the unreserved slots, a fresh request may signal possible work with zero estimated tokens until admission resolves its connector match. This is an estimate, not a reservation: checkpoint dependencies, connector results and resource changes during admission can still reduce a batch. Repeated probes reuse immutable prompt hashes while rechecking pool contents and resource fit. During decode protection, only the existence of queued or partial work is checked; partial-prefill hold bounds start after the interval.
Local hybrid models with state checkpointing can wait for an in-flight
producer’s reusable prompt-end checkpoint. The expected prefix must exceed both
the HBM hit and any offload match by at least one prefill budget. P/D transfers
and already-started offload loads keep their own progress paths. Deferred
requests retain their relative order, while at most 16 later queue entries are
examined for independent work each pass. Each request’s wait expires after
TTFT_MAX_TICKS scheduler passes from its first dependency wait; bypassing it
does not restart that deadline. MAX_QUEUE_MS bounds coalescing, not this
dependency wait: time spent queued before a producer becomes runnable does not
make duplicate prefill useful. Pure-attention models do not use checkpoint waits.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_ENABLE_PREFILL_DELAYER |
bool |
true |
Master switch for the prefill coalescer. |
ATOM_PREFILL_DELAYER_TARGET_FILL |
float |
0.9 |
Release once accumulated pending tokens reach |
ATOM_PREFILL_DELAYER_TTFT_MAX_TICKS |
int |
200 |
Max consecutive scheduler ticks a held prefill waits before force-release. Values |
ATOM_PREFILL_DELAYER_PARTIAL_MAX_TICKS |
int |
100 |
Tighter bound for a held mid-chunked-prefill (it holds allocated KV). Values |
ATOM_PREFILL_DELAYER_STALL_TICKS |
int |
10 |
After this many consecutive non-growing ticks, release (burst ended, more won’t come). Values |
ATOM_PREFILL_DELAYER_KV_HIGH_WATERMARK |
float |
0.9 |
At/above this KV usage a prefillable rank force-releases (can’t accumulate a bigger batch anyway). |
ATOM_PREFILL_DELAYER_TOKEN_USAGE_LOW_WATERMARK |
float|”” |
“” (None) |
If set, a prefillable rank below this KV usage force-releases (GPU starving). |
ATOM_PREFILL_DELAYER_MAX_QUEUE_MS |
float|”” |
“” (None) |
After decode protection, release coalescing when the oldest schedulable waiting prefill reaches this age since arrival. Empty disables the age guard. Checkpoint dependency waits use |
ATOM_PREFILL_DECODE_INTERVAL |
int |
0 |
Protect this many scheduler passes after an executed prefill. On DP=1, PP=1, a positive value also enables local coalescing when the master switch is on; |
ATOM_PREFILL_DELAYER_DEBUG |
bool |
false |
Per-tick FIRE/HOLD debug logging. |
ATOM_PREFILL_DELAYER_LOG_EVERY |
int |
1000 |
Emit aggregate stats (per-exit fire counts + hold rate) every N decisions (0 disables). |
Model loading#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DISABLE_MMAP |
bool |
false |
If set to |
ATOM_LOADER_NUM_THREADS |
int |
16 |
Worker threads for weight loading. |
ATOM_LOADER_STRICT_COVERAGE |
bool |
|
Fail loading when a fused MoE parameter does not receive every routed expert from the checkpoint. Set to |
ATOM_LOADER_PREFETCH |
bool |
|
Warm the page cache by reading this rank’s share of the checkpoint sequentially on a background thread, instead of leaving it to demand faults through the mmap. The fault pattern sustains ~3.2 GB/s on a local NVMe that a single sequential reader drives at 6.06 GB/s, so this is an access-pattern fix, not a queue-depth one. Measured on DeepSeek-R1 MXFP4 (350 GiB, TP=4): cold load 154s → 69s. Set to |
ATOM_LOADER_PREFETCH_THREADS |
int |
4 |
Concurrent sequential readers per rank used by the prefetcher. The device saturates at ~2 streams, so raising this mostly adds contention with the loader; |
ATOM_LOADER_PREFETCH_BLOCK_MB |
int |
16 |
Read block size for the prefetcher, in MiB. |
ATOM_LOADER_FADVISE |
bool |
|
Issue |
ATOM_ONLINE_QUANT_STREAMING |
bool |
|
Opt in to quantizing eligible online-quant modules as soon as their checkpoint weights are complete, then release source storage to reduce load-time peak memory. Only active with a valid online quantization config. See the streaming online quantization guide. |
ATOM_ONLINE_QUANT_STREAMING_HOST_STAGING |
bool |
|
Assemble streamed module weights in CPU storage before one H2D transfer. Keeps the checkpoint walk parallel; disabling it buffers loader calls and forces the checkpoint walk to one thread. |
ATOM_ONLINE_QUANT_STREAMING_THREADS |
int |
|
Tail workers for H2D, per-module quantization, and source release. More workers increase overlap and in-flight memory; |
Plugin mode#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DISABLE_VLLM_PLUGIN |
bool |
0 (false) |
If set to |
Kernel / backend selection#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_USE_TRITON_GEMM |
bool |
0 (false) |
If set to |
ATOM_FP8_BLOCKSCALE_USE_E8M0_SCALE |
bool |
unset (per checkpoint) |
E8M0 rather than FP32 128x128 FP8 block scales, for the weight and the activation quantized for it; on gfx950 E8M0 operands take the AITER FlyDSL GEMM. Unset: E8M0 on every arch but gfx942 when the checkpoint declares |
ATOM_GROUP32_WEIGHT_PRESHUFFLE |
bool |
1 (true) |
On gfx950, (16, 16)-shuffle native FP8 32x32 group32 weights (DeepSeek-V4.1) at load and after a weight sync, so AITER’s preshuffled group32 GEMM (FlyDSL) reads them; that GEMM emits BF16 only. Set to |
ATOM_USE_FP4_NON_SHUFFLE_TRITON_GEMM |
bool |
0 (false) |
If set to |
ATOM_MHC_USE_BF16 |
bool |
1 (true) |
Use AITER BF16 hi/lo mHC computation for attention, FFN and head. After loading, replace FP32 fn storage with |
ATOM_USE_TRITON_MXFP4_BMM |
bool |
0 (false) |
If set to |
ATOM_USE_FLYDSL_GATHER_KV_B_PROJ |
bool |
1 (true) |
Use the FlyDSL fused gather + |
ATOM_USE_FLYDSL_FP8_PREFILL_ATTN |
bool |
0 (false) |
Enable FlyDSL FP8 MLA prefill on gfx950, including fused K concatenation with QKV quantization and direct FP8 output from FlyDSL gather where supported. AITER capability checks are cached per layer. Missing FP8 attention support or unsupported device/model configurations select BF16 attention before quantization and emit a warning once per process. FP16 activations and synthetic RoPE-padding configurations also use the normal attention path. Cached K/V reuse descales |
ATOM_PA_FLYDSL |
bool |
0 (false) |
Route the MHA paged decode to aiter’s FlyDSL kernel, with its work planner, instead of gluon. Shapes outside FlyDSL’s domain, calls over 4096 sequences and calls whose context bound is below the query window fall back to gluon. Applies to the vLLM bridge too. Needs aiter with #5809; upgrade ATOM and aiter together. Plans use aiter’s default workgroup budget (2×CU). In native ATOM, captured batch sizes use a plan built by the metadata builder (shared by target and EAGLE draft); other calls, and every vLLM bridge call, get a runtime plan, and a new row count compiles the planner once. Native dense plans use aiter’s default partition ceiling; MiniMax-M3 sparse and the vLLM bridge keep their static split count. MiniMax-M3 sparse decode refreshes its plan once per step. |
ATOM_V4_HCA_PERSIST |
bool |
1 (true) |
DeepSeek-V4 HCA ( |
ATOM_V4_HCA_PERSIST_MIN_ROWS |
int |
15 |
Smallest decode call (q rows) that uses |
ATOM_DCP_SPARSE_PREFILL_NONPS |
bool |
1 (true) |
DSA models under DCP: run sparse prefill attention on the non-persistent fp8 gqa64 MLA kernel, which needs no work metadata. Applies with fp8 q/KV, 64 gathered heads (tp4/dcp4, tp8/dcp8), gfx950, |
GLM-5.3#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_GLM5_KPOOL |
bool |
1 (true) |
Enable the pooled sparse indexer. Setting |
ATOM_GLM5_FORCE_DENSE_MLA |
bool |
0 (false) |
Disable sparse MLA for short-context bring-up comparisons. |
ATOM_GLM5_DISABLE_FUSED_MHC |
bool |
0 (false) |
Force the PyTorch mHC reference path instead of AITER’s fused kernels. |
MiniMax-M3#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_MONO_ENABLE |
bool |
1 (true) |
Fused per-layer decode for MiniMax-M3 and DeepSeek-V4.1, on by default ( |
ATOM_MONO_CHECK |
bool |
0 (false) |
Debug aid for |
ATOM_MONO_TRACE |
path |
unset |
Debug aid for |
ATOM_MONO_DEBUG |
bool |
0 (false) |
Debug aid for |
ATOM_MONO_TIMELINE |
path prefix |
unset |
Debug aid for |
Mono decode routing#
DeepSeek-V4.1 (atom/models/deepseek_v41/mono/): two kernels a layer – K1 (mHC seam, attention norm, q / kv projections, the indexer query) and K2, one launch of K2a (the indexer’s scores and top-k at any context length, the sparse decode attention, the output projections and their all-reduce, the FFN seam) then K2b (the MoE and its all-reduce) – with the compressor and Engram left to the original modules. It routes a decode step of up to 48 rows, however they split into requests (a DSpark verify of any length a request, confidence-scheduled or ragged included, or one token a request without a drafter; each row’s window from its own batch id; one kernel build per width) and the DSpark draft’s block backbone of up to 8 requests at the full block width (draft_runner.py; after a prefill as after a decode), the draft’s LM head and Markov sampler unchanged. It needs TP2, TP4 or TP8 without EP (the routed experts sharded across TP; every per-rank width from kernels/dims.py; TP8’s kernels are compile-checked only, since the original path does not start at TP8), DP / PP / CP 1, no TBO, no plugin mode, FULL cudagraph or eager, bf16 KV and an fp8 or fp4 index cache (fp4: K1 quantizes the indexer query to E2M1 and K2a scores with index_score_fp4), no speculative decoding, or DSpark with a request’s verify (speculative tokens + 1) of at most 48 rows (another drafter’s hooks on the target layers would not fire under mono), ATOM_DSV41_SIDE_STREAMS=0, routed experts in A8W4 (MXFP4 weights, MXFP8 activations, gate / up interleaved: checked when the weights bind), at most 1024 attention keys a token (sliding window + index top-k) and the indexer shape the kernels are written for (top-512, 2048 candidate blocks of 8 rows, 32 index heads of 128).
MiniMax-M3 (atom/models/minimax_m3/mono/): decode steps of up to 16 tokens (requests x speculative query tokens; a verify’s tokens one row each), dense and sparse layers, Eagle3 aux hidden states included. It needs TP4, ptpc-FP8 attention linears, fp8 KV and index cache, KV block size 128, max_model_len up to 1M, no TBO / DP / PP / plugin mode, and FULL cudagraph or eager. A layer that reuses an earlier layer’s selection is served, as long as the first sparse layer selects. The same entry class serves both paths.
MoE all2all (MoRI) wire format#
Both are opt-in and default to off; they only apply with DP attention + expert
parallelism. They are not symmetric — FP4 dispatch only moves a quantization
the MoE GEMM was going to perform anyway (it consumes FP4 activations either
way, and per_1x32 is per-row, so it does not matter which rank runs it), while
FP8 combine adds a quantization that would not otherwise happen, since the
expert output is bf16. Treat the dispatch knob as format matching and the
combine knob as a quality/throughput tradeoff.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_MORI_FP4_DISPATCH |
bool |
0 (false) |
If set to |
ATOM_MEGA_COMBINE_WIRE |
str |
|
MegaMoE ( |
ATOM_MEGA_DECODE_MTPR |
int |
128 |
Capacity (rows per rank) of the small MegaMoEV2 instance that DP-unified decode / verify / draft passes reuse ( |
ATOM_MEGA_MASK_PAD_ROWS |
bool |
0 (false) |
If set to |
ATOM_MORI_COMBINE_QUANT |
str |
|
Combine-side codec passed into the MoRI config. |
EPLB#
Applies with --enable-eplb; the schedule itself is set by --eplb-config
(load_window_size, rebalance_interval, rebalance_min_balancedness, …).
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_EPLB_MAX_REBALANCES |
int |
0 (no limit) |
Stop periodic rebalancing once this many rebalances have run, and serve with that expert placement for the rest of the process: balance on early traffic, then pay no further migrations. Only rebalances that run count; an interval the balancedness gate skips does not. The limit is checked after a rebalance’s last migration chunk commits, on every rank in lockstep; after it, the manager also stops reducing the per-step has-prefill flag. Load recording continues (its buffers are fixed for CUDA graphs) but is no longer read. |
Fusion passes#
RMSNorm#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_USE_MODEL_SENSITIVE_RMSNORM |
bool |
0 (false) |
If set to |
TP AllReduce fusion#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_ENABLE_ALLREDUCE_RMSNORM_FUSION |
bool |
1 (true) |
If set to |
DeepSeek-style#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_ENABLE_DS_INPUT_RMSNORM_QUANT_FUSION |
bool |
1 (true) |
If set to |
ATOM_ENABLE_DS_QKNORM_FUSION |
bool |
1 (true) |
If set to |
ATOM_ENABLE_DS_QKNORM_QUANT_FUSION |
bool |
1 (true) |
If set to |
ATOM_DUAL_STREAM_MOE_TOKEN_THRESHOLD |
int |
1024 |
Upper bound on MoE token count ( |
ATOM_DUAL_STREAM_PIECEWISE |
bool |
0 |
Opt-in: allow a PIECEWISE-captured graph piece to hold the MoE dual-stream fork/join (shared experts on |
DSpark block sampling#
DSpark drafts a num_speculative_tokens-wide block in one backbone pass, then
samples it left-to-right with a low-rank first-order Markov head
(logits_k = base_logits_k + W1[x_{k-1}] @ W2ᵀ, x_k = argmax(logits_k)). The
unfused loop casts the whole [V, r] W2 table to fp32 on every iteration and
materializes two [B, V] fp32 tensors that only an argmax reads. See
atom/model_ops/dspark_markov_sample.py.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DSPARK_FUSED_MARKOV_SAMPLE |
bool |
1 (true) |
Sample the DSpark block with a fused Triton kernel that computes the rank- |
Qwen3 style#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_ENABLE_QK_NORM_ROPE_CACHE_QUANT_FUSION |
bool |
0 (false) |
If set to |
Llama-style#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_LLAMA_ENABLE_AITER_TRITON_FUSED_RMSNORM_QUANT |
bool |
1 (true) |
If set to |
ATOM_LLAMA_ENABLE_AITER_TRITON_FUSED_SILU_MUL_QUANT |
bool |
1 (true) |
If set to |
Draft CUDAGraphs (all drafter flavors)#
A drafter declares its forward passes as DraftGraphs (atom/spec_decode/drafter.py).
At the end of CUDAGraph capture the runner runs each one once per captured batch
size, so the per-shape JIT — aiter’s flydsl builds an hgemm per tile config,
in-process — is paid at startup instead of stalling a serving step. At serve
time a pass runs at the batch the target just ran, which ForwardMode.decide
picks out of those same capture_sizes — that is what makes a warmed shape and a
reachable shape one set rather than two lists that drift. A pass must support capture; the switch below then decides whether its warmup also records. V4.1 DSpark currently warms and drafts eagerly because its request windows and expert dispatch are outside the whole-block capture contract. Its target supports PIECEWISE tensor-stage graphs.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DRAFT_CUDAGRAPH |
bool |
1 (true) |
Capture each declared draft pass into a per- |
DSpark drafting#
The Kimi-K3 DSpark draft writes the target’s context rows into its own paged MLA cache once per draft layer per drafting step; the switch below shortens that path. The first write of each process logs which path it took, and logs again if that ever changes, so a fusion left inert by an unrecognised layout says so.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DSPARK_FUSED_CTX_KV |
bool |
1 (true) |
Write the context rows with one Triton kernel (RMSNorm + RoPE + concat + paged store) instead of four launches plus a throwaway |
ATOM_DSPARK_DISABLE_COMPILE |
bool |
0 (false) |
Run the DSpark draft eager while the target stays compiled. Prefer it over |
Speculative acceptance#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_ENABLE_RELAXED_MTP |
bool |
0 (false) |
Accept a draft token when it lands in the target’s top 10 within 0.6 of the top logit, instead of requiring the argmax. Intended for quantized MTP heads, whose drafts are right about the region and wrong about the exact winner often enough that strict acceptance throws away usable tokens. Read once at |
Engram (DeepSeek-V4.1)#
The n-gram tables are per-layer and large enough that where they live, and whether they are rebuilt, both show up at startup. Both switches below are all-or-nothing on purpose: a half-registered set would keep the host path for some layers and the device path for others, which is the confusing state.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_ENGRAM_UVA |
bool |
1 (true) |
Page-lock this rank’s shard of the hash tables in place and let a device kernel read the rows it needs across the bus, dequantizing there. No copy and no HBM for the table. |
ATOM_ENGRAM_CACHE_DIR |
path |
|
Where the compressed-vocab table is cached between runs. The table is reproducible from the tokenizer, so this only trades startup time for disk; point it at shared storage to let several servers build it once. A truncated or stale cache is rebuilt rather than raised. |
Attention side streams (DeepSeek-V4.1)#
A layer’s compressor reads the hidden row and its own arena state, and its indexer reads the normed query latent; neither reads what the query projection and the fused rope/window launch produce, so the three are branches of one dependency graph that a single stream serializes.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DSV41_SIDE_STREAMS |
int (0/1/2) |
0 |
How many of a layer’s branches leave the main stream. 0 — none. 1 — the compressor, on the MoE’s |
Level 0 is the default because forking measured slower, not faster. Median steady-state layer period, MI355X TP4 bf16 KV DSpark-5, 1024/1024 at concurrency 64, ~10k sampled layers per trace:
level |
layer period |
vs 0 |
|---|---|---|
0 |
636.40 µs (repeat: 636.72) |
— |
1 |
645.76 µs |
+1.47% |
2 |
640.32 µs |
+0.62% |
The anchor is the gap between consecutive topk_gating launches, so a branch
that moves to another stream cannot drop out of the sample. The two level-0
traces were taken either side of the other two, which puts the floor — session
drift included — at 0.05%, making those deltas 12× and 29× the noise; the
unfiltered medians (604.0/605.9 against 618.8 and 607.6) order the levels the
same way. End-to-end throughput cannot resolve this: one wall-clock number per
run carries 2–6% spread, which is why an earlier A/B called the same
arrangement a wash. Whoever revisits it should start from these numbers and
from GPU_MAX_HW_QUEUES, not from where the forks sit.
V4 attention backend (Migration)#
Selects between the legacy per-seq Python dispatch path in atom/models/deepseek_v4.py
and the new batched V4AttentionBackend (atom/model_ops/v4_attention_backend.py).
The new backend removes ~256 GPU→CPU .item() syncs per forward and is required
to enable CUDAGraph capture for V4. Legacy stays available during PR-A migration
for byte-equal A/B verification via dump-bisect; it is removed once all phases
land. See atom/model_ops/v4_backend_gate.py for the selector.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_V4_BACKEND |
str |
|
|
ATOM_V4_BACKEND_LAYERS |
csv int |
“” (= all) |
Comma-separated layer ids that use the new backend (others stay legacy). Empty means: apply |
State checkpoints#
For models carrying per-request recurrent state (GDN: Qwen3-Next / Qwen3.5;
Kimi-K3’s KDA; DeepSeek-V4’s compressor ring), a checkpoint lets a later prefix
hit resume mid-prompt instead of recomputing from zero. Where they are placed
is a policy, set by --state-checkpoint-interval-tokens (three regimes carried
by the sign — see the configuration guide) and the
flag below. Details in the state-checkpoint section of the
scheduling & KV cache guide.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_STATE_CHECKPOINT_DEMAND |
bool |
1 (true) |
Set to |
LMCache offload tier#
ATOM’s own offload knobs are defined in atom/utils/envs.py; their behavior is
documented in atom/kv_transfer/offload/README.md. Where a
kv_connector_extra_config key also exists, it takes precedence over the env
var. LMCACHE_EC_PIN_TIMEOUT_SEC belongs to LMCache and is read only to
derive a bound.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_KV_OFFLOAD |
str |
“” (off) |
Enables LMCache KV offload without |
ATOM_KV_OFFLOAD_EXTRA_CONFIG |
JSON object |
“” |
The offload connector’s |
OFFLOAD_COPY_WORKERS |
int |
1 |
Save executor threads per offload worker. Also scales the default |
OFFLOAD_LOAD_WORKERS |
int |
1 |
Load executor threads per offload worker. DSV4’s in-process path ignores it (its SLOT load path needs a serial load executor). |
OFFLOAD_MAX_PENDING_SAVES |
int |
unset: |
Bound on running-plus-queued saves. KV and state saves share it because both pin the same pool. A non-integer raises on the connector path and warns (using 2) on the state-tier path. Overridden by |
OFFLOAD_MIN_LOAD_TOKENS |
int |
8192 |
Smallest external-tier hit worth loading; shorter hits are recomputed. Negative values clamp to 0. |
OFFLOAD_MIN_SAVE_TOKENS |
int |
8192 |
Shortest prefix native |
OFFLOAD_LOOKUP_MEMO_STEPS |
int |
32 |
Scheduler steps a memoised tier-lookup answer is replayed before the tier is asked again. |
OFFLOAD_LOOKUP_RETRY_STEPS |
int |
32 |
Scheduler steps a failed tier lookup suppresses the next attempt. |
OFFLOAD_ASYNC_LOOKUP_DEPTH |
int |
16 |
|
OFFLOAD_LOOKUP_DEFER_S |
float |
2.0 |
How long |
OFFLOAD_PROFILE |
bool |
0 |
Emit |
OFFLOAD_SINGLE_STREAM |
bool |
0 |
Experimental: run the staging pack and copy legs on one stream. |
OFFLOAD_GPU_STAGING_CHUNKS |
int |
derived from KV geometry |
GPU staging buffer size in LMCache chunks (≥ 1). |
OFFLOAD_GPU_STAGING_MAX_BYTES |
int |
unset |
Upper bound on the GPU staging buffer in bytes; must hold at least one chunk. |
OFFLOAD_RELEASE_GPU_STAGING_AFTER_TRANSFER |
bool |
0 |
Free the GPU staging buffer after each transfer instead of keeping it. |
OFFLOAD_SLOT_STAGING_SLOTS |
int |
1 |
DSV4 in-process SLOT sidecar staging rows. Overridden by |
OFFLOAD_COMMITTED_SIDECAR_CAPACITY |
int |
65536 |
DSV4 in-process committed SLOT sidecar index capacity. Overridden by |
OFFLOAD_PUBLICATION_TIMEOUT_S |
float |
5.0 |
DSV4 in-process wait for a saved SLOT sidecar to become visible (finite, ≥ 0). |
OFFLOAD_PUBLICATION_POLL_INTERVAL_S |
float |
0.01 |
Poll period of that wait (finite, > 0). |
LMCACHE_MP_TRANSFER_MODE |
str |
auto |
|
LMCACHE_EC_PIN_TIMEOUT_SEC |
float |
LMCache’s own (300) |
LMCache’s source-pin timeout. ATOM reads it only to derive the engine’s save-abandon window ( |
KV cache events#
The scheduler can publish prefix-cache changes (BlockStored, BlockRemoved,
AllBlocksCleared, and BlockStored(medium=REMOTE) for KV received from a
PD producer) over ZMQ so external routers and cache managers can mirror what
each engine holds. Every batch carries a monotonic 8-byte sequence number;
a consumer that sees a gap can ask for the missed batches over the optional
replay socket. Events are advisory and never stall inference: the in-process
queue drops the oldest batch when full. These variables are the only way to
configure the feature today: they build KVEventsConfig (see atom/config.py)
and there is no CLI flag.
Endpoint rules: each publisher binds its PUB and replay endpoints offset by its
data-parallel rank (see ATOM_KV_EVENTS_ENDPOINT), so with tcp:// the two
configured ports must be at least data_parallel_size apart or rank N’s PUB
lands on rank N-1’s replay port. Under pipeline parallelism only the head stage
publishes; downstream stages bind nothing. Prefill/decode disaggregation runs
separate engine processes, and only decode publishes; if other engines share
the host, give each its own endpoints.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_KV_EVENTS_ENABLE |
bool |
0 (false) |
Set to |
ATOM_KV_EVENTS_PUBLISHER |
str |
|
|
ATOM_KV_EVENTS_ENDPOINT |
str |
|
ZMQ PUB bind address. Under data parallelism every rank binds its own socket: |
ATOM_KV_EVENTS_TOPIC |
str |
|
Subscription topic prefix sent as the first frame of every message. |
ATOM_KV_EVENTS_HWM |
int |
0 |
ZMQ high-water mark on the PUB socket (0 = unlimited). |
ATOM_KV_EVENTS_BUFFER_STEPS |
int |
10000 |
Depth of the in-process queue between the scheduler and the sender thread. When full, the oldest batch is dropped and counted in the publisher’s |
ATOM_KV_EVENTS_REPLAY_ENDPOINT |
str |
|
ZMQ ROUTER bind address for replay requests. Empty disables replay (PUB-only). A consumer sends an 8-byte big-endian start sequence and receives every retained batch with |
ATOM_KV_EVENTS_REPLAY_BUFFER_STEPS |
int |
10000 |
Number of most recently sent batches retained for replay. Independent of |
Shutdown#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_SHUTDOWN_TIMEOUT_S |
float |
5 |
Seconds that shutdown waits for child processes to exit on their own before terminating them (SIGTERM, then SIGKILL): the server process waits this long for its EngineCores, and each EngineCore for its ModelRunner workers. All the children of one process share the same deadline. Must be finite and positive. Raise it when workers need longer to finish on exit, for example to write a profiler’s output ( |
Profiling & debugging#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_METRICS_UPDATE_INTERVAL_S |
float |
1.0 |
Shared interval in seconds for ordinary/DP/PP engine metrics pushes and API snapshot refresh. Must be finite and positive; read when each loop starts, so set it before starting every service process. Prometheus scraping is configured independently. Does not cache rendered |
ATOM_ENABLE_METRICS_DEVICE_TIMER |
bool |
0 (false) |
Set to |
ATOM_TORCH_PROFILER_DIR |
str |
— |
When set, enables PyTorch profiler and writes traces to this directory. Create subdirectories per rank (e.g., |
ATOM_PROFILER_MORE |
bool |
0 (false) |
When |
ATOM_PROFILER_RECORD_SHAPES |
bool |
|
Set to |
ATOM_PROFILER_WITH_STACK |
bool |
|
Set to |
ATOM_PROFILER_PROFILE_MEMORY |
bool |
|
Set to |
ATOM_ENABLE_DETAILED_ANNOTATION |
bool |
0 (false) |
When profiling is active, appends detailed attention aggregates to the |
ATOM_LOG_MORE |
bool |
0 (false) |
If set to |
Garbage collection#
CPython’s generation-2 pass is stop-the-world and walks every tracked
container, so its cost tracks the live heap — which in a serving process is
almost entirely startup state (model, compiled graph, tokenizer, KV block
pool) that is never garbage. Measured on DeepSeek-V4-Flash-DSpark tp1: 242.8 ms
in the EngineCore, up to 596 ms in a ModelRunner worker, while reclaiming zero
objects once startup was done. See atom/utils/gc_utils.py.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_GC_FREEZE |
bool |
1 (true) |
Move the startup heap into CPython’s permanent generation once warmup is done, so collections stop scanning it. Applied in every process that outlives startup — the API server, the atomesh frontend, every EngineCore and every ModelRunner worker; undone on engine shutdown so an in-process teardown does not leak. Set |
ATOM_GC_DEBUG |
bool |
0 (false) |
Log every collection: generation, duration, objects reclaimed, objects tracked. Costly — counting the tracked set on every pass added ~90s of startup on a V4-Flash tp1 — but the only way to see these pauses, since a stall in the EngineCore idles the workers with no event in their torch trace. |
ATOM_GC_THRESHOLD |
csv int |
“” (= CPython default 700,10,10) |
|
Raising a process’s thresholds is free only while its collector keeps finding
nothing to free, which is a property of that one process, so it is exported
rather than assumed. All three are wired into the API server only; the engine
and worker processes serve no /metrics, so ATOM_GC_DEBUG=1 is what reads
them there.
atom:gc_collected_total(/metrics, per generation —prometheus_clientappends the_total) is the invariant as a series. Flat after startup is the expected shape; a rising line means the process has started building reference cycles, and spacing its collections out would defer real work into a growing heap.atom:gc_collections_total,atom:gc_uncollectable_totalandatom:gc_thresholdsit beside it for context. All are O(1) reads taken at scrape time, which is a bound and not a preference:/metricsrenders on the loop that delivers every stream. The frozen count is deliberately not here —gc.get_freeze_count()walks the permanent generation (11.9 ms at 430k frozen, the cost freezing exists to remove) for a number that changes twice in a process’s life. The startup log has it, and so does the census.reclaim_watchlogs one warning — once, not per check — if that line ever rises, because a counter nobody looks at is not a safeguard. It sees only what a collection reclaimed, so cyclic garbage that reaches gen-2 before it dies is invisible to it where gen-2 passes are rare; the gen-2 size in/debug/gc_censusis what shows that.GET /debug/gc_censusbreaks the scanned set down by type and by owning library. Unlike the metrics it walks every tracked object (~1s at a million), so it is asked for, never scraped, and it runs in a worker thread rather than on the loop that delivers the streams. It reports counts only: naming what a container holds would mean serialising the keys of parsed request bodies into an unauthenticated response.topandtypes_per_ownerbound the two breakdowns.
Incremental detokenizer#
Streaming decodes each delta from two tokenizer.decode calls that share a
window start, so subtracting one from the other isolates the new text without
emitting a half-formed UTF-8 character. These two settings govern the shortcut
that removes one of those calls. Nothing here is related to the KV prefix
cache, which is what “cache” means everywhere else in this repository.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DETOKENIZER_DELTA_REUSE |
auto | on | off |
auto |
Whether the incremental detokenizer may reuse the delta it last emitted in place of one of its two |
ATOM_DETOKENIZER_AUDIT_EVERY |
int |
1000 |
How often a reused delta is checked against a real decode once reuse is on. A mismatch answers that call from the decode, then turns reuse off for the process and logs once. Only audited calls are checked, so at the default up to 999 deltas can ship between a tokenizer starting to disagree and the audit that notices it; |
Debug dump (atom.utils.debug_helper)#
Env-gated dump / compare / monkey-patch primitives for forward bisect &
batch invariance investigation. All entries are no-op when their
controlling *_DIR is unset, so they are safe to leave wired into
production paths. See .claude/skills/dump-bisect-debug.md for the
methodology and atom/utils/debug_helper/ for the implementation.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_FWD_DUMP_DIR |
str |
— |
Enables |
ATOM_FWD_DUMP_LAYERS |
csv int |
“” (= all) |
Comma-separated layer ids to dump (e.g. |
ATOM_FWD_DUMP_BLOCK_CLASS |
csv str |
|
Module class names to hook. Multiple values supported (e.g. |
ATOM_FWD_DUMP_LAYER_ATTR |
str |
|
Attribute name on the block carrying its index. Some non-DeepSeek models use |
ATOM_FWD_DUMP_ONE_SHOT |
bool |
1 (true) |
When |
ATOM_WEIGHT_DUMP_DIR |
str |
— |
Enables |
ATOM_WEIGHT_DUMP_LAYERS |
csv int |
|
Comma-separated layer ids to dump weights for. |
ATOM_WEIGHT_DUMP_EXIT |
bool |
1 (true) |
When |
ATOM_DEBUG_TOPK |
int |
0 |
Set to |
ATOM_DEBUG_TOPK_PATH |
str |
— |
Optional output file for top-K logs. Writes to stderr if unset. |
CLI for comparing dumps:
python -m atom.utils.debug_helper.compare slot-invariance --dir DIR --n-slots 4
python -m atom.utils.debug_helper.compare ref-vs-target --dir DIR
python -m atom.utils.debug_helper.compare layer-bisect --dir DIR --threshold 0.99
python -m atom.utils.debug_helper.compare schema --a A.pt --b B.pt
Benchmarks (optional)#
Variable |
Type |
Default |
Description |
|---|---|---|---|
OPENAI_API_KEY |
str |
— |
API key for OpenAI-compatible benchmark requests. |
VLLM_USE_MODELSCOPE |
bool |
false |
If set to |
SAVE_TO_PYTORCH_BENCHMARK_FORMAT |
bool |
false |
If set, save benchmark results in PyTorch benchmark format. |
Internal / Set by ATOM#
The following variables are set internally by ATOM; users typically do not need to configure them:
Variable |
Description |
|---|---|
AITER_QUICK_REDUCE_QUANTIZATION |
Set to |
TORCHINDUCTOR_CACHE_DIR |
Set by compiler interface for inductor cache. |
TRITON_CACHE_DIR |
Set by compiler interface for Triton cache. |
Reference#
Environment variables are defined and accessed via atom.utils.envs:
from atom.utils import envs
# Example: check data parallel size
dp_size = envs.ATOM_DP_SIZE
See atom/utils/envs.py for the full list of lazy-evaluated environment variables.
Mooncake PD matched rails#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_MOONCAKE_MATCHED_RAILS |
str |
“” |
Use |
See Mooncake matched rails for independent P/D rank configuration, deployment requirements, and registration lifetime.
Mooncake PD MLA staging#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_PD_MLA_STAGING |
bool |
1 |
Prefill ( |
ATOM_PD_FAILED_WRITE_HOLD_S |
float |
60 |
Mooncake P/D. Mooncake gives up on a sync RDMA write at |
Mooncake PD MLA landing#
Set on the decode side; the prefill side follows what each write request offers and needs MLA staging enabled.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_PD_MLA_LANDING |
bool |
1 |
Decode ( |