ATOM Environment Variables#
This document describes the environment variables used in the ATOM project.
OpenTelemetry tracing#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_ENABLE_OTEL_TRACE |
bool |
false |
Enable standalone request tracing with |
ATOM_TRACE_VERSION |
str |
|
Exact build version attached to each tracing resource as |
The standard OTEL_SERVICE_NAME and OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
variables configure service identity and delivery. See tracing setup and
limitations.
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 |
ATOM_DP_PREFILL_BALANCE_TOKEN_WEIGHT |
int |
360000 |
DP prefill cost balancing (always on under DP attention with more than one rank; needs the prefill delayer): each lockstep prefill step aims at min(costliest, mean x (1 + slack)) of the ranks’ estimated chunk costs, and a costlier rank takes fewer tokens. This is the per-token term of the chunk cost, in attended-context tokens: cost = new tokens x (weight + prior context + new / 2). |
ATOM_DP_PREFILL_BALANCE_SLACK |
float |
0.1 |
The target cost is at most mean x (1 + slack) over the ranks that prefill. |
ATOM_DP_PREFILL_BALANCE_MIN_TOKENS |
int |
4096 |
Fewest tokens a shrunk rank still prefills in a step. |
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), including while some DP rank has no prefill. Values |
ATOM_PREFILL_DELAYER_COST_GATE |
bool |
true |
With DP prefill cost balancing (DP attention, DP > 1): while some DP rank has no prefill, also release when a held decode step is not expected to pay off (request arrival rate on the ranks without prefill × the decode stall of one unaligned prefill step < waiting requests), or once the held requests have waited (in measured decode steps) as long as that stall. Step times and arrival rates are measured per rank and combined over the DP group; until every rank has timed both steps, only the stall bound releases. |
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). |
Dynamic chunking#
Interpolates pipeline-parallel chunk sizes between the configured batch budget
and the equal-latency size solved from the calibrated chunk model. Used when
neither --dynamic-chunking-smooth-factor nor Config.dynamic_chunking_smooth_factor
is given, and read only when dynamic chunking is enabled, so a malformed value
cannot affect a server that does not use the feature.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_DYNAMIC_CHUNKING_SMOOTH_FACTOR |
float |
0.75 |
Blend from the initial chunk ( |
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_TOKENIZE_CACHE_MB |
int |
512 |
Host memory, in MB, the API server keeps for reusing prompt tokenization across requests. A chat template separates messages with added (special) tokens, and the text between two of them tokenizes the same on its own. Each prompt is kept with its ids up to its last added token, so the next turn of the conversation reuses them up to the last added token both share; the rest is split at added tokens and each segment’s ids are cached by content, the missing ones encoded in one parallel batch. Both are exact: they are used only when the tokenizer’s added tokens, normalizer and pre-tokenizer allow it, and the first prompts are checked against the plain encoder (any difference turns the cache off). Prompts under 100K characters are encoded whole. One budget covers segment ids and stored prompts, counted by the bytes they retain (ids, text, per-entry overhead). The result still covers the whole prompt, so a request costs time linear in its length; what is saved is re-encoding text already encoded. Lowers TTFT on long multi-turn conversations; ITL and throughput are unaffected. |
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 |
ATOM_V4_CG_INSTR_DIR |
str |
— (off) |
vLLM plugin, DeepSeek-V4 diagnostic: directory for per-process cudagraph dispatch counters (dispatched modes, steps whose uniform decode was overridden for a still-prefilling row, guard trips), one |
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_DSV41_MHC_BACKEND |
str |
|
V4.1 delayed post/pre/RMSNorm backend, selected after checkpoint loading. |
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) and partition ceiling (CU count), except at head_dim 256 (Qwen3.5), where they use 64 partitions and a 256-workgroup budget to keep aiter’s head-256 reducer on its fast path (until aiter #5949). 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. Other 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, |
ATOM_SPARSE_INDEXER_LOGITS_BUDGET_MB |
int |
2048 |
Sparse (DSA) indexer dense fp32 logits budget, in MiB. Prefill scores query rows in bands within it. It also sizes the decode logits workspace ( |
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. |
Native model decode#
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_MONO_ENABLE |
bool |
1 (true) |
Enable fused decode for supported GLM-5.2, Kimi-K3, MiniMax-M3, and DeepSeek-V4.1 deployments and step geometries. Unsupported configurations and forwards keep the production path; |
MiniMax-M3#
Variable |
Type |
Default |
Description |
|---|---|---|---|
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 |
ATOM_GLM52_FUSED_INDEXER |
bool |
0 (false) |
GLM-5.2 only. Moves the sparse indexer inside the mono decode launch on the 18 “full” layers that actually select: index Q/K/W projection, the paged FP8 index-cache update, scoring and the exact top-2048 all run in one kernel instead of |
ATOM_GLM52_INDEXER_CP |
bool |
0 (false) |
GLM-5.2 only, and strictly narrower than |
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.
GLM-5.2 (atom/models/glm52/mono/): the MoE suffix of the layers, at decode widths of 1 token (no speculation) or an MTP verify of 4, 5 or 6, batched 1 or 2. It needs TP4, ptpc-FP8 attention linears, fp8 KV, ATOM_MLA_PAGE_SIZE=1, and no DP attention / plugin mode / shuffled MLA cache. Unlike DeepSeek-V4.1 and MiniMax-M3 it does not refuse TR_HYBRID: it routes inside the uncompiled outer GlmMoeDsaForCausalLM and bypasses the compiled inner DeepseekV2Model entirely, so a token graph of that module never runs underneath mono and cannot conflict with it (common_refusals(..., refuse_tr_hybrid=False)). TR_HYBRID — which a config with no cudagraph_mode resolves to at level 3 (Config.__post_init__; the CLI itself defaults --cudagraph-mode to FULL) — is accepted, as is FULL and eager. By default the indexer runs outside the kernel (_refresh_indexer); ATOM_GLM52_FUSED_INDEXER=1 moves it inside the launch on the 18 full layers, with the extra requirements listed in the table above, and ATOM_GLM52_INDEXER_CP=1 additionally shards its scoring across the four ranks. Which one a width ended up on is named in the GLM-5.2 MonoKernel on: S=.. chunk=.. indexer=.. line, not inferred.
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_STAGE2_FUSED |
bool |
1 (true) |
Under |
ATOM_MEGA_STAGE1_FUSED |
bool |
1 (true) |
MegaMoE compact stage 1 (aiter |
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 |
1 (true) |
By default, the MegaMoE backend ( |
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 ( |
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 |
ATOM_QK_NORM_ROPE_CACHE_FUSION_MAX_TOKENS |
int |
8192 |
With the fusion above on, Gemma-norm models (Qwen3-Next, Qwen3.5) run the q/k norm, RoPE and cache write as separate kernels when a batch has more tokens than this; the fused kernel is slower at prefill sizes. |
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.
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 |
ATOM_TOKEN_GRAPH_SIZES |
str |
“256,512,1024,2048,3072” |
Prefill token buckets to record as TR graphs ( |
ATOM_TOKEN_GRAPH_DP_MAX_TOKENS |
int |
512 |
Under DP attention with a drafter, the largest decode token bucket recorded as a TR_HYBRID token graph; larger |
ATOM_DECODER_GRAPH_SIZES |
str |
“1,2,3,4,8,16” |
The |
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 |
Prefix cache lookup#
All three default on and move work off the scheduler thread.
ATOM_FRONTEND_PREFIX_HASH=0 and ATOM_PREFIX_WALK_MEMO=0 give the same hits.
ATOM_PREFIX_FAST_WALK=0 does not always: the fast walk compares tokens only on the last hit block, so a
64-bit xxhash chained-hash collision on an interior block reuses another
prompt’s KV with no error (wrong output). 0 compares every hit block’s tokens
and catches it; set it when chasing a corruption that does not reproduce.
Variable |
Type |
Default |
Description |
|---|---|---|---|
ATOM_FRONTEND_PREFIX_HASH |
bool |
true |
Hash the prompt’s block chain while the request is built (API server preprocessing executor) and ship it to the engine with a fingerprint of the prompt tokens, beside the request in the ADD message (never inside a pickled |
ATOM_PREFIX_FAST_WALK |
bool |
true |
Look the prompt’s chain up with C-speed |
ATOM_PREFIX_WALK_MEMO |
bool |
true |
Reuse a waiting request’s prefix walk across the probes that run every scheduler tick: the delayer’s head check and probe, and the DP chunk-cost estimate ( |
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 |
ATOM_STATE_CHECKPOINT_END_MARGIN |
int |
0 |
Minimum number of prompt tokens left after the prompt-end anchor. The anchor goes on the last hash-block boundary at least |
ATOM_DSV4_STATE_REPLAY |
bool |
0 (false) |
DeepSeek-V4: keep a prefix hit that reaches past the nearest state checkpoint by more than the window’s receptive field (7,748 tokens on V4-Pro, 7,875 with MTP or DSpark) and recompute only that field below it, which rebuilds the state exactly, instead of resuming at the checkpoint ( |
ATOM_STATE_CHECKPOINT_INTERVAL_TOKENS |
int |
— (the model’s native interval) |
vLLM plugin (DeepSeek-V4 state checkpoints): overrides the checkpoint interval in tokens, snapped down to a multiple of the block size. |
ATOM_STATE_CHECKPOINT_CHECK_BLOCKS |
bool |
0 (false) |
vLLM plugin (state checkpoints) diagnostic: after every schedule, raise if a request holds more than two blocks in any state checkpoint group. |
ATOM_GDN_STATE_MIDSTEP |
bool |
0 (false) |
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 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_SKIP_LOOKUP_ON_HBM_HIT |
bool |
1 (true) |
On by default; set to |
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. Unset, |
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 ( |