Skip to main content

Loading models

One entry point, a raw HuggingFace repo id, and a backend=.

BackendVRAMSpeedPointsSteering
vllmlowmediumany, chosen per requestyes
vllm-statichighhighonly those declaredyes
vllm-generatehighfastestnoneno
eagerlowlowany, including eager-onlyyes

vLLM (default)​

from interp_engine import load_model

model = load_model("Qwen/Qwen3-8B", backend="vllm")

vLLM static​

Taps are baked into CUDA graphs at load, so the points come first.

from interp_engine import Address, capture, load_model

point = Address("resid_post", 10)
model = load_model("Qwen/Qwen3-8B", backend="vllm-static", static_points=[point])

cache = capture(model, model.to_tokens("Hello, world"), [point])

static_points="auto" gives resid_post at every layer. static_writes= declares the points you will steer.

from interp_engine import load_model

model = load_model(
"Qwen/Qwen3-8B",
backend="vllm-static",
static_points="auto",
static_writes=["resid_post.10"],
)

Any point outside the declared set is refused rather than served slowly. A different set is a reload.

vLLM generate-only​

Fastest decode, no capture and no steering.

from interp_engine import load_model

model = load_model("Qwen/Qwen3-8B", backend="vllm-generate")

Eager​

attn_implementation="eager" is what makes attn_probs and attn_scores readable.

from interp_engine import load_model

model = load_model(
"google/gemma-2-2b-it",
backend="eager",
device="cuda",
dtype="bfloat16",
attn_implementation="eager",
)

Gradients through the forward are eager-only, and opt-in:

from interp_engine import load_model

model = load_model("google/gemma-2-2b-it", backend="eager", requires_grad=True)

Quantize on load​

A bf16 checkpoint can be narrowed as it loads, with no calibration step and no second repo. quantization="fp8" halves every linear layer on the vLLM backends; "bnb-4bit" quarters them on either backend; "bnb-8bit" is eager-only. The embeddings stay at the model dtype. On vLLM, kv_cache_dtype="fp8" halves the paged KV cache as well, independently.

from interp_engine import load_model

model = load_model(
"meta-llama/Llama-3.3-70B-Instruct",
backend="vllm",
quantization="fp8",
kv_cache_dtype="fp8",
num_gpus=2,
)

A scheme the backend cannot apply is refused with the alternative named: fp8 on eager says to use a vLLM backend or a repo that ships in FP8. The GPU sizer prices both knobs and prints the snippet with the arguments it priced.

Auto​

backend="auto" is the default: vLLM on CUDA where the architecture supports it, otherwise eager on CUDA, MPS or CPU. It never picks vllm-static.

from interp_engine import load_model, vllm_installed

vllm_installed() # False on an eager-only install
model = load_model("Qwen/Qwen3-8B") # warns if it falls back to eager

Ask before loading, when you need the resolved device and dtype first:

from interp_engine import select_backend, vllm_installed

selection = select_backend(
"Qwen/Qwen3-8B",
requested_device=None,
requested_dtype="auto",
force_backend=None,
vllm_available=vllm_installed(),
)
print(selection.use_vllm, selection.device, selection.dtype, selection.reason)

Backend keywords​

Anything else goes verbatim to the backend constructor.

from interp_engine import load_model

served = load_model(
"meta-llama/Llama-3.1-8B",
backend="vllm",
gpu_memory_utilization=0.85,
max_model_len=4096,
extra_vllm_kwargs={"enable_prefix_caching": False},
)
tp = load_model("Qwen/Qwen3-32B", backend="vllm", num_gpus=4) # tensor_parallel_size

num_gpus becomes tensor_parallel_size on vLLM and device_map="auto" on eager. Arbitrary vLLM engine args go through extra_vllm_kwargs, not **kwargs.

Multi-GPU​

num_gpus defaults to 1 and is never inferred: sharding a model that fits one card is a choice, so load_model waits to be told. Every point is served at any count. vLLM shards heads and MLP neurons across ranks, and the worker gathers q, k, v, z, mlp_act and the attention scores back together before they leave the worker, so a capture at num_gpus=4 has the same shapes and the same values as one at num_gpus=1. Steering and the logit lens work the same way. The validator checked this against the eager reference at num_gpus=2 (Qwen/Qwen3.8-27B on 2x A40) and ran vllm and vllm-static at num_gpus=8 (moonshotai/Kimi-K2.6 on 8x H200).

The validator and benchmark drivers (validator/comparison/run_all_models.sh, benchmarks/run_all.sh) are the exception: with nothing set they use every visible CUDA card, and NUM_GPUS=1 / --num-gpus 1 pins them to one.

Lifecycle​

import asyncio

from interp_engine import load_model


async def main():
model = load_model("Qwen/Qwen3-8B")
await model.warmup() # where the load actually happens
print(model.n_layers, model.d_model)
await model.shutdown() # required before loading a second model on vLLM


asyncio.run(main())

Construction is lazy on both backends. warmup() is where the cost lands, so call it before you time anything. shutdown() reaps vLLM's child process; dropping the reference does not.