Skip to content

Comparisons

Three other libraries put interpretability on vLLM: interp-engine (Neuronpedia / Decode Research), vLLM-Lens (UK AISI) and TransformerLens 4.0, whose bridge can boot a vLLM engine. This page compares nnsight's vLLM integration with each, job by job, and ends with one throughput grid run over all of them on the same machine. Every other page in this section avoids comparison; this one is nothing else.


interp-engine

interp-engine is the interpretability engine behind Neuronpedia: a fixed vocabulary of 34 named points (resid_post.10, mlp_act.5, ...) served on a hooked vLLM backend, a CUDA-graph vllm-static backend, and a HuggingFace eager backend, with a validator that checks its captures against TransformerLens and nnsight across 50+ architectures. It is good, careful software, and this section deliberately follows the shape of its documentation page for page so the two can be read side by side.

This page is the comparison the others avoid. It was written against interp-engine 1.3.4 and nnsight 0.8 on the same machine, and the throughput table at the bottom is one grid run over both.

The one-sentence difference

interp-engine answers a closed question well: give me point P, steered by spec S. nnsight runs your Python inside the engine's forward: any module, any computation, any write, on the same request path. Most of what follows is that difference worked through each job.

Addressing

interp-engine nnsight
Unit Address(name, layer[, stream]), 34 canonical names a module on the tree + a side (.input, .output, .inputs, .source.<op>)
Layer index flattened forward order the tree's own indices (layers[10])
Discovery model.points(), the visualizer, SUPPORTED_POINTS.md print(model) on the meta tree, .source for the ops inside a forward
Portability across families the point means the same tensor on every family (attn_out_post vs attn_out on sandwich-norm models is handled for you) you address vLLM's module for this family; the sum of a layer's tuple is the residual on the standard families, and you read the tree to know
Missing on vLLM mlp_pre, mlp_pre_linear, lm_head, attn_gate, expert_weights, expert_indices (fused away); attn_scores/attn_probs by recompute gate_up_proj.output split in two is mlp_pre and mlp_pre_linear; logits_processor(lm_head, h) is the unembed; the pattern is a recompute here too; expert selection is inside the fused kernel for both

The table on Locations is, in effect, interp-engine's point list rewritten as nnsight locations on Qwen3-8B.

Loading

interp-engine nnsight
load_model(id, backend="vllm") — hooked, eager, every point VLLM(id) — every location
backend="vllm-static", static_points=[...] — graphs on, declared taps VLLM(id, taps=[...]) — graphs on, declared taps
backend="vllm-generate" — graphs + compile, no capture plain vllm.LLM, or model.generate(...) outside a block on any engine
backend="eager" (HuggingFace; gradients, batches, attn_probs) TransformersModel(id) — the same block, with gradients
warmup() / shutdown() dispatch=True / process exit
num_gpus=4 tensor_parallel_size=4
configure_static(points) after construction taps are fixed at construction

Both static/tapped engines sit on the same seam — vLLM's breakable CUDA graphs, a callable recorded at capture and run on every replay — and both turn torch.compile off to get it. interp-engine's "auto" static set is resid_post at every layer, read and write; nnsight's taps are whatever module locations you name, read and write.

Capabilities

interp-engine asks first: model.hooks_available, model.points(), model.grad_support, CapabilityUnsupported naming the capability and an alternative. nnsight has less to ask because there is no point table to be absent from — the tree is the capability — and refuses in the same spirit: a non-tap read, a barrier, a chunked prompt, a misspelled sampling keyword and a typo'd tap each raise with the location and the fix (Capabilities and limits).

Reading

Job interp-engine nnsight
one point run_with_cache(model, tokens, [point]) x = loc.clone().save() in a trace
every layer a list of 36 addresses a loop over model.model.layers
while generating capture_generation(...) tracer.all()
a batch eager only many invokes, on vLLM, batched by the scheduler
MoE routing router_logits; selection eager-only mlp.gate.output; selection fused, same
per-head contributions head_contributions(model, cache, 10) z[:, h*d:(h+1)*d] @ W_O[:, h*d:(h+1)*d].T in the block
direct logit attribution client-side, from captured points every layer and head in one forward, in the worker (Attribution)
SAE features capture the point, encode on the client the same, or a feature's live activation in the worker from two rows (SAE features)
gradients eager only HuggingFace path only

Where the two differ in kind: interp-engine's cache is assembled on the client from what the worker shipped; nnsight's block runs on the worker and ships what you saved. Capturing every layer of a 70B model at every step costs interp-engine 3.5× its single-layer rate and nnsight nothing measurable (table below), because the clones never leave the worker until the end.

Attention

Neither library has the attention pattern as a hook on vLLM — the paged kernel never forms it. interp-engine rebuilds it inside capture_attention from captured q/k; on nnsight you rebuild it in the block from self_attn.attn.inputs, in a dozen lines, and both match HuggingFace's eager attention to bf16 noise. interp-engine additionally ships per_head_value, attn_out_gate and split_fused_qkv for the family-specific layouts; on nnsight those are slices you write, and the Attention page gives them for Qwen3. Under tensor parallelism the two part ways: interp-engine's per-head z and direct attribution are single-GPU only (num_gpus > 1 shards the heads across ranks and its off-kernel recompute sees one shard), while nnsight gathers o_proj.input whole before the block reads it, so the per-head material above works at any tensor_parallel_size.

Logit lens

interp-engine's decode_residuals is a method that applies the family's post-unembed arithmetic and runs on the client from captured residuals (a topk variant runs on the worker). nnsight calls the model's own norm and logits_processor in the block, on the worker, so the vocab-wide tensor never travels unless you save it — and it works at every layer in one forward on vLLM, where interp-engine's layer_logits is eager-only.

Writing

interp-engine nnsight
Operations AddSpec, OrthogonalDecompSpec, ProjectionCapSpec, in order per layer any expression
Where resid_post by default; point="z" and the stream points any location — a head's slice of o_proj.input, a router logit, the logits, the sampled id
Positions position_mask, SteerMask.SPECIAL_TOKENS a boolean mask on the rows
During generation the spec is applied every step put the edit under tracer.iter[:N]
Conditional / stateful no yes: if step == 3, a running estimate, a probe's output deciding the write (Conditional interventions)
Activation patching no write of another run's activation a saved tensor written at a position, per layer or per head, batched as invokes (Activation patching)
Ablate a component no write off the residual points mlp.output[:] = 0, a head's slice of o_proj.input, a neuron, a router logit (Ablation)
SAE feature clamp add a fixed decoder direction scale the feature's live activation through generation (SAE features)

The ablate and force rows of the grid — zeroing a head's slice of o_proj.input, and overriding the sampled token — are the two the interp-engine harness could not express on any backend.

Generating

generate_stream yields a GenStep per token with n_logprobs; on vLLM its .logits is None because the sampler never ships the tensor out of the worker. nnsight's model.logits is that tensor, on the worker, readable and writable every step, and logprobs=k rides tracer.result for the portable case. Streaming text is generate_stream on both backends in interp-engine and mode="async" in nnsight; generate_full and tracer.result are the same vLLM RequestOutput.

Chat and tokens

interp-engine's Tokenize helper is the richer one: message_partition, message_spans, GeneratedTurnSpans, compose_assistant_turns, with the DeepSeek-V4 template quirks handled. nnsight hands you the HuggingFace tokenizer and the Chat and tokens page shows offset-mapping spans in a few lines.

Serving

interp-engine nnsight
In-process async every method is async; sync_model facade mode="async"; tracer.backend streams
Concurrency asyncio.gather over capture(...) asyncio.gather over traces
A server your FastAPI app, model built in lifespan nnsight-serve, or your app around VLLM
Clients without a GPU your API VLLM(id) meta tree + trace(..., serve=url)
Instrument every request a spec in a steer() context model.edit() — one block, every request, any tenant

Correctness

interp-engine's validator compares its points against TransformerLens and nnsight/nnterp across 50+ architectures at early, middle and late layers, with the results checked into the repository; that is a level of cross-engine validation nnsight does not have. nnsight's vLLM path is covered by ~195 tests on two GPUs, and every value shown in this section was checked against a HuggingFace forward of the same checkpoint. Its sharded path is checked value by value against a one-rank engine of the same checkpoint — Qwen2.5 at tp=2, DeepSeek-V2-Lite at tp=2 and at tp=4, dcp=2 — and the request accounting under preemption, n > 1, aborted streams and foreign tenants sharing the batch has a test each. Both libraries note the same trap: a value that is the right shape from the wrong place raises nothing.

Throughput

See Throughput, measured at the end of the page — one grid over all three libraries. The interp-engine columns there are vllm (hooked, eager) and vllm-static (CUDA graphs, declared taps); vllm-generate is left out because it is vanilla vLLM under another name (its numbers matched the vanilla column to within 1%), and the eager HuggingFace backends are compared in the text below rather than plotted.

What the grid says about interp-engine:

  • Under graphs the two are the same engine. nnsight taps and IE vllm-static are within noise of each other and of vanilla vLLM on plain generation and single-layer capture, at every size and parallelism — as they should be, sitting on the same vLLM seam.
  • Both eager engines pay the same tax, and it is the driver's: 86 vs 86 tok/s on one GPU, ~70 tok/s flat as GPUs are added.
  • Where the computation happens is the difference that scales. Every-layer capture and the logit lens ship tensors to interp-engine's client per request; nnsight keeps them on the worker. At 70B: 35 vs 10 tok/s for every-layer capture, 35 vs 22 for the lens.
  • Gradients are the one row interp-engine leads, on its eager HuggingFace backend: one forward+backward at 70B takes 531 ms there against 1,216 ms on nnsight's TransformersModel.

vLLM-Lens

vLLM-Lens (UK AISI, MIT) is a vLLM plugin: it registers through vLLM's general_plugins entry point, so an unmodified vllm serve or offline LLM gains activation capture, steering vectors and Garçon-style hooks the moment the package is installed, driven by SamplingParams.extra_args (offline) or vllm_xargs (over the OpenAI API). It also ships an Inspect AI model provider and a set of examples — causal tracing, logit and Jacobian lens, a deception probe, an emotion tracker, an activation oracle. This section was written against vLLM-Lens 1.2.1 on vLLM 0.27.1, the same engine version as the rest of this site.

The Examples group of this section is the vLLM-Lens example set redone in nnsight, so each can be read against its original.

The one-sentence difference

vLLM-Lens exposes one seam — a decoder layer's residual stream, on the way in (pre-hook) or out (post-hook) — and lets you capture it, add to it, or run a pickled function on it. nnsight exposes every module and runs your block interleaved with the forward: the attention projections, the per-head outputs, the router, the logits, the sampled id, and the residual stream are all locations, and the same block reads and writes any of them.

Where a hook can fire

vLLM-Lens nnsight
Residual stream leaving a block output_residual_stream=[l], Hook(layer_indices=[l]) sum(layers[l].output)
Residual stream entering a block Hook(..., pre=True) layers[l].input_layernorm.output[1], embed_tokens.output
q / k / v, per-head z, o_proj input, MLP neurons, the router — any of them (Locations)
Pre-sampling logits, the sampled id — (logprobs through the API) model.logits, model.samples, both writable
Inside a module's forward — .source ops

Both libraries take the residual stream to be vLLM's (hidden, residual) summed, and both clone before handing it to user code (vLLM-Lens clones for you; on nnsight a kept reference must be cloned — Locations).

Reading

Job vLLM-Lens nnsight
Capture layers extra_args={"output_residual_stream": [15, 20]} → out.activations["residual_stream"], (layers, pos, d) .save() on the location; tracer.cache()
Every step of a generation captured per forward pass, stacked tracer.all() / tracer.iter
Compute on the worker Hook(fn); results in ctx.saved, returned as hook_results the block itself
Parameters under TP / PP ctx.get_parameter(name) gathers; prefetch_params for PP activations are gathered; logits_processor(lm_head, h) for the unembed (Tensor parallelism)
A sweep of many prompts register_hooks once, generate per prompt, collect_hook_results model.edit() once, generate(prompts), values on each output
Batch of prompts llm.generate(prompts, params) generate(prompts) (plain) or one invoke per prompt (traced)

Writing

vLLM-Lens nnsight
Additive steering SteeringVector(activations, layer_indices, scale, norm_match, position_indices) layers[l].output[0][:] += scale * v under tracer.iter
Norm-matched norm_match=True: h += scale · ‖h‖ · v/‖v‖ h += scale * h.norm(dim=-1, keepdim=True) * v / v.norm()
Position-specific 3-D activations + position_indices index the rows
Anything else return a tensor from a Hook — at a layer boundary any expression at any location, including the sampler
Persistent register_hooks model.edit()

Serving

