Environment Variables¶
Spyre Inference reads the following SPYRE_* environment variables to configure
the plugin. Each is evaluated the first time it is read, so set it before
launching vLLM. The list below is generated from spyre_inference/envs.py; the
comment above each entry documents its effect and default.
environment_variables: dict[str, Callable[[], Any]] = {
# Comma-separated Spyre device indices to run on, mapping each tensor-parallel
# local rank to a physical card. Unset lets the runtime pick the default device(s).
"SPYRE_DEVICES": lambda: os.getenv("SPYRE_DEVICES"),
# Which model runner the worker uses:
# - "v1": the V1 runner (default)
# - "v2": the Model Runner V2 port (#906), in bring-up and not yet functional. On
# x86 it runs MRV2's Triton kernels on triton-cpu.
"SPYRE_MODEL_RUNNER": lambda: os.getenv("SPYRE_MODEL_RUNNER") or "v1",
# Granularity of the decoder's torch.compile graph:
# - "block": compile one transformer block at a time (default)
# - "model": compile the whole model as a single graph
"SPYRE_COMPILE_GRANULARITY": lambda: os.getenv("SPYRE_COMPILE_GRANULARITY") or "block",
# What to do when a model block, attention kernel or the lm_head compiles *after*
# warmup, which costs a full Inductor compile mid-request:
# - "off": nothing
# - "warn": log each distinct violation (default)
# - "error": raise. Use in CI to keep a warmup-coverage regression from landing,
# bearing in mind it catches most but not all: torch runs its compile-start
# callbacks only when a process-wide pending counter goes 0 -> 1, so a compile
# starting while another is in flight goes unreported.
# torch-spyre compiles every eager aten op, so those compiles continue for the
# whole run; they are never reported.
"SPYRE_COMPILE_GUARD": lambda: os.getenv("SPYRE_COMPILE_GUARD") or "warn",
# When "1", wrap attention forward/softmax in torch.profiler.record_function
# spans for kineto trace capture. Off by default: profiled runs are not
# wall-clock comparable.
"SPYRE_ATTN_PROFILING": lambda: bool(int(os.getenv("SPYRE_ATTN_PROFILING", "0"))),
# When "1" (default), pre-compile every attention variant the run can need during
# warmup, so no request pays an Inductor compile mid-serving. "0" falls back to
# compiling each variant lazily on first use.
"SPYRE_ATTN_RECORD": lambda: bool(int(os.getenv("SPYRE_ATTN_RECORD", "1"))),
# When "1", paged attention walks KV pages and batched decode walks logical
# block chunks with torch-spyre's `for_each_tile`, so each traced graph holds
# one loop body. Enabled by default; "0" runs the same bodies under Python loops.
"SPYRE_ATTN_FOR_EACH_TILE": lambda: bool(int(os.getenv("SPYRE_ATTN_FOR_EACH_TILE", "1"))),
# Comma-separated kv_len buckets to record, unset uses the SPYRE_ATTN_KV_LADDER preset.
"SPYRE_ATTN_KV_BUCKETS": lambda: os.getenv("SPYRE_ATTN_KV_BUCKETS"),
# Named KV bucket ladder: pow2 (default), 8_5, 4_3, 9_8 or uniform.
"SPYRE_ATTN_KV_LADDER": lambda: os.getenv("SPYRE_ATTN_KV_LADDER") or "pow2",
# A denser preset for batched decode at SPYRE_ATTN_LARGE_BATCH_MIN_SEQS sequences or
# more, unioned with SPYRE_ATTN_KV_LADDER. Unset keeps one ladder for every batch size;
# rejected together with SPYRE_ATTN_KV_BUCKETS.
"SPYRE_ATTN_KV_LADDER_LARGE_BATCH": lambda: (
os.getenv("SPYRE_ATTN_KV_LADDER_LARGE_BATCH") or None
),
"SPYRE_ATTN_LARGE_BATCH_MIN_SEQS": lambda: int(
os.getenv("SPYRE_ATTN_LARGE_BATCH_MIN_SEQS") or "16"
),
# Comma-separated query_len buckets to record, unset uses the default buckets
# [1] + multiples of min(512, max_num_batched_tokens) up to max_num_batched_tokens.
"SPYRE_ATTN_QUERY_BUCKETS": lambda: os.getenv("SPYRE_ATTN_QUERY_BUCKETS"),
# Comma-separated num_seqs buckets for the batched decode kernel, unset uses the
# default buckets of powers of two from 1 up to max_num_seqs.
"SPYRE_ATTN_NUM_SEQS_BUCKETS": lambda: os.getenv("SPYRE_ATTN_NUM_SEQS_BUCKETS"),
# Core cap for the attention compile only, leaving the rest of the model on all 32.
# "0" (default) lets the LX path pick its own cap and leaves the others uncapped.
"SPYRE_ATTN_MAX_CORES": lambda: int(os.getenv("SPYRE_ATTN_MAX_CORES", "0")),
# When "1" (default), enables the batched multi-sequence decode kernel.
"SPYRE_BATCHED_DECODE": lambda: bool(int(os.getenv("SPYRE_BATCHED_DECODE", "1"))),
# When "1" (default), a decode batch spanning several KV buckets runs one batched
# dispatch per bucket group instead of padding every sequence to the longest.
"SPYRE_JAGGED_DECODE": lambda: bool(int(os.getenv("SPYRE_JAGGED_DECODE", "1"))),
# When "1" (default), build each linear layer's transposed weight on the host in
# blocks of source rows. A plain `w.t().contiguous()` copies on one thread; the
# blocked copy runs on torch's CPU threads and cuts model load time. Bit-identical.
# Set to "0" when torch runs far more threads than the CPUs it gets (e.g. with
# SPYRE_UPDATE_THREAD_CONFIG=0 in a CPU-limited pod): there it is slower.
"SPYRE_BLOCKED_TRANSPOSE": lambda: bool(int(os.getenv("SPYRE_BLOCKED_TRANSPOSE", "1"))),
# When "1", reuse compiled Spyre kernels across processes by caching them on
# disk. Off by default. TORCHINDUCTOR_FORCE_DISABLE_CACHES=1 disables the cache
# even when this flag is enabled.
"SPYRE_KERNEL_CACHE": lambda: os.getenv("SPYRE_KERNEL_CACHE", "0") == "1",
# When "1", each TP rank copies only its vocab shard of the logits to the host and the
# shards are gathered on the CPU. Needs OMP_WAIT_POLICY=PASSIVE (spinning OpenMP
# threads otherwise slow the next step).
"SPYRE_LOGITS_CPU_GATHER": lambda: os.getenv("SPYRE_LOGITS_CPU_GATHER", "0") == "1",
# Maximum number of sequences allowed to prefill in the same batch. "1" (default)
# serialises prefills, so a batch spends the whole token budget on one prompt
# instead of topping itself up with a short chunk of the next. Any non-positive
# value removes the cap, as does a pooling runner, which never decodes.
"SPYRE_MAX_NUM_PARTIAL_PREFILLS": lambda: int(os.getenv("SPYRE_MAX_NUM_PARTIAL_PREFILLS", "1")),
# Largest packed-token count handled by a compiled loop of single-token gathered kernels.
# Larger batches use one all-expert kernel because its fixed weight-read cost is amortized.
# "8" (default) is measured on gemma-4-26B-A4B: gathered wins up to 8 at TP1 and TP4.
"SPYRE_MOE_GATHERED_MAX_TOKENS": lambda: int(os.getenv("SPYRE_MOE_GATHERED_MAX_TOKENS", "8")),
# Override the automatically selected hidden-axis chunk count for gathered MoE. Unset
# derives it from top_k, stick alignment and the core count.
"SPYRE_MOE_CHUNKS": lambda: (
int(value) if (value := os.getenv("SPYRE_MOE_CHUNKS")) is not None else None
),
# CPU budget used to size thread pools. "0" (default) auto-detects the budget
# (cgroup CPU quota, then physical core count).
"SPYRE_NUM_CPUS": lambda: int(os.getenv("SPYRE_NUM_CPUS", "0")),
# When "1" (default), clamp the CPU threading env vars (OMP_NUM_THREADS and
# friends) to the detected budget to avoid thread oversubscription in
# CPU-limited containers. Set to "0" to leave them untouched and only warn.
"SPYRE_UPDATE_THREAD_CONFIG": lambda: bool(int(os.getenv("SPYRE_UPDATE_THREAD_CONFIG", "1"))),
}