This is where vLLM-Lens is strongest. It lives inside vllm serve: the OpenAI-compatible completions and chat endpoints accept vllm_xargs for capture, steering and hooks, the server gains /v1/hooks/* for persistent hooks and parameter prefetch, activations come back base64- encoded in the response, and any OpenAI client — or Inspect, through the bundled provider — can drive it. nnsight's equivalent is nnsight-serve, a single-model server that runs nnsight traces submitted by GPU-less clients and installs engine-wide edits; it does not speak the OpenAI API. An edit installed on an nnsight engine does run on every request the engine serves, whoever sent it, which is the persistent-hook pattern; but the front door for OpenAI-style traffic is vLLM-Lens's.

vLLM-Lens nnsight
Server vllm serve + plugin nnsight-serve
Client protocol OpenAI API + vllm_xargs; VLLMLensClient nnsight traces over HTTP (serve=url)
Code on the server cloudpickled hook functions (arbitrary code; trusted clients only) serialized trace blocks (likewise)
Persistent instrumentation /v1/hooks/register model.edit(serve=url)
Inspect AI provider built in —
Streaming the API's mode="async"

Parallelism and engines

vLLM-Lens nnsight
Tensor parallel steering and hooks on every rank; capture on rank 0; a hook that saves Python lists sees them tp_size× every rank runs the block; reads are gathered whole; rank 0 reports
Pipeline parallel yes (prefetch_params for cross-stage weights) no
Expert parallel / MoE yes yes, incl. MoE partial-sum gather
CUDA graphs never — the plugin forces enforce_eager for every engine in the process taps= keeps replay (Performance)
LoRA yes (lora_request; the activation-oracle example) yes (lora_request in the sampling kwargs)
Installed alongside other engines VLLM_LENS_DISABLE=1 to make it a no-op nothing is patched until VLLM(...) is built

The last row matters operationally: vLLM-Lens patches EngineArgs.create_engine_config and LLM.generate at import, so every vLLM engine in a process that has it installed runs eager with the worker extension attached — including one you did not mean to instrument.

The examples

vLLM-Lens example Mechanism there On nnsight
causal_tracing.py pre-hook noise on the subject embeddings, post-hook restore, one HTTP request per (layer, position) Causal tracing — one trace per layer, one invoke per position, batched by the scheduler
logit_lens.py hook with ctx.get_parameter("lm_head.weight"), manual RMSNorm Logit lens — the model's own norm and logits_processor
jacobian_lens.py, jacobian_lens_chat.py hook applying a fitted J_l, prefetched weights; lens fit separately on prime-rl Jacobian lens — Neuronpedia's fitted lens, read out per step; edit() for the chat pattern
deception_probe.py persistent hooks over contrastive prompts, LBFGS probe A linear probe — edit() over the prompts; then the probe runs inside the model every step
emotion_tracker.py persistent hooks for direction vectors, per-token projections via chat Concept directions
activation_oracle.py capture, then norm-matched positional steering under a LoRA oracle expressible (norm-matched positional write + lora_request); not reproduced here — it needs the 70B oracle adapter
extract_residual_stream.ipynb per-request and persistent capture, offline and HTTP Capture, Async and servers

Throughput

vLLM-Lens is the fifth series in Throughput, measured; its column ran in its own environment (the plugin forces eager mode on every engine in a process) on the same cards, and the grid gained one row for it: a sweep of 1024 short prompts at one token each, capturing one layer — the activation-extraction workload vLLM-Lens is built for.

  • Plain generation and single-layer capture are a wash. Both libraries hook the same seam and both run the engine eagerly by default: 86–87 tok/s plain, 77–79 capturing one layer. nnsight's taps column is the only one that keeps CUDA graphs, and the only one within a few percent of vanilla.
  • vLLM-Lens pays per layer and per hook. Its hooks are installed on every decoder layer and each does its bookkeeping for every in-flight request on every step, and a steering vector or hook clones the layer's output: capturing all 32 layers halves throughput (37 vs nnsight's 68 tok/s), and a steering vector, a probe or a lens each cost about 40% (48–49 vs 78). nnsight's block visits only the locations it names.
  • The sweep is close. vLLM-Lens's capture rides the request with little per-request setup (1.32 s over 1024 prompts, 1.8× vanilla). An nnsight trace serializes a block per invoke and collects per step (1.6 s, 2.1×); the intended shape for a sweep is model.edit(), which installs the block once — 1.1 s eager, 1.0 s under taps, the fastest capture of the three.
  • Two rows vLLM-Lens cannot express: an ablation inside the attention block and an override of the sampled token, because its hook points are layer boundaries and it has no hook on the sampler.

A trap found while measuring, worth knowing on the nnsight side: writing model.model.layers[16].output inside the block references the model, and each of the 1024 invokes then serializes it — 8.6 s for the sweep. Binding the layer envoy before the trace and using it inside is what the grid shows. Invisible in a single trace, decisive in a sweep (Performance).

  • Tensor parallelism. vLLM-Lens installs its hooks on every rank; at tp=4 plain generation matches the other eager engines (76 tok/s vs 69–72), single-layer capture too (69), but a steering vector or probe costs a quarter (58, 57) where the eager nnsight engine and interp-engine lose nothing, and every-layer capture drops to 37 (nnsight eager 58, taps 208). At 70B/tp=4 the same shape: plain generation and one-layer capture on par or slightly ahead, steering, probe and lens at 18–19 tok/s against the eager engines' 27–29.

TransformerLens

TransformerLens 4.0 adds vLLM as a source for its bridge: RemoteBridge.boot_vllm(id) returns an object with the familiar run_with_cache, run_with_hooks and hook_dict, under the canonical TransformerLens hook names, with a vLLM engine doing the forward. The same driver backs an Inspect AI provider (tl_bridge_vllm). This section was written against TransformerLens 4.0.0, which pins vllm>=0.20.2,<0.21; it was run there, and tried on vLLM 0.27.1, the engine version of the rest of this site: the default compiled path works unmodified and captures bit-identical activations, and the batched path fails at boot ('GPUModelRunner' object has no attribute 'input_batch'), which is the drift the pin is for.

The one-sentence difference

TransformerLens uses vLLM as a fast prefill for a cache: one forward over a prompt, a fixed set of module outputs copied into preallocated buffers, and a four-word vocabulary of affine edits. nnsight runs your Python inside the engine's forward, through generation: any module, any expression, every decode step, many requests in one batch.

How it hooks the engine

This is the most interesting part of the design, and it is the opposite bet to nnsight's. The package registers a vllm.general_plugins entry point that patches Worker.load_model, and installs its forward hooks after the weights load and before vLLM compiles the model, so the hooks are traced into the torch.compile graph and captured in the CUDA graphs. A hook can therefore hold no Python: it writes output * scale + bias into a preallocated (max_num_batched_tokens, width) buffer through a first-write-wins gate made of torch.where, and an intervention is a swap of the contents of scale and bias between forwards. The engine keeps compilation and graphs, which neither nnsight's taps nor interp-engine's static backend do (both turn torch.compile off).

The cost is everything the graph cannot express, which is the rest of this section.

TransformerLens nnsight
Hooks live inside the compiled graph, fixed at boot eager: Python per module call; taps: a callable at declared graph breaks
torch.compile kept (compile cache disabled, so every boot recompiles) off under taps; off when eager
Hook set every hook point, always — 3 × layers + 2 buffers allocated and written on every forward, whatever you asked for only the locations a block names
Buffer memory (3L + 2) × max_num_batched_tokens × d_model: 1.6 GB on Llama-8B at the default 2048 tokens, doubled again by enable_position_interventions none resident
Requests per forward one (batch_size=1); enable_batching=True lifts it by switching the engine to enforce_eager many invokes, batched by the scheduler, either mode
Prefix caching forced off on by default: a trace asks for a recompute of its own prompt, plain requests still hit the cache; only an engine-wide edit() needs it off
In other engines in the process the patch is inert unless boot_vllm set its spec channel nothing is patched until VLLM(...) is built

Where a hook can fire

TransformerLens nnsight
Residual stream leaving a block blocks.{i}.hook_out (the (hidden, residual) pair summed for you) sum(layers[i].output)
Attention and MLP output blocks.{i}.attn.hook_out, blocks.{i}.mlp.hook_out self_attn.output, mlp.output
Embedding, final norm embed.hook_out, ln_final.hook_normalized (un-folded to the pre-weight value so it matches the HuggingFace bridge) embed_tokens.output, norm.output
Block, attention and MLP inputs not fireable .input on any module
q / k / v, per-head z, rotary q/k not fireable qkv_proj.output, o_proj.input, self_attn.attn.inputs (Locations)
Attention pattern and scores not fireable a recompute in the block (Attention)
MLP neurons, the router — gate_up_proj.output, mlp.gate.output
Logits reconstructed on the client as ln_final @ W_U in fp32, full sequence — so return_type="loss" works model.logits on the worker, readable and writable
The sampled id — model.samples
Inside a module's forward — .source ops

TransformerLens is careful about what it cannot do: every unavailable name is declared up front (non_fireable_hook_points), asking for one raises this backend cannot fire 'blocks.8.attn.hook_z'; use boot_transformers(), a hook that installed on no rank fails the boot, and a tensor-parallel boot cross-checks that every rank's copy of a capture agrees. One architecture overlay covers every decoder-only model by vLLM's conventional module paths (model.layers.{i}, .self_attn, .mlp), and the models that fall outside it fail at boot rather than reading zeros: Qwen3.5-0.8B, whose composite multimodal config has no hidden_size, fails before the engine is built on either path, and Qwen1.5-MoE-A2.7B fails on the compiled path when torch.compile rejects the graph with the hooks in it (ConstraintViolationError on the token dimension; plain vLLM compiles the same model, and the eager batched path runs it).

Reading

Job TransformerLens nnsight
One point, one prompt run_with_cache(tokens, names_filter=[name]) x = loc.clone().save() in a trace
Every layer names_filter= a list; without one, all 3L + 2 captures cross to the client a loop over model.model.layers
A batch of prompts enable_batching=True (eager); right-padded (batch, seq, width) cache one invoke per prompt, eager or under taps
While generating no: the driver raises on max_new_tokens != 1, and RemoteBridge has no generate() tracer.all() / tracer.iter
Compute on the worker no: captures are shipped, and everything else runs on the client the block itself
Logit lens, DLA, probes client-side from the cache, with TransformerLens's own ActivationCache helpers on the worker, per step (Logit lens, Attribution)
Gradients no (bwd_hooks, incl_bwd raise) — use boot_transformers HuggingFace path only

Two defaults are worth knowing before timing anything. A run_with_cache with no names_filter copies every hook point to the client, and every forward rebuilds full-sequence logits on the CPU unless you pass return_logits=False, a driver argument the bridge passes through. On Llama-3.2-1B with a 512-token prompt: 1,571 ms by default, 217 ms for one hook with logits, 32 ms for one hook without. The batched path has a third: its eager hook copies every hook point of every request to the CPU as it fires, and names_filter only trims what is concatenated afterwards.

Writing

TransformerLens nnsight
Operations suppress, scale, add, set — intervene={name: {"op": "add", "value": v}} any expression
A Python function rejected (vLLM accepts intervention specs (dict), not callables) the block is Python
run_with_hooks(fwd_hooks=...) runs after the forward on the captured tensors, read-only; a returned tensor is discarded, with a warning —
Where the five module-output families above any location, the logits and the sampled id included
Positions "pos": [3, 4], behind enable_position_interventions=True at boot (compiled path only) index the rows
A value that depends on the activation no: scale and bias are fixed before the forward yes — norm-matched steering, projection, a probe deciding the write
Patching another run's activation set with a width-shaped value, one position at a time per pos row a saved tensor written at any rows, batched as invokes (Activation patching)
Ablate a head no (hook_z is not fireable) a slice of o_proj.input (Ablation)
During generation no generation tracer.iter[:N]
Persistent no: the full spec set is pushed, and reset, every forward model.edit()

An add does what it should — a steering vector at layer 8 of Llama-3.2-1B moves the top prediction after The Eiffel Tower is in the city of off Paris — and the write is carried into the next layer by returning (modified − residual, residual), exact at identity and bf16-rounded under an edit.

Generating, serving, parallelism

TransformerLens nnsight
Generation none through the bridge; the Inspect provider generates, clearing interventions first and capturing the prompt's activations only model.generate, tracer.iter, mode="async"
A server none; Inspect AI provider (tl_bridge_vllm, single GPU) for capture inside evals nnsight-serve, trace(..., serve=url)
Tensor parallel yes, compiled path: hook points are post-all-reduce, captures are deduplicated across ranks and checked once yes, either mode; reads gathered whole
Pipeline parallel yes, compiled path no
Tensor parallel with many requests per forward refused (enable_batching with TP/PP raises) yes
Multi-node no (the hook spec travels in an environment variable) through Ray
vLLM versions pinned <0.21; one internals.py isolates the engine walks tracks current vLLM (0.27.1 here)

Correctness

On Llama-3.2-1B against a HuggingFace forward of the same checkpoint, TransformerLens's vLLM captures agree at every hook family (cosine 0.9998–1.0000; block output exact to bf16 rounding), its reconstructed logits pick the same top token at every position, and the captures are bit-identical between vLLM 0.20.2 and 0.27.1. The library's own end-to-end check of the compiled mutation path is a notebook run by hand on a GPU; its CI covers the dispatch protocol.

Throughput

TransformerLens is the yellow pair in Throughput, measured, on the panels it was run on, and most of its cells are ✗: the grid is built around generation — a 512-token prompt and 128 new tokens, with something read or written at every step — and the bridge does one forward per call. The rows it can express are the single forward with a capture and the 1024-prompt sweep. Its columns ran in their own environment on vLLM 0.20.2, the version it pins, so each dot is a share of plain vLLM 0.20.2 measured in that same environment (the vanilla vLLM 0.20.2 column of the tables) rather than of the 0.27.1 vanilla the other libraries are read against.

  • Two engines, neither of which is both fast and batched. The default path keeps torch.compile and CUDA graphs but takes one prompt per forward, so the sweep is 1,024 sequential generate calls, each bracketed by two RPCs to push the intervention state and open the capture gates and one to read the buffer back: 25.9 s on Llama-8B (23.8 s on Qwen3-8B, 7.7 s on Llama-1B), against 0.76 s for plain vLLM 0.20.2 given the same prompts as one batch and 1.6 s for an nnsight trace. Most of that is the shape rather than the hook — plain vLLM driven one prompt per call, nothing attached, takes 15–19 s — and the same path on vLLM 0.27.1 is 20% slower again (29.8 s). enable_batching=True submits the sweep as one batch, and gives up compilation and graphs to do it: 7.2 s, 4.5× nnsight's per-request trace and 6.6× its edit().
  • The batched hook copies everything. On the eager path every hook point of every request is sliced and moved to the CPU as it fires — 98 tensors per request on Llama-8B — and names_filter is applied afterwards, so asking for one layer costs the same as asking for all of them. It shows most on the small model: a single forward on Llama-1B is 72 ms batched against 13 ms compiled and 9 ms for plain vLLM.
  • A single forward is where it is closest, provided the two defaults above are turned off (names_filter=[...], return_logits=False): 62 ms on Llama-8B against 37 ms for plain vLLM 0.20.2 and 53 ms for an nnsight trace on 0.27.1; the compiled hooks add an affine and a gated copy at every hook point whether or not anything was asked for. With the defaults on it is about 370 ms.
  • Tensor parallelism makes it slower. From one card to tensor_parallel_size=4, plain vLLM's single forward drops 37 → 25 → 19 ms and its one-per-call sweep 15–19 → 9 → 6 s; TransformerLens's forward goes the other way, 62 → 73 → 101 ms, because every capture read now crosses every rank and is merged on the client, and its sweep comes down only with the engine (25.9 → 14.1 → 12.6 s). nnsight's trace follows vanilla (53 → 45 → 39 ms; 1.6 → 1.3 → 1.2 s).
  • The mixture of experts is its best panel, on the path that survives. On Qwen1.5-MoE-A2.7B the compiled path does not boot (above), but the eager batched path is closer to nnsight than anywhere else: 2.8 s for the sweep against nnsight's 1.4 s (0.7 s with edit()), and 62 ms for a single forward against 46 ms. An MoE forward is expensive relative to the hook's fixed per-request copy, so the copy shows less.
  • Every other row is ✗, for one of three reasons recorded in the tables' hover text: no generation through the bridge; no hook point inside the attention block or on the sampler; no backward pass.

Throughput, measured

One harness, every library, the same machine: bf16, A100-80GB, vLLM 0.27.1 (TransformerLens on the 0.20.2 it pins, read against that version's vanilla), transformers 5.15, 512-token prompt, 128 new tokens, greedy, prefix caching off on every engine, 3 processes × 3 timed runs per cell (mean; std ≤ 2% except the HuggingFace-eager rows, which are not plotted). Each dot is a library's throughput on a workload as a share of plain vLLM doing the same generation with nothing attached — vanilla generate for single-stream rows, vanilla 8-concurrent generate for the ×8 rows, and vanilla's own sweep time for the sweep rows (where less time is more). Hollow dots are eager engines, filled dots keep CUDA graphs; hue is the library. The whisker through each dot is the min–max over its repeated runs (nine for most cells: three processes × three timed runs; the 70B and tp=8 panels have six). A cell that is statistically significantly faster than its nnsight counterpart — the eager engines and vLLM-Lens against nnsight eager, vllm-static against nnsight taps — by an exact two-sided Mann-Whitney U test at p < 0.05 and at least 3% apart is bold in the tables; a bold nnsight cell beat every counterpart in its row by the same test. Hover a dot for the number, the run count and the range.

Every panel was measured in one session on one machine, which is what makes the libraries comparable; the absolute tok/s of an eager row is not. A graph engine is bound by the GPU and reproduces: re-run on the same machine while other tenants are running, vanilla vLLM gives 91.8 tok/s against the 92 below and nnsight taps 88.6 against 89. An eager engine spends a Python round trip per module call on the driver, so its throughput follows whatever CPU the host has left, and the whole nnsight eager column on that re-run came back between 44 and 58 tok/s instead of 68 to 86. Plain vLLM with enforce_eager=True and nothing attached moves with it (52.1 tok/s there, against nnsight's 54.1), which is the reason the eager rows on Performance are published as a share of that engine rather than as a rate.

Other models and scenarios

The same grid on more models and two more workload shapes (three processes each; the DeepSeek panel has no interp-engine column, whose runner was not pointed at it):

  • Qwen3-8B (36 layers with QK-norm, the model this section's examples use). The eager engines are all at ~74% of vanilla on plain generation (67–69 tok/s vs 91) where on Llama-8B they were at 93%: the eager tax scales with the number of module calls per layer, and Qwen3 has more of them. The graph engines are unaffected (nnsight taps 88, interp-engine static 88). Everything else has the Llama shape — nnsight leads on every-layer capture and the lens, interp-engine's hooked engine leads by ~10% on single-layer capture, steering and the probe, vLLM-Lens pays ~35% for a hook or steering vector.
  • Qwen1.5-MoE-A2.7B (60 experts, 4 active). The eager engines collapse to 20% of vanilla (40 tok/s vs 210): an MoE layer is many small kernels, and each is a Python round trip on an eager path. nnsight's taps keep 98% (205); interp-engine's static engine 86% (181); vLLM-Lens is an eager engine and sits with the eager nnsight and interp-engine at 40–42. At 8 concurrent requests nnsight taps (1,091) and interp-engine static (874) both exceed vanilla (671), which turns out to be vLLM's torch.compile path losing on this model — see the note under the MoE chart.
  • DeepSeek-V2-Lite (MLA attention + MoE, 27 layers, 16B). Same picture, stronger: eager engines at 19% of vanilla (31 vs 164), nnsight taps at 98% (161). The MLA path (kv_a_proj, kv_b_proj, the absorbed decode kernel) adds module calls that only a graph engine hides.
  • Llama-3.2-1B. With a small model the fixed per-step Python cost dominates: the eager engines are at 37–39% of vanilla, the graph engines at 94–95%, and vLLM-Lens's hook costs show most clearly (steering 116 vs interp-engine 145, every-layer capture 96 vs nnsight 119).
  • Long context (2048-token prompt, 512 new tokens, Llama-8B). Longer decode amortizes per-step overheads: every eager engine moves up a few points relative to the 512/128 grid, the ordering is unchanged, and vLLM-Lens's per-hook cost persists (steering 50, probe 49 vs the eager engines' 75–84).
  • 32 concurrent requests (Llama-8B). Plain generation is a wash for every engine (1,430–1,470 tok/s vs vanilla 1,523). Capturing one layer on all 32 streams costs nnsight 6–10% (1,330 eager, 1,367 taps), interp-engine 4–8% (1,408 static, 1,371 hooked) and vLLM-Lens 25% (1,100): at this batch size the per-request bookkeeping that all three do on every step is what shows, and the graph engines' decode advantage is mostly spent.
Llama-3.1-8B, one GPU Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens TransformerLens batched TransformerLens (compile + CUDA graphs) 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 86 (93% of vanilla; 9 runs, 84–87) interp-engine vllm-static: 90 (97% of vanilla; 9 runs, 90–90) vLLM-Lens: 86 (94% of vanilla; 9 runs, 84–87) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 86 (93% of vanilla; 9 runs, 84–87) nnsight taps (CUDA graphs): 89 (97% of vanilla; 9 runs, 89–89) generate, 8 concurrent interp-engine vllm: 580 (94% of vanilla; 9 runs, 579–582) interp-engine vllm-static: 596 (97% of vanilla; 9 runs, 594–597) vLLM-Lens: 578 (94% of vanilla; 9 runs, 577–580) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 577 (93% of vanilla; 9 runs, 576–578) nnsight taps (CUDA graphs): 597 (97% of vanilla; 9 runs, 597–598) capture 1 layer, every step interp-engine vllm: 84 (91% of vanilla; 9 runs, 81–87); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 89 (97% of vanilla; 9 runs, 89–89) vLLM-Lens: 75 (81% of vanilla; 9 runs, 72–77); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 79 (85% of vanilla; 9 runs, 78–80) nnsight taps (CUDA graphs): 89 (96% of vanilla; 9 runs, 88–89) capture every layer, every step interp-engine vllm: 48 (52% of vanilla; 9 runs, 46–51); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 71 (77% of vanilla; 9 runs, 68–74); significantly slower than the counterpart (p<0.05) vLLM-Lens: 37 (40% of vanilla; 9 runs, 36–38); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 68 (73% of vanilla; 9 runs, 67–69); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 88 (96% of vanilla; 9 runs, 88–88); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 568 (92% of vanilla; 9 runs, 561–572); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 588 (95% of vanilla; 9 runs, 585–590) vLLM-Lens: 470 (76% of vanilla; 9 runs, 459–478); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 529 (86% of vanilla; 9 runs, 524–536) nnsight taps (CUDA graphs): 577 (93% of vanilla; 9 runs, 576–578) additive steering, 1 layer interp-engine vllm: 85 (93% of vanilla; 9 runs, 83–87); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 89 (97% of vanilla; 9 runs, 89–89) vLLM-Lens: 49 (53% of vanilla; 9 runs, 48–50); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 78 (85% of vanilla; 9 runs, 76–80) nnsight taps (CUDA graphs): 89 (96% of vanilla; 9 runs, 89–89) logit lens every step interp-engine vllm: 63 (68% of vanilla; 9 runs, 62–64); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 65 (71% of vanilla; 9 runs, 65–66); significantly slower than the counterpart (p<0.05) vLLM-Lens: 47 (51% of vanilla; 9 runs, 47–48); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 78 (84% of vanilla; 9 runs, 76–79); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 84 (91% of vanilla; 9 runs, 84–84); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 84 (92% of vanilla; 9 runs, 83–86); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 89 (96% of vanilla; 9 runs, 89–89) vLLM-Lens: 49 (53% of vanilla; 9 runs, 48–50); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 78 (84% of vanilla; 9 runs, 77–79) nnsight taps (CUDA graphs): 89 (96% of vanilla; 9 runs, 88–89) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ nnsight eager: 79 (85% of vanilla; 9 runs, 78–79) nnsight taps (CUDA graphs): 89 (96% of vanilla; 9 runs, 89–89) override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ nnsight eager: 79 (86% of vanilla; 9 runs, 78–81) nnsight taps (CUDA graphs): 89 (96% of vanilla; 9 runs, 88–89) one forward over 512 tokens, capture 1 layer interp-engine vllm: 43 ms (104% of vanilla; 9 runs, 42 ms–45 ms); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 44 ms (104% of vanilla; 9 runs, 42 ms–46 ms); significantly faster than the counterpart (p<0.05) vLLM-Lens: 45 ms (100% of vanilla; 9 runs, 44 ms–47 ms); significantly faster than the counterpart (p<0.05) TransformerLens batched: 98 ms (37% of vanilla vLLM 0.20.2; 6 runs, 87 ms–118 ms) TransformerLens (compile + CUDA graphs): 62 ms (59% of vanilla vLLM 0.20.2; 9 runs, 53 ms–74 ms) nnsight eager: 53 ms (85% of vanilla; 9 runs, 51 ms–61 ms) nnsight taps (CUDA graphs): 53 ms (86% of vanilla; 9 runs, 51 ms–55 ms) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.33 s (56% of vanilla; 9 runs, 1.30 s–1.36 s); significantly faster than the counterpart (p<0.05) TransformerLens batched: 7.18 s (11% of vanilla vLLM 0.20.2; 6 runs, 6.41 s–8.39 s) TransformerLens (compile + CUDA graphs): 25.90 s (3% of vanilla vLLM 0.20.2; 6 runs, 24.42 s–30.47 s) nnsight eager: 1.60 s (47% of vanilla; 9 runs, 1.55 s–1.63 s) nnsight taps (CUDA graphs): 1.48 s (50% of vanilla; 9 runs, 1.46 s–1.50 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ TransformerLens batched: captures are per forward; there is no installed-once block ✗ TransformerLens (compile + CUDA graphs): captures are per forward; there is no installed-once block ✗ nnsight eager: 1.09 s (69% of vanilla; 9 runs, 1.05 s–1.11 s) nnsight taps (CUDA graphs): 1.00 s (74% of vanilla; 9 runs, 0.98 s–1.04 s)
The numbers — Llama-3.1-8B, one GPU (tok/s; sweeps in seconds)
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens vanilla vLLM 0.20.2 TransformerLens batched TransformerLens (compile + CUDA graphs)
generate 92 86 89 86 90 86 92 ✗ ✗
generate, 8 concurrent 618 577 597 580 596 578 616 ✗ ✗
capture 1 layer, every step · 79 89 84 89 75 · ✗ ✗
capture every layer, every step · 68 88 48 71 37 · ✗ ✗
capture 1 layer, 8 concurrent · 529 577 568 588 470 · ✗ ✗
additive steering, 1 layer · 78 89 85 89 49 · ✗ ✗
logit lens every step · 78 84 63 65 47 · ✗ ✗
linear probe every step · 78 89 84 89 49 · ✗ ✗
zero one attention head every step · 79 89 ✗ ✗ ✗ · ✗ ✗
override the sampled token every step · 79 89 ✗ ✗ ✗ · ✗ ✗
one forward over 512 tokens, capture 1 layer 45 ms 53 ms 53 ms 43 ms 44 ms 45 ms 37 ms 98 ms 62 ms
sweep: 1024 × 1 token, capture 1 layer, per request 0.75 s 1.60 s 1.48 s · · 1.33 s 0.76 s 7.18 s 25.90 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 1.09 s 1.00 s · · ✗ · ✗ ✗
Llama-3.1-8B, tensor-parallel 2 Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens TransformerLens (compile + CUDA graphs) 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 71 (48% of vanilla; 9 runs, 70–72) interp-engine vllm-static: 142 (96% of vanilla; 9 runs, 142–142) vLLM-Lens: 75 (51% of vanilla; 9 runs, 74–76); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 72 (48% of vanilla; 9 runs, 70–73) nnsight taps (CUDA graphs): 142 (96% of vanilla; 9 runs, 141–142) generate, 8 concurrent interp-engine vllm: 520 (56% of vanilla; 9 runs, 514–523) interp-engine vllm-static: 893 (96% of vanilla; 9 runs, 888–898) vLLM-Lens: 537 (57% of vanilla; 9 runs, 523–551) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 525 (56% of vanilla; 9 runs, 517–534) nnsight taps (CUDA graphs): 898 (96% of vanilla; 9 runs, 896–900) capture 1 layer, every step interp-engine vllm: 71 (48% of vanilla; 9 runs, 69–72); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 139 (94% of vanilla; 9 runs, 139–140) vLLM-Lens: 67 (45% of vanilla; 9 runs, 66–68) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 67 (45% of vanilla; 9 runs, 66–68) nnsight taps (CUDA graphs): 140 (95% of vanilla; 9 runs, 140–140) capture every layer, every step interp-engine vllm: 36 (24% of vanilla; 9 runs, 34–37); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 65 (44% of vanilla; 9 runs, 60–69); significantly slower than the counterpart (p<0.05) vLLM-Lens: 35 (23% of vanilla; 9 runs, 34–36); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 59 (40% of vanilla; 9 runs, 58–59); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 138 (94% of vanilla; 9 runs, 138–138); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 507 (54% of vanilla; 9 runs, 497–513); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 859 (92% of vanilla; 9 runs, 849–866) vLLM-Lens: 424 (45% of vanilla; 9 runs, 414–438); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 462 (49% of vanilla; 9 runs, 457–466) nnsight taps (CUDA graphs): 851 (91% of vanilla; 9 runs, 843–858) additive steering, 1 layer interp-engine vllm: 72 (49% of vanilla; 9 runs, 71–73); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 139 (94% of vanilla; 9 runs, 138–140) vLLM-Lens: 53 (36% of vanilla; 9 runs, 52–54); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 66 (44% of vanilla; 9 runs, 64–67) nnsight taps (CUDA graphs): 139 (94% of vanilla; 9 runs, 139–139) logit lens every step interp-engine vllm: 43 (29% of vanilla; 9 runs, 42–44); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 59 (40% of vanilla; 9 runs, 56–61); significantly slower than the counterpart (p<0.05) vLLM-Lens: 51 (34% of vanilla; 9 runs, 50–52); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 66 (45% of vanilla; 9 runs, 65–67); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 134 (91% of vanilla; 9 runs, 134–134); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 72 (49% of vanilla; 9 runs, 71–73); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 139 (94% of vanilla; 9 runs, 137–139) vLLM-Lens: 53 (36% of vanilla; 9 runs, 52–53); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 66 (45% of vanilla; 9 runs, 64–66) nnsight taps (CUDA graphs): 138 (94% of vanilla; 9 runs, 138–139) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ nnsight eager: 66 (45% of vanilla; 9 runs, 65–67) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ nnsight eager: 67 (45% of vanilla; 9 runs, 66–68) nnsight taps (CUDA graphs): 140 (95% of vanilla; 9 runs, 139–140) one forward over 512 tokens, capture 1 layer interp-engine vllm: 37 ms (80% of vanilla; 9 runs, 35 ms–42 ms); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 38 ms (78% of vanilla; 9 runs, 36 ms–42 ms); significantly faster than the counterpart (p<0.05) vLLM-Lens: 36 ms (84% of vanilla; 9 runs, 33 ms–39 ms); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): 73 ms (34% of vanilla vLLM 0.20.2; 6 runs, 69 ms–78 ms) nnsight eager: 45 ms (66% of vanilla; 9 runs, 41 ms–53 ms) nnsight taps (CUDA graphs): 46 ms (65% of vanilla; 9 runs, 40 ms–49 ms) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.17 s (42% of vanilla; 9 runs, 1.13 s–1.20 s); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): 14.05 s (3% of vanilla vLLM 0.20.2; 3 runs, 13.96 s–14.21 s) nnsight eager: 1.33 s (37% of vanilla; 3 runs, 1.31 s–1.34 s) nnsight taps (CUDA graphs): 1.42 s (35% of vanilla; 3 runs, 1.34 s–1.50 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ TransformerLens (compile + CUDA graphs): captures are per forward; there is no installed-once block ✗ nnsight eager: 1.05 s (47% of vanilla; 3 runs, 1.00 s–1.15 s) nnsight taps (CUDA graphs): 1.05 s (47% of vanilla; 3 runs, 1.01 s–1.10 s)
The numbers — Llama-3.1-8B, tp=2
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens vanilla vLLM 0.20.2 TransformerLens (compile + CUDA graphs)
generate 148 72 142 71 142 75 149 ✗
generate, 8 concurrent 933 525 898 520 893 537 942 ✗
capture 1 layer, every step · 67 140 71 139 67 · ✗
capture every layer, every step · 59 138 36 65 35 · ✗
capture 1 layer, 8 concurrent · 462 851 507 859 424 · ✗
additive steering, 1 layer · 66 139 72 139 53 · ✗
logit lens every step · 66 134 43 59 51 · ✗
linear probe every step · 66 138 72 139 53 · ✗
zero one attention head every step · 66 ✗ ✗ ✗ ✗ · ✗
override the sampled token every step · 67 140 ✗ ✗ ✗ · ✗
one forward over 512 tokens, capture 1 layer 30 ms 45 ms 46 ms 37 ms 38 ms 36 ms 25 ms 73 ms
sweep: 1024 × 1 token, capture 1 layer, per request 0.49 s 1.33 s 1.42 s · · 1.17 s 0.49 s 14.05 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 1.05 s 1.05 s · · ✗ · ✗
Llama-3.1-8B, tensor-parallel 4 Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens TransformerLens (compile + CUDA graphs) 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 69 (30% of vanilla; 9 runs, 68–70); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 216 (95% of vanilla; 9 runs, 214–216) vLLM-Lens: 76 (33% of vanilla; 9 runs, 74–77); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 72 (31% of vanilla; 9 runs, 70–73) nnsight taps (CUDA graphs): 216 (94% of vanilla; 9 runs, 215–217) generate, 8 concurrent interp-engine vllm: 512 (36% of vanilla; 9 runs, 505–519); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 1,345 (94% of vanilla; 9 runs, 1,331–1,357) vLLM-Lens: 550 (38% of vanilla; 9 runs, 538–558); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 532 (37% of vanilla; 9 runs, 520–541) nnsight taps (CUDA graphs): 1,355 (95% of vanilla; 9 runs, 1,343–1,367) capture 1 layer, every step interp-engine vllm: 69 (30% of vanilla; 9 runs, 66–71); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 206 (90% of vanilla; 9 runs, 205–206); significantly slower than the counterpart (p<0.05) vLLM-Lens: 69 (30% of vanilla; 9 runs, 68–70); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 66 (29% of vanilla; 9 runs, 64–68) nnsight taps (CUDA graphs): 213 (93% of vanilla; 9 runs, 212–213); significantly faster than every counterpart (p<0.05) capture every layer, every step interp-engine vllm: 29 (13% of vanilla; 9 runs, 28–31); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 54 (24% of vanilla; 9 runs, 49–58); significantly slower than the counterpart (p<0.05) vLLM-Lens: 37 (16% of vanilla; 9 runs, 36–38); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 58 (26% of vanilla; 9 runs, 58–59); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 208 (91% of vanilla; 9 runs, 207–208); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 482 (34% of vanilla; 9 runs, 470–496); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 1,163 (81% of vanilla; 9 runs, 1,125–1,201); significantly slower than the counterpart (p<0.05) vLLM-Lens: 430 (30% of vanilla; 9 runs, 420–439); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 463 (32% of vanilla; 9 runs, 454–476) nnsight taps (CUDA graphs): 1,256 (88% of vanilla; 9 runs, 1,243–1,265); significantly faster than every counterpart (p<0.05) additive steering, 1 layer interp-engine vllm: 71 (31% of vanilla; 9 runs, 69–72); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 210 (92% of vanilla; 9 runs, 207–211) vLLM-Lens: 58 (25% of vanilla; 9 runs, 56–59); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 66 (29% of vanilla; 9 runs, 65–67) nnsight taps (CUDA graphs): 210 (92% of vanilla; 9 runs, 209–211) logit lens every step interp-engine vllm: 33 (15% of vanilla; 9 runs, 33–34); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 48 (21% of vanilla; 9 runs, 47–49); significantly slower than the counterpart (p<0.05) vLLM-Lens: 55 (24% of vanilla; 9 runs, 54–56); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 66 (29% of vanilla; 9 runs, 65–67); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 205 (90% of vanilla; 9 runs, 204–205); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 69 (30% of vanilla; 9 runs, 67–71); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 206 (90% of vanilla; 9 runs, 204–208) vLLM-Lens: 57 (25% of vanilla; 9 runs, 56–58); significantly slower than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 66 (29% of vanilla; 9 runs, 66–67) nnsight taps (CUDA graphs): 209 (91% of vanilla; 9 runs, 208–209) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ nnsight eager: 66 (29% of vanilla; 9 runs, 63–67) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ nnsight eager: 67 (29% of vanilla; 9 runs, 66–68) nnsight taps (CUDA graphs): 213 (93% of vanilla; 9 runs, 212–213) one forward over 512 tokens, capture 1 layer interp-engine vllm: 34 ms (66% of vanilla; 9 runs, 34 ms–36 ms); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 34 ms (66% of vanilla; 9 runs, 34 ms–35 ms); significantly faster than the counterpart (p<0.05) vLLM-Lens: 29 ms (77% of vanilla; 9 runs, 28 ms–32 ms); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): 101 ms (19% of vanilla vLLM 0.20.2; 6 runs, 96 ms–107 ms) nnsight eager: 39 ms (58% of vanilla; 9 runs, 37 ms–44 ms) nnsight taps (CUDA graphs): 39 ms (58% of vanilla; 9 runs, 36 ms–42 ms) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.00 s (33% of vanilla; 9 runs, 0.96 s–1.06 s); significantly faster than the counterpart (p<0.05) TransformerLens (compile + CUDA graphs): 12.59 s (3% of vanilla vLLM 0.20.2; 6 runs, 12.07 s–12.81 s) nnsight eager: 1.20 s (28% of vanilla; 3 runs, 1.18 s–1.21 s) nnsight taps (CUDA graphs): 1.18 s (28% of vanilla; 3 runs, 1.16 s–1.21 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ TransformerLens (compile + CUDA graphs): captures are per forward; there is no installed-once block ✗ nnsight eager: 1.46 s (23% of vanilla; 3 runs, 1.30 s–1.69 s) nnsight taps (CUDA graphs): 1.35 s (25% of vanilla; 3 runs, 1.21 s–1.54 s)
The numbers — Llama-3.1-8B, tp=4
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens vanilla vLLM 0.20.2 TransformerLens (compile + CUDA graphs)
generate 228 72 216 69 216 76 231 ✗
generate, 8 concurrent 1,431 532 1,355 512 1,345 550 1,460 ✗
capture 1 layer, every step · 66 213 69 206 69 · ✗
capture every layer, every step · 58 208 29 54 37 · ✗
capture 1 layer, 8 concurrent · 463 1,256 482 1,163 430 · ✗
additive steering, 1 layer · 66 210 71 210 58 · ✗
logit lens every step · 66 205 33 48 55 · ✗
linear probe every step · 66 209 69 206 57 · ✗
zero one attention head every step · 66 ✗ ✗ ✗ ✗ · ✗
override the sampled token every step · 67 213 ✗ ✗ ✗ · ✗
one forward over 512 tokens, capture 1 layer 23 ms 39 ms 39 ms 34 ms 34 ms 29 ms 19 ms 101 ms
sweep: 1024 × 1 token, capture 1 layer, per request 0.33 s 1.20 s 1.18 s · · 1.00 s 0.33 s 12.59 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 1.46 s 1.35 s · · ✗ · ✗
Llama-3.1-70B, tensor-parallel 4 Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 29 (79% of vanilla; 9 runs, 29–29) interp-engine vllm-static: 36 (98% of vanilla; 9 runs, 36–36) vLLM-Lens: 32 (87% of vanilla; 9 runs, 32–32); significantly faster than the counterpart (p<0.05) nnsight eager: 30 (81% of vanilla; 9 runs, 29–30) nnsight taps (CUDA graphs): 36 (97% of vanilla; 9 runs, 36–36) generate, 8 concurrent interp-engine vllm: 203 (86% of vanilla; 9 runs, 199–207) interp-engine vllm-static: 226 (96% of vanilla; 9 runs, 224–228) vLLM-Lens: 214 (91% of vanilla; 9 runs, 212–216); significantly faster than the counterpart (p<0.05) nnsight eager: 200 (85% of vanilla; 9 runs, 197–203) nnsight taps (CUDA graphs): 226 (96% of vanilla; 9 runs, 226–227) capture 1 layer, every step interp-engine vllm: 29 (79% of vanilla; 9 runs, 29–30); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 35 (95% of vanilla; 9 runs, 35–35) vLLM-Lens: 28 (77% of vanilla; 9 runs, 27–29); significantly faster than the counterpart (p<0.05) nnsight eager: 27 (75% of vanilla; 9 runs, 27–28) nnsight taps (CUDA graphs): 35 (97% of vanilla; 9 runs, 35–36) capture every layer, every step interp-engine vllm: 7 (20% of vanilla; 9 runs, 7–8); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 10 (27% of vanilla; 9 runs, 9–11); significantly slower than the counterpart (p<0.05) vLLM-Lens: 10 (26% of vanilla; 9 runs, 9–10); significantly slower than the counterpart (p<0.05) nnsight eager: 23 (63% of vanilla; 9 runs, 23–24); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 35 (96% of vanilla; 9 runs, 35–35); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 187 (80% of vanilla; 9 runs, 184–190); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 212 (90% of vanilla; 9 runs, 209–214); significantly slower than the counterpart (p<0.05) vLLM-Lens: 170 (72% of vanilla; 9 runs, 165–175); significantly slower than the counterpart (p<0.05) nnsight eager: 179 (76% of vanilla; 9 runs, 178–181) nnsight taps (CUDA graphs): 220 (94% of vanilla; 9 runs, 219–220); significantly faster than every counterpart (p<0.05) additive steering, 1 layer interp-engine vllm: 29 (79% of vanilla; 9 runs, 28–29); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 35 (96% of vanilla; 9 runs, 35–35) vLLM-Lens: 19 (51% of vanilla; 9 runs, 19–19); significantly slower than the counterpart (p<0.05) nnsight eager: 27 (74% of vanilla; 9 runs, 27–27) nnsight taps (CUDA graphs): 35 (97% of vanilla; 9 runs, 35–35) logit lens every step interp-engine vllm: 19 (53% of vanilla; 9 runs, 18–20); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 22 (61% of vanilla; 9 runs, 22–23); significantly slower than the counterpart (p<0.05) vLLM-Lens: 18 (49% of vanilla; 9 runs, 18–18); significantly slower than the counterpart (p<0.05) nnsight eager: 27 (75% of vanilla; 9 runs, 27–28); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 35 (96% of vanilla; 9 runs, 35–35); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 29 (79% of vanilla; 9 runs, 29–29); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 35 (96% of vanilla; 9 runs, 35–35) vLLM-Lens: 19 (51% of vanilla; 9 runs, 18–19); significantly slower than the counterpart (p<0.05) nnsight eager: 27 (74% of vanilla; 9 runs, 26–27) nnsight taps (CUDA graphs): 35 (97% of vanilla; 9 runs, 35–35) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 27 (74% of vanilla; 9 runs, 26–28) nnsight taps (CUDA graphs): 35 (97% of vanilla; 9 runs, 35–35) override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 27 (75% of vanilla; 9 runs, 27–28) nnsight taps (CUDA graphs): 35 (97% of vanilla; 9 runs, 35–36)
The numbers — Llama-3.1-70B, tp=4
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate 37 30 36 29 36 32
generate, 8 concurrent 235 200 226 203 226 214
capture 1 layer, every step · 27 35 29 35 28
capture every layer, every step · 23 35 7 10 10
capture 1 layer, 8 concurrent · 179 220 187 212 170
additive steering, 1 layer · 27 35 29 35 19
logit lens every step · 27 35 19 22 18
linear probe every step · 27 35 29 35 19
zero one attention head every step · 27 35 ✗ ✗ ✗
override the sampled token every step · 27 35 ✗ ✗ ✗
Qwen3-8B, one GPU Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens TransformerLens batched TransformerLens (compile + CUDA graphs) 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 69 (76% of vanilla; 9 runs, 68–70) interp-engine vllm-static: 88 (96% of vanilla; 9 runs, 88–88) vLLM-Lens: 68 (75% of vanilla; 9 runs, 67–70) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 67 (74% of vanilla; 9 runs, 66–69) nnsight taps (CUDA graphs): 88 (96% of vanilla; 9 runs, 87–88) generate, 8 concurrent interp-engine vllm: 496 (82% of vanilla; 9 runs, 493–501); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 580 (96% of vanilla; 9 runs, 580–580) vLLM-Lens: 490 (81% of vanilla; 9 runs, 482–496); significantly faster than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 471 (78% of vanilla; 9 runs, 464–477) nnsight taps (CUDA graphs): 583 (96% of vanilla; 9 runs, 581–584) capture 1 layer, every step interp-engine vllm: 67 (73% of vanilla; 9 runs, 65–68); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 87 (96% of vanilla; 9 runs, 87–87) vLLM-Lens: 60 (66% of vanilla; 9 runs, 58–61) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 60 (66% of vanilla; 9 runs, 60–61) nnsight taps (CUDA graphs): 87 (96% of vanilla; 9 runs, 87–87) capture every layer, every step interp-engine vllm: 39 (43% of vanilla; 9 runs, 37–40); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 67 (73% of vanilla; 9 runs, 64–70); significantly slower than the counterpart (p<0.05) vLLM-Lens: 32 (35% of vanilla; 9 runs, 31–32); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 52 (58% of vanilla; 6 runs, 52–53); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 86 (95% of vanilla; 9 runs, 86–87); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 475 (79% of vanilla; 9 runs, 460–483); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 568 (94% of vanilla; 9 runs, 563–572) vLLM-Lens: 387 (64% of vanilla; 9 runs, 358–396); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 419 (69% of vanilla; 6 runs, 414–422) nnsight taps (CUDA graphs): 562 (93% of vanilla; 9 runs, 561–563) additive steering, 1 layer interp-engine vllm: 67 (74% of vanilla; 9 runs, 66–70); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 87 (96% of vanilla; 9 runs, 87–87) vLLM-Lens: 42 (47% of vanilla; 9 runs, 42–43); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 59 (65% of vanilla; 6 runs, 59–60) nnsight taps (CUDA graphs): 87 (95% of vanilla; 9 runs, 86–87) logit lens every step interp-engine vllm: 51 (57% of vanilla; 9 runs, 50–53); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 63 (69% of vanilla; 9 runs, 62–63); significantly slower than the counterpart (p<0.05) vLLM-Lens: 41 (45% of vanilla; 9 runs, 40–41); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 61 (67% of vanilla; 6 runs, 60–61); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 82 (90% of vanilla; 9 runs, 82–82); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 68 (75% of vanilla; 9 runs, 66–70); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 87 (95% of vanilla; 9 runs, 84–87) vLLM-Lens: 42 (46% of vanilla; 9 runs, 41–42); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 60 (66% of vanilla; 6 runs, 60–60) nnsight taps (CUDA graphs): 87 (95% of vanilla; 9 runs, 86–87) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ nnsight eager: 61 (67% of vanilla; 6 runs, 60–62) nnsight taps (CUDA graphs): 87 (96% of vanilla; 9 runs, 87–87) override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ nnsight eager: 60 (66% of vanilla; 6 runs, 58–61) nnsight taps (CUDA graphs): 87 (96% of vanilla; 9 runs, 87–87) one forward over 512 tokens, capture 1 layer interp-engine vllm: 43 ms (103% of vanilla; 9 runs, 42 ms–43 ms); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 46 ms (96% of vanilla; 9 runs, 44 ms–52 ms); significantly faster than the counterpart (p<0.05) vLLM-Lens: 45 ms (99% of vanilla; 9 runs, 44 ms–46 ms); significantly faster than the counterpart (p<0.05) TransformerLens batched: 101 ms (36% of vanilla vLLM 0.20.2; 3 runs, 93 ms–117 ms) TransformerLens (compile + CUDA graphs): 51 ms (72% of vanilla vLLM 0.20.2; 3 runs, 49 ms–52 ms) nnsight eager: 54 ms (81% of vanilla; 6 runs, 53 ms–57 ms) nnsight taps (CUDA graphs): 55 ms (81% of vanilla; 9 runs, 53 ms–57 ms) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.41 s (56% of vanilla; 9 runs, 1.36 s–1.44 s); significantly faster than the counterpart (p<0.05) TransformerLens batched: 6.64 s (12% of vanilla vLLM 0.20.2; 3 runs, 6.26 s–7.10 s) TransformerLens (compile + CUDA graphs): 23.75 s (3% of vanilla vLLM 0.20.2; 9 runs, 23.13 s–24.76 s) nnsight eager: 1.74 s (45% of vanilla; 6 runs, 1.70 s–1.81 s) nnsight taps (CUDA graphs): 1.59 s (49% of vanilla; 9 runs, 1.54 s–1.63 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ TransformerLens batched: captures are per forward; there is no installed-once block ✗ TransformerLens (compile + CUDA graphs): captures are per forward; there is no installed-once block ✗ nnsight eager: 1.19 s (66% of vanilla; 6 runs, 1.17 s–1.20 s) nnsight taps (CUDA graphs): 1.04 s (75% of vanilla; 9 runs, 1.00 s–1.08 s)
The numbers — Qwen3-8B, one GPU
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens vanilla vLLM 0.20.2 TransformerLens batched TransformerLens (compile + CUDA graphs)
generate 91 67 88 69 88 68 91 ✗ ✗
generate, 8 concurrent 604 471 583 496 580 490 605 ✗ ✗
capture 1 layer, every step · 60 87 67 87 60 · ✗ ✗
capture every layer, every step · 52 86 39 67 32 · ✗ ✗
capture 1 layer, 8 concurrent · 419 562 475 568 387 · ✗ ✗
additive steering, 1 layer · 59 87 67 87 42 · ✗ ✗
logit lens every step · 61 82 51 63 41 · ✗ ✗
linear probe every step · 60 87 68 87 42 · ✗ ✗
zero one attention head every step · 61 87 ✗ ✗ ✗ · ✗ ✗
override the sampled token every step · 60 87 ✗ ✗ ✗ · ✗ ✗
one forward over 512 tokens, capture 1 layer 44 ms 54 ms 55 ms 43 ms 46 ms 45 ms 37 ms 101 ms 51 ms
sweep: 1024 × 1 token, capture 1 layer, per request 0.78 s 1.74 s 1.59 s · · 1.41 s 0.77 s 6.64 s 23.75 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 1.19 s 1.04 s · · ✗ · ✗ ✗
Qwen1.5-MoE-A2.7B (mixture of experts), one GPU Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens TransformerLens batched 25% 50% 75% 100% 125% 150% vanilla vLLM generate interp-engine vllm: 41 (20% of vanilla; 9 runs, 40–42) interp-engine vllm-static: 181 (86% of vanilla; 9 runs, 179–182); significantly slower than the counterpart (p<0.05) vLLM-Lens: 42 (20% of vanilla; 9 runs, 41–42); significantly faster than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 40 (19% of vanilla; 9 runs, 40–42) nnsight taps (CUDA graphs): 205 (98% of vanilla; 9 runs, 204–205); significantly faster than every counterpart (p<0.05) generate, 8 concurrent interp-engine vllm: 309 (46% of vanilla; 9 runs, 298–317) interp-engine vllm-static: 874 (130% of vanilla; 9 runs, 867–876); significantly slower than the counterpart (p<0.05) vLLM-Lens: 312 (46% of vanilla; 9 runs, 309–314); significantly faster than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 303 (45% of vanilla; 9 runs, 295–311) nnsight taps (CUDA graphs): 1,091 (163% of vanilla; 9 runs, 1,090–1,093); significantly faster than every counterpart (p<0.05) capture 1 layer, every step interp-engine vllm: 41 (19% of vanilla; 9 runs, 40–41); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 179 (86% of vanilla; 9 runs, 178–180); significantly slower than the counterpart (p<0.05) vLLM-Lens: 39 (19% of vanilla; 9 runs, 37–40) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 38 (18% of vanilla; 9 runs, 37–39) nnsight taps (CUDA graphs): 202 (97% of vanilla; 9 runs, 202–203); significantly faster than every counterpart (p<0.05) capture every layer, every step interp-engine vllm: 34 (16% of vanilla; 9 runs, 33–34); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 148 (70% of vanilla; 9 runs, 143–153); significantly slower than the counterpart (p<0.05) vLLM-Lens: 33 (16% of vanilla; 9 runs, 32–33); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 35 (17% of vanilla; 9 runs, 34–36); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 200 (95% of vanilla; 9 runs, 200–200); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 304 (45% of vanilla; 9 runs, 299–311); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 863 (129% of vanilla; 9 runs, 856–867); significantly slower than the counterpart (p<0.05) vLLM-Lens: 279 (42% of vanilla; 9 runs, 275–285) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 280 (42% of vanilla; 9 runs, 269–287) nnsight taps (CUDA graphs): 1,023 (152% of vanilla; 9 runs, 1,019–1,026); significantly faster than every counterpart (p<0.05) additive steering, 1 layer interp-engine vllm: 41 (20% of vanilla; 9 runs, 41–42); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 180 (86% of vanilla; 9 runs, 179–180); significantly slower than the counterpart (p<0.05) vLLM-Lens: 36 (17% of vanilla; 9 runs, 35–37); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 38 (18% of vanilla; 9 runs, 37–39) nnsight taps (CUDA graphs): 201 (96% of vanilla; 9 runs, 200–201); significantly faster than every counterpart (p<0.05) logit lens every step interp-engine vllm: 34 (16% of vanilla; 9 runs, 33–35); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 99 (47% of vanilla; 9 runs, 96–102); significantly slower than the counterpart (p<0.05) vLLM-Lens: 35 (17% of vanilla; 9 runs, 34–36); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 38 (18% of vanilla; 9 runs, 37–38); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 188 (90% of vanilla; 9 runs, 187–188); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 40 (19% of vanilla; 9 runs, 39–41); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 179 (85% of vanilla; 9 runs, 178–180); significantly slower than the counterpart (p<0.05) vLLM-Lens: 36 (17% of vanilla; 9 runs, 35–36); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 38 (18% of vanilla; 9 runs, 38–39) nnsight taps (CUDA graphs): 200 (95% of vanilla; 9 runs, 199–200); significantly faster than every counterpart (p<0.05) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ nnsight eager: 38 (18% of vanilla; 9 runs, 36–39) nnsight taps (CUDA graphs): 202 (97% of vanilla; 9 runs, 202–203) override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ nnsight eager: 38 (18% of vanilla; 9 runs, 37–39) nnsight taps (CUDA graphs): 202 (96% of vanilla; 9 runs, 201–202) one forward over 512 tokens, capture 1 layer interp-engine vllm: 36 ms (88% of vanilla; 9 runs, 34 ms–37 ms); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 34 ms (93% of vanilla; 9 runs, 33 ms–34 ms); significantly faster than the counterpart (p<0.05) vLLM-Lens: 37 ms (86% of vanilla; 9 runs, 34 ms–38 ms); significantly faster than the counterpart (p<0.05) TransformerLens batched: 62 ms (46% of vanilla vLLM 0.20.2; 9 runs, 60 ms–63 ms) nnsight eager: 46 ms (68% of vanilla; 9 runs, 45 ms–47 ms) nnsight taps (CUDA graphs): 43 ms (73% of vanilla; 9 runs, 41 ms–47 ms) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 0.82 s (54% of vanilla; 9 runs, 0.80 s–0.91 s); significantly faster than the counterpart (p<0.05) TransformerLens batched: 2.81 s (16% of vanilla vLLM 0.20.2; 9 runs, 2.67 s–3.05 s) nnsight eager: 1.36 s (32% of vanilla; 6 runs, 1.33 s–1.38 s) nnsight taps (CUDA graphs): 1.27 s (35% of vanilla; 9 runs, 1.25 s–1.30 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ TransformerLens batched: captures are per forward; there is no installed-once block ✗ nnsight eager: 0.71 s (62% of vanilla; 9 runs, 0.69 s–0.74 s) nnsight taps (CUDA graphs): 0.66 s (67% of vanilla; 9 runs, 0.64 s–0.67 s)

The vanilla column on this model is vLLM's default engine: CUDA graphs and torch.compile. On this MoE the compiled path is the slow one at 8 concurrent requests. Measured directly on plain vLLM, same prompts and settings: eager 315 tok/s; torch.compile alone 245 (the compiled forward is slower than eager on this model); compile + CUDA graphs, the default, 669; CUDA graphs with compilation off 1,095 (1,099 in the breakable-graph mode nnsight's taps use). Enabling vLLM's own custom kernels under compile (custom_ops=all) changes nothing (668), so it is the inductor-compiled forward itself, not a missing fused op. nnsight taps (1,091) and interp-engine static (874) are therefore not beating vLLM; they run the un-compiled graph path that vLLM's default loses to on this model. Single-request generation is unaffected (210 default vs 205 taps).

The numbers — Qwen1.5-MoE-A2.7B, one GPU
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens vanilla vLLM 0.20.2 TransformerLens batched
generate 210 40 205 41 181 42 209 ✗
generate, 8 concurrent 671 303 1,091 309 874 312 673 ✗
capture 1 layer, every step · 38 202 41 179 39 · ✗
capture every layer, every step · 35 200 34 148 33 · ✗
capture 1 layer, 8 concurrent · 280 1,023 304 863 279 · ✗
additive steering, 1 layer · 38 201 41 180 36 · ✗
logit lens every step · 38 188 34 99 35 · ✗
linear probe every step · 38 200 40 179 36 · ✗
zero one attention head every step · 38 202 ✗ ✗ ✗ · ✗
override the sampled token every step · 38 202 ✗ ✗ ✗ · ✗
one forward over 512 tokens, capture 1 layer 31 ms 46 ms 43 ms 36 ms 34 ms 37 ms 29 ms 62 ms
sweep: 1024 × 1 token, capture 1 layer, per request 0.44 s 1.36 s 1.27 s · · 0.82 s 0.44 s 2.81 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 0.71 s 0.66 s · · ✗ · ✗
Llama-3.2-1B, one GPU Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens TransformerLens batched TransformerLens (compile + CUDA graphs) 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 150 (38% of vanilla; 9 runs, 147–154); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 376 (95% of vanilla; 9 runs, 376–376) vLLM-Lens: 155 (39% of vanilla; 9 runs, 147–158); significantly faster than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 145 (37% of vanilla; 9 runs, 142–148) nnsight taps (CUDA graphs): 372 (94% of vanilla; 9 runs, 371–372) generate, 8 concurrent interp-engine vllm: 1,102 (43% of vanilla; 9 runs, 1,056–1,125) interp-engine vllm-static: 2,405 (94% of vanilla; 9 runs, 2,399–2,413) vLLM-Lens: 1,148 (45% of vanilla; 9 runs, 1,123–1,172); significantly faster than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 1,090 (43% of vanilla; 9 runs, 1,058–1,116) nnsight taps (CUDA graphs): 2,421 (95% of vanilla; 9 runs, 2,417–2,425) capture 1 layer, every step interp-engine vllm: 143 (36% of vanilla; 9 runs, 139–147); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 370 (94% of vanilla; 9 runs, 366–371) vLLM-Lens: 135 (34% of vanilla; 9 runs, 132–138) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 135 (34% of vanilla; 9 runs, 131–137) nnsight taps (CUDA graphs): 366 (93% of vanilla; 9 runs, 366–368) capture every layer, every step interp-engine vllm: 102 (26% of vanilla; 9 runs, 99–104); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 295 (75% of vanilla; 9 runs, 288–302); significantly slower than the counterpart (p<0.05) vLLM-Lens: 96 (24% of vanilla; 9 runs, 94–98); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 119 (30% of vanilla; 9 runs, 117–121); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 361 (91% of vanilla; 9 runs, 360–362); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 1,032 (40% of vanilla; 9 runs, 999–1,050); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 2,339 (92% of vanilla; 9 runs, 2,332–2,345); significantly faster than the counterpart (p<0.05) vLLM-Lens: 882 (35% of vanilla; 9 runs, 859–900); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 940 (37% of vanilla; 9 runs, 924–958) nnsight taps (CUDA graphs): 2,224 (87% of vanilla; 9 runs, 2,209–2,229) additive steering, 1 layer interp-engine vllm: 145 (37% of vanilla; 9 runs, 140–148); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 370 (94% of vanilla; 9 runs, 370–371) vLLM-Lens: 116 (29% of vanilla; 9 runs, 115–119); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 134 (34% of vanilla; 9 runs, 131–136) nnsight taps (CUDA graphs): 365 (92% of vanilla; 9 runs, 362–366) logit lens every step interp-engine vllm: 90 (23% of vanilla; 9 runs, 88–92); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 145 (37% of vanilla; 9 runs, 138–148); significantly slower than the counterpart (p<0.05) vLLM-Lens: 109 (27% of vanilla; 9 runs, 106–111); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 134 (34% of vanilla; 9 runs, 133–136); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 325 (82% of vanilla; 9 runs, 324–325); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 143 (36% of vanilla; 9 runs, 132–147); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 367 (93% of vanilla; 9 runs, 364–368) vLLM-Lens: 112 (28% of vanilla; 9 runs, 110–115); significantly slower than the counterpart (p<0.05) TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only) ✗ nnsight eager: 134 (34% of vanilla; 9 runs, 132–136) nnsight taps (CUDA graphs): 364 (92% of vanilla; 9 runs, 363–364) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and hook points are module outputs (block, attn, mlp), so o_proj's input is not addressable ✗ nnsight eager: 135 (34% of vanilla; 9 runs, 134–137) nnsight taps (CUDA graphs): 367 (93% of vanilla; 9 runs, 366–368) override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ TransformerLens batched: RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ TransformerLens (compile + CUDA graphs): RemoteBridge has no generate(); VLLMDriver.forward raises NotImplementedError for max_new_tokens != 1 (prefill only); and there is no hook on the sampler ✗ nnsight eager: 136 (34% of vanilla; 9 runs, 132–138) nnsight taps (CUDA graphs): 365 (92% of vanilla; 9 runs, 364–366) one forward over 512 tokens, capture 1 layer interp-engine vllm: 14 ms (78% of vanilla; 9 runs, 14 ms–14 ms); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 14 ms (80% of vanilla; 9 runs, 13 ms–14 ms); significantly faster than the counterpart (p<0.05) vLLM-Lens: 15 ms (74% of vanilla; 6 runs, 14 ms–16 ms); significantly faster than the counterpart (p<0.05) TransformerLens batched: 60 ms (14% of vanilla vLLM 0.20.2; 3 runs, 40 ms–72 ms) TransformerLens (compile + CUDA graphs): 13 ms (66% of vanilla vLLM 0.20.2; 3 runs, 13 ms–14 ms) nnsight eager: 19 ms (58% of vanilla; 9 runs, 18 ms–20 ms) nnsight taps (CUDA graphs): 18 ms (60% of vanilla; 9 runs, 17 ms–19 ms) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 0.56 s (40% of vanilla; 6 runs, 0.54 s–0.57 s); significantly faster than the counterpart (p<0.05) TransformerLens batched: 3.73 s (5% of vanilla vLLM 0.20.2; 9 runs, 2.91 s–4.39 s) TransformerLens (compile + CUDA graphs): 8.07 s (2% of vanilla vLLM 0.20.2; 6 runs, 7.68 s–8.50 s) nnsight eager: 0.90 s (25% of vanilla; 3 runs, 0.89 s–0.92 s) nnsight taps (CUDA graphs): 1.07 s (21% of vanilla; 3 runs, 1.01 s–1.11 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ TransformerLens batched: captures are per forward; there is no installed-once block ✗ TransformerLens (compile + CUDA graphs): captures are per forward; there is no installed-once block ✗ nnsight eager: 0.54 s (41% of vanilla; 3 runs, 0.40 s–0.82 s) nnsight taps (CUDA graphs): 0.48 s (46% of vanilla; 3 runs, 0.43 s–0.52 s)
The numbers — Llama-3.2-1B, one GPU
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens vanilla vLLM 0.20.2 TransformerLens batched TransformerLens (compile + CUDA graphs)
generate 395 145 372 150 376 155 359 ✗ ✗
generate, 8 concurrent 2,548 1,090 2,421 1,102 2,405 1,148 2,353 ✗ ✗
capture 1 layer, every step · 135 366 143 370 135 · ✗ ✗
capture every layer, every step · 119 361 102 295 96 · ✗ ✗
capture 1 layer, 8 concurrent · 940 2,224 1,032 2,339 882 · ✗ ✗
additive steering, 1 layer · 134 365 145 370 116 · ✗ ✗
logit lens every step · 134 325 90 145 109 · ✗ ✗
linear probe every step · 134 364 143 367 112 · ✗ ✗
zero one attention head every step · 135 367 ✗ ✗ ✗ · ✗ ✗
override the sampled token every step · 136 365 ✗ ✗ ✗ · ✗ ✗
one forward over 512 tokens, capture 1 layer 11 ms 19 ms 18 ms 14 ms 14 ms 15 ms 9 ms 60 ms 13 ms
sweep: 1024 × 1 token, capture 1 layer, per request 0.22 s 0.90 s 1.07 s · · 0.56 s 0.19 s 3.73 s 8.07 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 0.54 s 0.48 s · · ✗ · ✗ ✗
Llama-3.1-8B, 2048-token prompt, 512 new tokens Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 85 (94% of vanilla; 9 runs, 84–85) interp-engine vllm-static: 87 (97% of vanilla; 9 runs, 87–87) vLLM-Lens: 85 (94% of vanilla; 9 runs, 83–85) nnsight eager: 84 (94% of vanilla; 9 runs, 83–85) nnsight taps (CUDA graphs): 87 (97% of vanilla; 9 runs, 87–87) generate, 8 concurrent interp-engine vllm: 537 (94% of vanilla; 9 runs, 537–538) interp-engine vllm-static: 553 (96% of vanilla; 9 runs, 552–553) vLLM-Lens: 539 (94% of vanilla; 9 runs, 537–541) nnsight eager: 539 (94% of vanilla; 9 runs, 538–541) nnsight taps (CUDA graphs): 558 (97% of vanilla; 9 runs, 557–558) capture 1 layer, every step interp-engine vllm: 84 (94% of vanilla; 9 runs, 83–84); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 87 (97% of vanilla; 9 runs, 86–87) vLLM-Lens: 76 (85% of vanilla; 9 runs, 74–78) nnsight eager: 77 (86% of vanilla; 9 runs, 74–78) nnsight taps (CUDA graphs): 87 (97% of vanilla; 9 runs, 87–87) capture every layer, every step interp-engine vllm: 48 (53% of vanilla; 9 runs, 47–50); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 67 (74% of vanilla; 9 runs, 63–70); significantly slower than the counterpart (p<0.05) vLLM-Lens: 38 (42% of vanilla; 9 runs, 37–38); significantly slower than the counterpart (p<0.05) nnsight eager: 66 (74% of vanilla; 9 runs, 64–67); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 86 (96% of vanilla; 9 runs, 86–86); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 528 (92% of vanilla; 9 runs, 526–532) interp-engine vllm-static: 544 (95% of vanilla; 9 runs, 542–546) vLLM-Lens: 468 (82% of vanilla; 9 runs, 461–473); significantly slower than the counterpart (p<0.05) nnsight eager: 530 (92% of vanilla; 9 runs, 523–534) nnsight taps (CUDA graphs): 553 (96% of vanilla; 9 runs, 552–553) additive steering, 1 layer interp-engine vllm: 84 (93% of vanilla; 9 runs, 83–84); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 87 (97% of vanilla; 9 runs, 86–87) vLLM-Lens: 50 (56% of vanilla; 9 runs, 50–50); significantly slower than the counterpart (p<0.05) nnsight eager: 75 (84% of vanilla; 9 runs, 75–77) nnsight taps (CUDA graphs): 86 (96% of vanilla; 9 runs, 86–87) logit lens every step interp-engine vllm: 65 (72% of vanilla; 9 runs, 64–65); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 64 (72% of vanilla; 9 runs, 63–66); significantly slower than the counterpart (p<0.05) vLLM-Lens: 48 (53% of vanilla; 9 runs, 46–48); significantly slower than the counterpart (p<0.05) nnsight eager: 77 (86% of vanilla; 9 runs, 76–77); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 82 (92% of vanilla; 9 runs, 82–82); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 83 (92% of vanilla; 9 runs, 81–84); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 87 (97% of vanilla; 9 runs, 86–87) vLLM-Lens: 49 (55% of vanilla; 9 runs, 48–50); significantly slower than the counterpart (p<0.05) nnsight eager: 75 (84% of vanilla; 9 runs, 74–76) nnsight taps (CUDA graphs): 86 (96% of vanilla; 9 runs, 86–86) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 77 (85% of vanilla; 9 runs, 75–78) nnsight taps (CUDA graphs): 87 (97% of vanilla; 9 runs, 86–87) override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 76 (85% of vanilla; 9 runs, 74–78) nnsight taps (CUDA graphs): 87 (97% of vanilla; 9 runs, 86–87) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.35 s (56% of vanilla; 9 runs, 1.29 s–1.39 s); significantly faster than the counterpart (p<0.05) nnsight eager: 1.64 s (46% of vanilla; 9 runs, 1.60 s–1.70 s) nnsight taps (CUDA graphs): 1.54 s (49% of vanilla; 9 runs, 1.49 s–1.59 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ nnsight eager: 1.12 s (68% of vanilla; 9 runs, 1.09 s–1.15 s) nnsight taps (CUDA graphs): 1.03 s (73% of vanilla; 9 runs, 1.01 s–1.06 s)
The numbers — Llama-3.1-8B, 2048-token prompt, 512 new tokens
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate 90 84 87 85 87 85
generate, 8 concurrent 574 539 558 537 553 539
capture 1 layer, every step · 77 87 84 87 76
capture every layer, every step · 66 86 48 67 38
capture 1 layer, 8 concurrent · 530 553 528 544 468
additive steering, 1 layer · 75 86 84 87 50
logit lens every step · 77 82 65 64 48
linear probe every step · 75 86 83 87 49
zero one attention head every step · 77 87 ✗ ✗ ✗
override the sampled token every step · 76 87 ✗ ✗ ✗
sweep: 1024 × 1 token, capture 1 layer, per request 0.76 s 1.64 s 1.54 s · · 1.35 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 1.12 s 1.03 s · · ✗
Llama-3.1-8B, 32 concurrent requests Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate, 32 concurrent interp-engine vllm: 1,430 (94% of vanilla; 9 runs, 1,426–1,434) interp-engine vllm-static: 1,456 (96% of vanilla; 9 runs, 1,452–1,461) vLLM-Lens: 1,455 (96% of vanilla; 9 runs, 1,446–1,461) nnsight eager: 1,434 (94% of vanilla; 9 runs, 1,433–1,436) nnsight taps (CUDA graphs): 1,469 (97% of vanilla; 9 runs, 1,465–1,479) capture 1 layer, 32 concurrent interp-engine vllm: 1,371 (90% of vanilla; 9 runs, 1,365–1,383); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 1,408 (92% of vanilla; 9 runs, 1,400–1,419); significantly faster than the counterpart (p<0.05) vLLM-Lens: 1,100 (72% of vanilla; 9 runs, 1,068–1,118); significantly slower than the counterpart (p<0.05) nnsight eager: 1,325 (87% of vanilla; 9 runs, 1,302–1,334) nnsight taps (CUDA graphs): 1,366 (90% of vanilla; 9 runs, 1,363–1,367)
The numbers — Llama-3.1-8B, 32 concurrent
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate, 32 concurrent 1,523 1,434 1,469 1,430 1,456 1,455
capture 1 layer, 32 concurrent · 1,325 1,366 1,371 1,408 1,100
DeepSeek-V2-Lite (MLA + MoE), one GPU Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate vLLM-Lens: 32 (19% of vanilla; 9 runs, 31–32) nnsight eager: 31 (19% of vanilla; 9 runs, 31–31) nnsight taps (CUDA graphs): 161 (98% of vanilla; 9 runs, 161–162) generate, 8 concurrent vLLM-Lens: 237 (27% of vanilla; 9 runs, 235–242) nnsight eager: 232 (27% of vanilla; 9 runs, 231–234) nnsight taps (CUDA graphs): 871 (100% of vanilla; 9 runs, 870–872) capture 1 layer, every step vLLM-Lens: 30 (18% of vanilla; 9 runs, 29–31); significantly faster than the counterpart (p<0.05) nnsight eager: 29 (18% of vanilla; 9 runs, 29–30) nnsight taps (CUDA graphs): 159 (97% of vanilla; 9 runs, 159–159) capture every layer, every step vLLM-Lens: 26 (16% of vanilla; 9 runs, 25–26); significantly slower than the counterpart (p<0.05) nnsight eager: 27 (17% of vanilla; 9 runs, 27–28); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 157 (96% of vanilla; 9 runs, 156–157) capture 1 layer, 8 concurrent vLLM-Lens: 215 (25% of vanilla; 9 runs, 213–217) nnsight eager: 213 (24% of vanilla; 9 runs, 209–216) nnsight taps (CUDA graphs): 810 (93% of vanilla; 9 runs, 805–812) additive steering, 1 layer vLLM-Lens: 28 (17% of vanilla; 9 runs, 28–28); significantly slower than the counterpart (p<0.05) nnsight eager: 29 (18% of vanilla; 9 runs, 29–29); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 158 (96% of vanilla; 9 runs, 158–158) logit lens every step vLLM-Lens: 27 (16% of vanilla; 9 runs, 26–27); significantly slower than the counterpart (p<0.05) nnsight eager: 29 (18% of vanilla; 9 runs, 29–29); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 152 (93% of vanilla; 9 runs, 152–152) linear probe every step vLLM-Lens: 27 (17% of vanilla; 9 runs, 27–28); significantly slower than the counterpart (p<0.05) nnsight eager: 29 (18% of vanilla; 9 runs, 28–29); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 157 (96% of vanilla; 9 runs, 157–157) zero one attention head every step vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 29 (18% of vanilla; 9 runs, 28–29) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 29 (18% of vanilla; 9 runs, 28–30) nnsight taps (CUDA graphs): 159 (97% of vanilla; 9 runs, 158–159) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 0.92 s (54% of vanilla; 9 runs, 0.89 s–1.16 s); significantly faster than the counterpart (p<0.05) nnsight eager: 1.53 s (33% of vanilla; 9 runs, 1.51 s–1.55 s) nnsight taps (CUDA graphs): 1.42 s (35% of vanilla; 9 runs, 1.41 s–1.44 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ nnsight eager: 0.94 s (53% of vanilla; 9 runs, 0.79 s–1.27 s) nnsight taps (CUDA graphs): 0.71 s (71% of vanilla; 6 runs, 0.69 s–0.72 s)
The numbers — DeepSeek-V2-Lite, one GPU
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) vLLM-Lens
generate 164 31 161 32
generate, 8 concurrent 873 232 871 237
capture 1 layer, every step · 29 159 30
capture every layer, every step · 27 157 26
capture 1 layer, 8 concurrent · 213 810 215
additive steering, 1 layer · 29 158 28
logit lens every step · 29 152 27
linear probe every step · 29 157 27
zero one attention head every step · 29 ✗ ✗
override the sampled token every step · 29 159 ✗
sweep: 1024 × 1 token, capture 1 layer, per request 0.50 s 1.53 s 1.42 s 0.92 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 0.94 s 0.71 s ✗
Qwen3.5-0.8B, one GPU Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 48 (12% of vanilla; 9 runs, 46–50) interp-engine vllm-static: 241 (57% of vanilla; 9 runs, 240–242); significantly slower than the counterpart (p<0.05) vLLM-Lens: 48 (11% of vanilla; 9 runs, 47–49) nnsight eager: 47 (11% of vanilla; 9 runs, 45–48) nnsight taps (CUDA graphs): 251 (60% of vanilla; 9 runs, 250–251); significantly faster than every counterpart (p<0.05) generate, 8 concurrent interp-engine vllm: 354 (14% of vanilla; 9 runs, 340–365) interp-engine vllm-static: 1,406 (56% of vanilla; 9 runs, 1,402–1,411); significantly slower than the counterpart (p<0.05) vLLM-Lens: 358 (14% of vanilla; 9 runs, 354–362); significantly faster than the counterpart (p<0.05) nnsight eager: 346 (14% of vanilla; 9 runs, 341–352) nnsight taps (CUDA graphs): 1,545 (61% of vanilla; 9 runs, 1,519–1,552); significantly faster than every counterpart (p<0.05) capture 1 layer, every step interp-engine vllm: 49 (12% of vanilla; 9 runs, 48–50); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 239 (57% of vanilla; 9 runs, 237–240) vLLM-Lens: 35 (8% of vanilla; 9 runs, 34–35); significantly slower than the counterpart (p<0.05) nnsight eager: 45 (11% of vanilla; 9 runs, 43–46) nnsight taps (CUDA graphs): 245 (58% of vanilla; 9 runs, 244–246) capture every layer, every step interp-engine vllm: 40 (10% of vanilla; 9 runs, 39–41) interp-engine vllm-static: 215 (51% of vanilla; 9 runs, 211–218); significantly slower than the counterpart (p<0.05) vLLM-Lens: 32 (8% of vanilla; 9 runs, 31–32); significantly slower than the counterpart (p<0.05) nnsight eager: 41 (10% of vanilla; 9 runs, 40–42) nnsight taps (CUDA graphs): 239 (57% of vanilla; 9 runs, 239–240); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 352 (14% of vanilla; 9 runs, 338–358); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 1,370 (54% of vanilla; 9 runs, 1,252–1,389) vLLM-Lens: 254 (10% of vanilla; 9 runs, 248–261); significantly slower than the counterpart (p<0.05) nnsight eager: 319 (13% of vanilla; 9 runs, 314–322) nnsight taps (CUDA graphs): 1,374 (54% of vanilla; 9 runs, 1,332–1,383) additive steering, 1 layer interp-engine vllm: 49 (12% of vanilla; 9 runs, 48–50); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 239 (57% of vanilla; 9 runs, 237–240) vLLM-Lens: 32 (8% of vanilla; 9 runs, 31–33); significantly slower than the counterpart (p<0.05) nnsight eager: 44 (11% of vanilla; 9 runs, 44–45) nnsight taps (CUDA graphs): 241 (57% of vanilla; 9 runs, 240–242) logit lens every step interp-engine vllm: 36 (8% of vanilla; 9 runs, 35–36); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 89 (21% of vanilla; 9 runs, 84–92); significantly slower than the counterpart (p<0.05) vLLM-Lens: 31 (7% of vanilla; 9 runs, 30–31); significantly slower than the counterpart (p<0.05) nnsight eager: 44 (11% of vanilla; 9 runs, 43–45); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 226 (54% of vanilla; 9 runs, 225–227); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 48 (11% of vanilla; 9 runs, 46–49); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 238 (57% of vanilla; 9 runs, 236–239) vLLM-Lens: 32 (8% of vanilla; 9 runs, 32–33); significantly slower than the counterpart (p<0.05) nnsight eager: 44 (11% of vanilla; 9 runs, 44–45) nnsight taps (CUDA graphs): 238 (57% of vanilla; 9 runs, 229–241) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 45 (11% of vanilla; 9 runs, 44–45) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 45 (11% of vanilla; 9 runs, 42–46) nnsight taps (CUDA graphs): 245 (58% of vanilla; 9 runs, 244–246) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 0.81 s (56% of vanilla; 9 runs, 0.78 s–0.85 s); significantly faster than the counterpart (p<0.05) nnsight eager: 1.30 s (35% of vanilla; 9 runs, 1.25 s–1.36 s) nnsight taps (CUDA graphs): 1.28 s (36% of vanilla; 9 runs, 1.24 s–1.33 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ nnsight eager: 0.91 s (50% of vanilla; 9 runs, 0.74 s–1.31 s) nnsight taps (CUDA graphs): 0.82 s (56% of vanilla; 9 runs, 0.73 s–1.09 s)
The numbers — Qwen3.5-0.8B, one GPU
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate 420 47 251 48 241 48
generate, 8 concurrent 2,522 346 1,545 354 1,406 358
capture 1 layer, every step · 45 245 49 239 35
capture every layer, every step · 41 239 40 215 32
capture 1 layer, 8 concurrent · 319 1,374 352 1,370 254
additive steering, 1 layer · 44 241 49 239 32
logit lens every step · 44 226 36 89 31
linear probe every step · 44 238 48 238 32
zero one attention head every step · 45 ✗ ✗ ✗ ✗
override the sampled token every step · 45 245 ✗ ✗ ✗
sweep: 1024 × 1 token, capture 1 layer, per request 0.45 s 1.30 s 1.28 s · · 0.81 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 0.91 s 0.82 s · · ✗
Qwen3.5-4B, one GPU Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 37 (26% of vanilla; 9 runs, 36–37); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 103 (73% of vanilla; 9 runs, 103–103) vLLM-Lens: 36 (26% of vanilla; 9 runs, 35–37); significantly faster than the counterpart (p<0.05) nnsight eager: 35 (25% of vanilla; 9 runs, 34–36) nnsight taps (CUDA graphs): 106 (75% of vanilla; 9 runs, 105–106) generate, 8 concurrent interp-engine vllm: 274 (32% of vanilla; 9 runs, 270–276); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 627 (73% of vanilla; 9 runs, 626–628); significantly slower than the counterpart (p<0.05) vLLM-Lens: 270 (31% of vanilla; 9 runs, 262–276) nnsight eager: 263 (31% of vanilla; 9 runs, 254–268) nnsight taps (CUDA graphs): 648 (76% of vanilla; 9 runs, 642–653); significantly faster than every counterpart (p<0.05) capture 1 layer, every step interp-engine vllm: 36 (26% of vanilla; 9 runs, 36–37); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 102 (73% of vanilla; 9 runs, 101–103) vLLM-Lens: 26 (19% of vanilla; 9 runs, 25–26); significantly slower than the counterpart (p<0.05) nnsight eager: 33 (24% of vanilla; 9 runs, 32–34) nnsight taps (CUDA graphs): 104 (74% of vanilla; 9 runs, 104–104) capture every layer, every step interp-engine vllm: 28 (20% of vanilla; 9 runs, 27–28); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 87 (62% of vanilla; 9 runs, 84–88); significantly slower than the counterpart (p<0.05) vLLM-Lens: 22 (16% of vanilla; 9 runs, 21–22); significantly slower than the counterpart (p<0.05) nnsight eager: 31 (22% of vanilla; 9 runs, 30–31); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 103 (74% of vanilla; 9 runs, 102–103); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 263 (31% of vanilla; 9 runs, 257–268); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 623 (73% of vanilla; 9 runs, 618–631); significantly faster than the counterpart (p<0.05) vLLM-Lens: 193 (22% of vanilla; 9 runs, 187–197); significantly slower than the counterpart (p<0.05) nnsight eager: 242 (28% of vanilla; 9 runs, 237–246) nnsight taps (CUDA graphs): 604 (70% of vanilla; 9 runs, 602–605) additive steering, 1 layer interp-engine vllm: 36 (25% of vanilla; 9 runs, 35–36); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 102 (73% of vanilla; 9 runs, 102–103) vLLM-Lens: 24 (17% of vanilla; 9 runs, 24–25); significantly slower than the counterpart (p<0.05) nnsight eager: 33 (24% of vanilla; 9 runs, 33–34) nnsight taps (CUDA graphs): 104 (74% of vanilla; 9 runs, 104–104) logit lens every step interp-engine vllm: 28 (20% of vanilla; 9 runs, 28–29); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 60 (43% of vanilla; 9 runs, 58–61); significantly slower than the counterpart (p<0.05) vLLM-Lens: 23 (17% of vanilla; 9 runs, 23–24); significantly slower than the counterpart (p<0.05) nnsight eager: 33 (24% of vanilla; 9 runs, 32–34); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 97 (69% of vanilla; 9 runs, 96–97); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 36 (26% of vanilla; 9 runs, 35–36); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 102 (73% of vanilla; 9 runs, 99–102) vLLM-Lens: 24 (17% of vanilla; 9 runs, 24–24); significantly slower than the counterpart (p<0.05) nnsight eager: 33 (24% of vanilla; 9 runs, 33–34) nnsight taps (CUDA graphs): 104 (74% of vanilla; 9 runs, 103–104) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 33 (24% of vanilla; 9 runs, 32–34) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 33 (24% of vanilla; 9 runs, 33–34) nnsight taps (CUDA graphs): 104 (74% of vanilla; 9 runs, 103–104) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.59 s (60% of vanilla; 9 runs, 1.56 s–1.62 s); significantly faster than the counterpart (p<0.05) nnsight eager: 2.01 s (48% of vanilla; 9 runs, 1.98 s–2.05 s) nnsight taps (CUDA graphs): 2.01 s (48% of vanilla; 9 runs, 1.98 s–2.06 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ nnsight eager: 1.65 s (59% of vanilla; 9 runs, 1.47 s–2.01 s) nnsight taps (CUDA graphs): 1.49 s (65% of vanilla; 9 runs, 1.45 s–1.54 s)
The numbers — Qwen3.5-4B, one GPU
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate 140 35 106 37 103 36
generate, 8 concurrent 857 263 648 274 627 270
capture 1 layer, every step · 33 104 36 102 26
capture every layer, every step · 31 103 28 87 22
capture 1 layer, 8 concurrent · 242 604 263 623 193
additive steering, 1 layer · 33 104 36 102 24
logit lens every step · 33 97 28 60 23
linear probe every step · 33 104 36 102 24
zero one attention head every step · 33 ✗ ✗ ✗ ✗
override the sampled token every step · 33 104 ✗ ✗ ✗
sweep: 1024 × 1 token, capture 1 layer, per request 0.96 s 2.01 s 2.01 s · · 1.59 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 1.65 s 1.49 s · · ✗
Qwen3.6-35B-A3B (mixture of experts), tensor-parallel 2 Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 16 (9% of vanilla; 9 runs, 15–16); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 108 (59% of vanilla; 9 runs, 106–108); significantly slower than the counterpart (p<0.05) vLLM-Lens: 15 (8% of vanilla; 9 runs, 15–16); significantly faster than the counterpart (p<0.05) nnsight eager: 15 (8% of vanilla; 9 runs, 15–15) nnsight taps (CUDA graphs): 119 (65% of vanilla; 9 runs, 118–119); significantly faster than every counterpart (p<0.05) generate, 8 concurrent interp-engine vllm: 115 (16% of vanilla; 9 runs, 113–117); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 491 (67% of vanilla; 9 runs, 479–500); significantly slower than the counterpart (p<0.05) vLLM-Lens: 117 (16% of vanilla; 9 runs, 115–118); significantly faster than the counterpart (p<0.05) nnsight eager: 112 (15% of vanilla; 9 runs, 109–115) nnsight taps (CUDA graphs): 545 (75% of vanilla; 9 runs, 530–557); significantly faster than every counterpart (p<0.05) capture 1 layer, every step interp-engine vllm: 16 (9% of vanilla; 9 runs, 15–16); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 107 (59% of vanilla; 9 runs, 107–107); significantly slower than the counterpart (p<0.05) vLLM-Lens: 13 (7% of vanilla; 9 runs, 13–13); significantly slower than the counterpart (p<0.05) nnsight eager: 14 (8% of vanilla; 9 runs, 14–15) nnsight taps (CUDA graphs): 116 (64% of vanilla; 9 runs, 115–116); significantly faster than every counterpart (p<0.05) capture every layer, every step interp-engine vllm: 13 (7% of vanilla; 9 runs, 13–13); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 74 (40% of vanilla; 9 runs, 72–76); significantly slower than the counterpart (p<0.05) vLLM-Lens: 12 (6% of vanilla; 9 runs, 11–12); significantly slower than the counterpart (p<0.05) nnsight eager: 14 (8% of vanilla; 9 runs, 13–14); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 114 (63% of vanilla; 9 runs, 114–114); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 116 (16% of vanilla; 9 runs, 115–117); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 492 (67% of vanilla; 9 runs, 487–498) vLLM-Lens: 95 (13% of vanilla; 9 runs, 92–97); significantly slower than the counterpart (p<0.05) nnsight eager: 105 (14% of vanilla; 9 runs, 102–106) nnsight taps (CUDA graphs): 487 (67% of vanilla; 9 runs, 483–491) additive steering, 1 layer interp-engine vllm: 16 (9% of vanilla; 9 runs, 16–16); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 107 (59% of vanilla; 9 runs, 107–107); significantly slower than the counterpart (p<0.05) vLLM-Lens: 12 (7% of vanilla; 9 runs, 12–13); significantly slower than the counterpart (p<0.05) nnsight eager: 14 (8% of vanilla; 9 runs, 14–15) nnsight taps (CUDA graphs): 115 (63% of vanilla; 9 runs, 112–116); significantly faster than every counterpart (p<0.05) logit lens every step interp-engine vllm: 12 (7% of vanilla; 9 runs, 12–12); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 38 (21% of vanilla; 9 runs, 37–38); significantly slower than the counterpart (p<0.05) vLLM-Lens: 12 (7% of vanilla; 9 runs, 12–12); significantly slower than the counterpart (p<0.05) nnsight eager: 14 (8% of vanilla; 9 runs, 14–14); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 111 (61% of vanilla; 9 runs, 110–112); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 15 (9% of vanilla; 9 runs, 15–16); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 106 (58% of vanilla; 9 runs, 102–107); significantly slower than the counterpart (p<0.05) vLLM-Lens: 12 (7% of vanilla; 9 runs, 12–13); significantly slower than the counterpart (p<0.05) nnsight eager: 14 (8% of vanilla; 9 runs, 14–15) nnsight taps (CUDA graphs): 115 (63% of vanilla; 9 runs, 114–115); significantly faster than every counterpart (p<0.05) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 14 (8% of vanilla; 9 runs, 14–15) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 14 (8% of vanilla; 9 runs, 14–15) nnsight taps (CUDA graphs): 116 (64% of vanilla; 9 runs, 114–116) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.62 s (50% of vanilla; 9 runs, 1.38 s–1.71 s); significantly faster than the counterpart (p<0.05) nnsight eager: 2.27 s (36% of vanilla; 9 runs, 1.87 s–2.54 s) nnsight taps (CUDA graphs): 2.11 s (39% of vanilla; 9 runs, 1.85 s–2.26 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ nnsight eager: 1.59 s (51% of vanilla; 9 runs, 1.30 s–1.70 s) nnsight taps (CUDA graphs): 1.52 s (54% of vanilla; 9 runs, 1.27 s–1.67 s)
The numbers — Qwen3.6-35B-A3B (MoE), tp=2
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate 182 15 119 16 108 15
generate, 8 concurrent 732 112 545 115 491 117
capture 1 layer, every step · 14 116 16 107 13
capture every layer, every step · 14 114 13 74 12
capture 1 layer, 8 concurrent · 105 487 116 492 95
additive steering, 1 layer · 14 115 16 107 12
logit lens every step · 14 111 12 38 12
linear probe every step · 14 115 15 106 12
zero one attention head every step · 14 ✗ ✗ ✗ ✗
override the sampled token every step · 14 116 ✗ ✗ ✗
sweep: 1024 × 1 token, capture 1 layer, per request 0.82 s 2.27 s 2.11 s · · 1.62 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 1.59 s 1.52 s · · ✗

The three panels above are hybrid gated-delta-net trunks (Qwen3.5-0.8B: 18 of 24 layers are recurrent), and they needed a correctness fix before any taps number could be published: a full CUDA graph captured over such a trunk silently miscomputes prefill (plain vLLM does, with compilation off — the recurrent layers branch on the batch's prefill/decode composition), so a tapped nnsight engine now pins cudagraph_mode="FULL_DECODE_ONLY" on any model vLLM reports as hybrid or attention-free: prefill runs eagerly, decode keeps replay, and tapped generation matches eager exactly. Two consequences show in the numbers. The taps-to-vanilla gap is wider here (60–76%) than on standard trunks (93–97%), because vanilla's torch.compile genuinely pays on these new architectures and taps run without it — yet taps still lead every graph-mode alternative on these models. And the eager engines collapse hardest of any model measured (8–25% of vanilla, identically for all three libraries): a recurrent layer is many small kernels, each a Python round trip, and vanilla is very fast. The head-ablation row is ✗ under taps here because only layer outputs are tapped on these trunks.

Llama-3.1-8B, tensor-parallel 8 Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 72 (23% of vanilla; 9 runs, 70–73); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 289 (92% of vanilla; 9 runs, 288–290) vLLM-Lens: 74 (24% of vanilla; 9 runs, 71–75); significantly faster than the counterpart (p<0.05) nnsight eager: 69 (22% of vanilla; 9 runs, 68–70) nnsight taps (CUDA graphs): 286 (91% of vanilla; 9 runs, 278–290) generate, 8 concurrent interp-engine vllm: 511 (30% of vanilla; 9 runs, 496–524); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 1,540 (91% of vanilla; 9 runs, 1,514–1,554) vLLM-Lens: 525 (31% of vanilla; 9 runs, 516–533); significantly faster than the counterpart (p<0.05) nnsight eager: 496 (29% of vanilla; 9 runs, 469–507) nnsight taps (CUDA graphs): 1,568 (93% of vanilla; 9 runs, 1,557–1,581) capture 1 layer, every step interp-engine vllm: 68 (22% of vanilla; 9 runs, 67–70); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 251 (80% of vanilla; 9 runs, 250–253); significantly slower than the counterpart (p<0.05) vLLM-Lens: 65 (21% of vanilla; 9 runs, 64–68) nnsight eager: 64 (20% of vanilla; 9 runs, 63–65) nnsight taps (CUDA graphs): 284 (91% of vanilla; 9 runs, 282–285); significantly faster than every counterpart (p<0.05) capture every layer, every step interp-engine vllm: 22 (7% of vanilla; 9 runs, 20–24); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 36 (12% of vanilla; 9 runs, 30–40); significantly slower than the counterpart (p<0.05) vLLM-Lens: 36 (11% of vanilla; 9 runs, 34–37); significantly slower than the counterpart (p<0.05) nnsight eager: 57 (18% of vanilla; 9 runs, 56–57); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 276 (88% of vanilla; 9 runs, 275–277); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 440 (26% of vanilla; 9 runs, 426–452) interp-engine vllm-static: 1,082 (64% of vanilla; 9 runs, 993–1,120); significantly slower than the counterpart (p<0.05) vLLM-Lens: 415 (25% of vanilla; 9 runs, 404–423); significantly slower than the counterpart (p<0.05) nnsight eager: 442 (26% of vanilla; 9 runs, 432–450) nnsight taps (CUDA graphs): 1,443 (85% of vanilla; 9 runs, 1,435–1,451); significantly faster than every counterpart (p<0.05) additive steering, 1 layer interp-engine vllm: 70 (22% of vanilla; 9 runs, 68–71); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 251 (80% of vanilla; 9 runs, 250–253); significantly slower than the counterpart (p<0.05) vLLM-Lens: 57 (18% of vanilla; 9 runs, 56–58); significantly slower than the counterpart (p<0.05) nnsight eager: 64 (20% of vanilla; 9 runs, 63–65) nnsight taps (CUDA graphs): 278 (89% of vanilla; 9 runs, 276–279); significantly faster than every counterpart (p<0.05) logit lens every step interp-engine vllm: 23 (7% of vanilla; 9 runs, 22–24); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 30 (10% of vanilla; 9 runs, 19–33); significantly slower than the counterpart (p<0.05) vLLM-Lens: 52 (17% of vanilla; 9 runs, 50–54); significantly slower than the counterpart (p<0.05) nnsight eager: 63 (20% of vanilla; 9 runs, 62–65); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 275 (88% of vanilla; 9 runs, 273–276); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 68 (22% of vanilla; 9 runs, 65–70); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 245 (78% of vanilla; 9 runs, 236–249); significantly slower than the counterpart (p<0.05) vLLM-Lens: 56 (18% of vanilla; 9 runs, 55–56); significantly slower than the counterpart (p<0.05) nnsight eager: 64 (20% of vanilla; 9 runs, 63–65) nnsight taps (CUDA graphs): 276 (88% of vanilla; 9 runs, 273–277); significantly faster than every counterpart (p<0.05) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 64 (20% of vanilla; 9 runs, 62–64) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 65 (21% of vanilla; 9 runs, 64–66) nnsight taps (CUDA graphs): 284 (91% of vanilla; 9 runs, 283–285) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 1.02 s (25% of vanilla; 9 runs, 0.91 s–1.20 s); significantly faster than the counterpart (p<0.05) nnsight eager: 1.21 s (21% of vanilla; 9 runs, 1.15 s–1.31 s) nnsight taps (CUDA graphs): 1.18 s (21% of vanilla; 9 runs, 1.12 s–1.26 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ nnsight eager: 2.24 s (11% of vanilla; 9 runs, 2.01 s–2.54 s) nnsight taps (CUDA graphs): 2.21 s (11% of vanilla; 9 runs, 2.01 s–2.53 s)
The numbers — Llama-3.1-8B, tp=8
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate 313 69 286 72 289 74
generate, 8 concurrent 1,689 496 1,568 511 1,540 525
capture 1 layer, every step · 64 284 68 251 65
capture every layer, every step · 57 276 22 36 36
capture 1 layer, 8 concurrent · 442 1,443 440 1,082 415
additive steering, 1 layer · 64 278 70 251 57
logit lens every step · 63 275 23 30 52
linear probe every step · 64 276 68 245 56
zero one attention head every step · 64 ✗ ✗ ✗ ✗
override the sampled token every step · 65 284 ✗ ✗ ✗
sweep: 1024 × 1 token, capture 1 layer, per request 0.25 s 1.21 s 1.18 s · · 1.02 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 2.24 s 2.21 s · · ✗
Llama-3.1-70B, tensor-parallel 8 Throughput as a share of plain vLLM doing the same generation with nothing attached (100% = vanilla). Hollow = eager, filled = CUDA graphs. Whisker = min–max over runs. nnsight dots are smaller and in front, so a tie shows both. ✗ = cannot express. Hover a dot for the number, its run count and range, and whether it is significantly faster than its nnsight counterpart (exact Mann-Whitney U, p<0.05, ≥3% apart). nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens 25% 50% 75% 100% vanilla vLLM generate interp-engine vllm: 32 (53% of vanilla; 6 runs, 32–33); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 59 (97% of vanilla; 6 runs, 59–59) vLLM-Lens: 32 (53% of vanilla; 9 runs, 32–33); significantly faster than the counterpart (p<0.05) nnsight eager: 30 (50% of vanilla; 6 runs, 30–31) nnsight taps (CUDA graphs): 59 (96% of vanilla; 9 runs, 59–59) generate, 8 concurrent interp-engine vllm: 218 (62% of vanilla; 6 runs, 215–219); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 334 (95% of vanilla; 6 runs, 332–336) vLLM-Lens: 219 (63% of vanilla; 9 runs, 218–221); significantly faster than the counterpart (p<0.05) nnsight eager: 202 (57% of vanilla; 6 runs, 188–209) nnsight taps (CUDA graphs): 331 (94% of vanilla; 9 runs, 308–338) capture 1 layer, every step interp-engine vllm: 31 (50% of vanilla; 6 runs, 30–31); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 55 (90% of vanilla; 6 runs, 55–56); significantly slower than the counterpart (p<0.05) vLLM-Lens: 29 (47% of vanilla; 9 runs, 28–30); significantly faster than the counterpart (p<0.05) nnsight eager: 28 (45% of vanilla; 6 runs, 27–28) nnsight taps (CUDA graphs): 58 (96% of vanilla; 6 runs, 58–58); significantly faster than every counterpart (p<0.05) capture every layer, every step interp-engine vllm: 5 (9% of vanilla; 6 runs, 5–6); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 7 (12% of vanilla; 6 runs, 6–8); significantly slower than the counterpart (p<0.05) vLLM-Lens: 10 (17% of vanilla; 9 runs, 10–11); significantly slower than the counterpart (p<0.05) nnsight eager: 24 (39% of vanilla; 6 runs, 23–24); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 57 (94% of vanilla; 6 runs, 57–58); significantly faster than every counterpart (p<0.05) capture 1 layer, 8 concurrent interp-engine vllm: 189 (54% of vanilla; 6 runs, 186–192) interp-engine vllm-static: 279 (80% of vanilla; 6 runs, 276–282); significantly slower than the counterpart (p<0.05) vLLM-Lens: 173 (49% of vanilla; 9 runs, 153–179); significantly slower than the counterpart (p<0.05) nnsight eager: 185 (53% of vanilla; 6 runs, 181–188) nnsight taps (CUDA graphs): 323 (92% of vanilla; 9 runs, 321–325); significantly faster than every counterpart (p<0.05) additive steering, 1 layer interp-engine vllm: 31 (51% of vanilla; 6 runs, 31–31); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 56 (91% of vanilla; 6 runs, 55–56); significantly slower than the counterpart (p<0.05) vLLM-Lens: 21 (34% of vanilla; 9 runs, 17–22); significantly slower than the counterpart (p<0.05) nnsight eager: 27 (44% of vanilla; 6 runs, 26–28) nnsight taps (CUDA graphs): 58 (95% of vanilla; 9 runs, 58–58); significantly faster than every counterpart (p<0.05) logit lens every step interp-engine vllm: 16 (26% of vanilla; 6 runs, 16–16); significantly slower than the counterpart (p<0.05) interp-engine vllm-static: 21 (35% of vanilla; 6 runs, 21–22); significantly slower than the counterpart (p<0.05) vLLM-Lens: 21 (34% of vanilla; 9 runs, 20–21); significantly slower than the counterpart (p<0.05) nnsight eager: 28 (45% of vanilla; 6 runs, 28–28); significantly faster than every counterpart (p<0.05) nnsight taps (CUDA graphs): 58 (95% of vanilla; 6 runs, 58–58); significantly faster than every counterpart (p<0.05) linear probe every step interp-engine vllm: 30 (50% of vanilla; 6 runs, 30–31); significantly faster than the counterpart (p<0.05) interp-engine vllm-static: 56 (91% of vanilla; 6 runs, 55–56); significantly slower than the counterpart (p<0.05) vLLM-Lens: 22 (36% of vanilla; 6 runs, 22–22); significantly slower than the counterpart (p<0.05) nnsight eager: 28 (46% of vanilla; 6 runs, 27–29) nnsight taps (CUDA graphs): 58 (95% of vanilla; 6 runs, 58–58); significantly faster than every counterpart (p<0.05) zero one attention head every step interp-engine vllm: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ interp-engine vllm-static: no write to z / o_proj input: steering ops are add, orthogonal, projection_cap at residual points ✗ vLLM-Lens: hooks fire on decoder-layer outputs (and inputs) only; o_proj's input is not a hook point ✗ nnsight eager: 28 (46% of vanilla; 6 runs, 27–29) nnsight taps (CUDA graphs): o_proj is not tapped on this engine (taps carry layer outputs only) ✗ override the sampled token every step interp-engine vllm: no hook on sampling: generation is the engine's own ✗ interp-engine vllm-static: no hook on sampling: generation is the engine's own ✗ vLLM-Lens: no hook on sampling: generation is the engine's own ✗ nnsight eager: 28 (47% of vanilla; 6 runs, 28–29) nnsight taps (CUDA graphs): 58 (96% of vanilla; 6 runs, 58–58) sweep: 1024 × 1 token, capture 1 layer, per request vLLM-Lens: 5.70 s (55% of vanilla; 9 runs, 5.16 s–6.38 s) nnsight eager: 5.51 s (57% of vanilla; 6 runs, 5.44 s–5.61 s) nnsight taps (CUDA graphs): 5.12 s (62% of vanilla; 9 runs, 3.43 s–5.56 s) sweep: 1024 × 1 token, capture 1 layer, edit() once vLLM-Lens: no persistent-block equivalent for capture; persistent hooks cover the hook path ✗ nnsight eager: 6.24 s (51% of vanilla; 6 runs, 5.91 s–6.77 s) nnsight taps (CUDA graphs): 5.70 s (55% of vanilla; 9 runs, 5.33 s–6.29 s)
The numbers — Llama-3.1-70B, tp=8
workload vanilla vLLM nnsight eager nnsight taps (CUDA graphs) interp-engine vllm interp-engine vllm-static vLLM-Lens
generate 61 30 59 32 59 32
generate, 8 concurrent 351 202 331 218 334 219
capture 1 layer, every step · 28 58 31 55 29
capture every layer, every step · 24 57 5 7 10
capture 1 layer, 8 concurrent · 185 323 189 279 173
additive steering, 1 layer · 27 58 31 56 21
logit lens every step · 28 58 16 21 21
linear probe every step · 28 58 30 56 22
zero one attention head every step · 28 ✗ ✗ ✗ ✗
override the sampled token every step · 28 58 ✗ ✗ ✗
sweep: 1024 × 1 token, capture 1 layer, per request 3.16 s 5.51 s 5.12 s · · 5.70 s
sweep: 1024 × 1 token, capture 1 layer, edit() once · 6.24 s 5.70 s · · ✗

The two panels above extend the tensor-parallel series to all eight cards (three trials for 8B, two for 70B, on a shared node — runs that overlapped another user's job were dropped, and the trials that remain agree within 3%). The trend from tp=2 and tp=4 simply continues. Plain vLLM keeps scaling (8B: 92 → 148 → 229 → 313 tok/s from one to eight cards; 70B: 37 → 61 from four to eight), and the graph engines follow it: capturing a layer every step, nnsight taps hold 91% of vanilla on 8B and 95% on 70B, interp-engine static 80% and 90%. The eager engines do not move at all — nnsight eager, interp-engine's hooked engine and vLLM-Lens sit at 64–74 tok/s on 8B at every card count, which is now 20–24% of vanilla, and at 28–32 on 70B (46–53%; the heavier step hides more of the per-module handoff). Where the libraries differ is what they serve under graphs: every-layer capture on 70B is 57 tok/s under taps against 5–10 for the three engines that gather the tensors out of the worker. One number goes the other way: the edit()-once sweep, the fastest capture on one GPU, gets slower with every rank added while the per-request trace does not — on 8B, 1.05 s at tp=2, 1.35–1.46 s at tp=4, 2.2 s at tp=8, against a per-request trace steady at 1.2–1.4 s (70B/tp=8: 5.7 against 5.1). An installed block runs its saves on every rank; a per-request one collects once. Past four cards, trace the sweep.

The harness (ie-bench/: one runner per library, common.py for the rows, report.py and the chart script), the raw results-*.jsonl and every log live alongside nnsight's tests; every number above was produced by it.