Repository navigation
0.8.2 is a bug-fix release. It frees GPU memory that stopped traces, failed backward passes and edits kept allocated. It fixes several cases where vLLM returned wrong values without raising, under tensor parallelism and with concurrent requests. It supports tensor parallelism on transformers 5.21, and it pins vLLM to 0.27 and httpx below 1.0.
If you trace with vLLM under tensor parallelism, with LoRA, or with several requests at once, read the vLLM section below: some results from earlier versions may need re-running.
Highlights
Stopped traces, failed backward passes and edits no longer hold GPU memory
tracer.stop()(#790). The exception that ends a stopped trace stayed alive in a reference cycle. The cycle held every model frame the stop unwound, and with them those layers' activations and, during generation, the KV cache. GPU allocations don't trigger Python's cyclic garbage collector, so a loop of stopped traces kept growing: Llama-3.2-1B at 16×512 tokens, stopped at block 8, filled a 48 GB A6000 in about a dozen traces. A stopped trace now frees them when it ends. The vLLM runner keeps a stop for error reporting and is not changed by this fix.- A backward pass that fails partway (#793). If the backward raised while a
with loss.backward():block was still waiting on a.grad, for example on an out-of-memory error, the block stayed suspended and held the forward's tensors and autograd graph. When the trace ran on a thread that then exited, nothing could free them; on a server replica this held about 65 GiB for a week. The block is now unwound when the backward fails. - Edits (#783, @tivris). Values bound inside a
model.edit()block stayed referenced until the next trace replaced them. With gpt2 and a 681-token prompt, a one-layer steering edit kept 545 MiB alive between traces. They are now freed when the trace ends.
vLLM: values that came back wrong without an error
If you used any of the setups below on 0.8.1 or earlier, re-run those results on 0.8.2.
Tensor parallelism (tensor_parallel_size > 1)
qkv_projandgate_up_projpack several projections into each rank's shard, and their gathered output came back in rank order ([q0 k0 v0 | q1 k1 v1]) instead of the single-GPU layout ([q | k | v]). Per-head slices andgate, up = x.chunk(2, -1)were wrong. The gather now returns the single-GPU layout, and edits are split back per rank (#817). On Qwen2.5-0.5B at tp=2, the relative error against tp=1 went from 0.78 to 0.0046 forqkv_proj.outputand from 0.99 to 0.0076 forgate_up_proj.output.- With fewer KV heads than ranks, K and V came back once per rank. The gather now keeps one copy (#817).
- With
enable_lora=True, a block read only one rank's shard of each linear layer, and its edits went back unsplit. LoRA-wrapped layers are now gathered like the layers they wrap (#820).
Concurrent requests
- Saves were tracked in one set shared by every request on the engine, so a request could lose a save or receive a name it never saved. In a sustained run, 20 of 60 requests came back with such a name. Each request now has its own set (#804).
- A block reading
logits_processorwas given every request's rows, and an edit to one request's row could change another request's token. Reads and edits now see only the block's own row (#813). - A list, dict or set that an installed edit (
model.edit()) captured from the surrounding code was one object shared by every request. Each request's output also carried every earlier request's entries. Each request's copy of the edit now starts with its own (#812). - After a
KeyboardInterruptduring a synchronous trace orgenerate, the interrupted requests kept running and their outputs arrived with the next trace's, so its variables held the interrupted run's values. An interrupted run now aborts its requests (#808).
Tensor parallelism on transformers 5.21
transformers 5.21 shards embed_tokens on every model with a TP plan and allows uneven shards. On 0.8.1, reading embed_tokens.output raised an AssertionError on every sharded model, and a vocabulary that tp_size doesn't divide failed at the next collective. Both work now, and the two new sharding styles, embedding_colwise and replicate, are handled (#791).
Upgrade notes
- vLLM is pinned to 0.27. The
vllmextra now requiresvllm>=0.27.1,<0.28(#801). Without a pin,pip install "nnsight[vllm]"installed vLLM 0.32, where taps stop firing after the prefill without an error. Moving past 0.27 means porting to vLLM's V2 model runner, which is separate work. - httpx is capped below 1.0 (#792). The httpx 1.0 previews remove
TimeoutandAsyncClient, so every remote call failed with anAttributeError. - vLLM refuses three configurations when the model is built, each with a
NotImplementedErrorthat says what to use instead:speculative_config(#795). A speculative step has several logits rows per request, and a block is scoped to one.data_parallel_size > 1(#814). Onmode="async", requests on any engine but the first came back with no saves and no error. The synchronous engine already refused it.- Ray's compiled-graph executor,
VLLM_USE_RAY_V2_EXECUTOR_BACKEND=0(#815). It runs the forward on a different thread, sotracer.resultfailed and some saves came back unbound. vLLM's default Ray executor is unaffected.
- Two vLLM behaviour changes (#795):
- On a structured-output request, a block now reads and edits the logits after vLLM applies the grammar mask.
- A request whose block raises under
ignore_eosnow runs tomax_tokenswith sampled tokens, instead of filling its output with eos tokens.
- Edit replays (#783). If a
model.edit()block reads a name that this trace's run of the block did not bind, it now raisesNameErrorinstead of reading the previous trace's value. nnsight.ndif.get_remote_env:force_refreshis now keyword-only (#729).
Other fixes and improvements
vLLM
- One request's failure no longer costs the other requests in the same collect their values. A block that raises after reading
tracer.result(#803) and a saved value that can't be pickled (#810) are now errors on their own request only. - A trace's real error is no longer hidden when an installed edit ends with
tracer.stop()(#805). - A block whose request failed outside the block, or an installed edit cleared mid-request, no longer stays suspended holding its scope (#806). Workers of finished requests are released at the end of each collect instead of staying held while the engine is idle (#807).
- Closing an async trace's stream early now aborts the request instead of letting it run to
max_tokens(#809). nnsight-serve: a long-running server could run an earlier trace's code for a later multi-invoke trace from the same file, and its source caches grew with every client filename. Each build now runs insidennsight.tracing.globals.block_scope, which restores the caches afterwards (#811).
Remote
- The remote environment is cached per host, and
nnsight.compare(host=...)compares against a specific server. Before, a trace with a per-call host (remote="http://host:port") read the configured server's environment. A trailing slash on a host no longer produces//requestURLs (#729).
Batching and performance
- A
.skip()across several invokes now rebuilds the module's output as its own type. A namedtuple output no longer raisesTypeError, and a dict subclass such as a transformersModelOutputis no longer turned into a plaindict(#800, @abhishekcpp007). - Preparing each invoke now reads the generate-argument names once per pipeline class instead of on every invoke, which takes 1 µs instead of 83 µs. A 256-invoke trace on an A6000 went from 128.8 ms to 106.3 ms (#779).
Docs
Check your
.output[0]. Under transformers 5, GPT-2 and Llama blocks return their hidden states as a plain tensor, not a tuple, soh[i].output[0]is the first batch row. A batched edit written ash[5].output[0][:] = 0changes only the first prompt and raises no error; writeh[5].output[:] = 0. Modules that still return tuples, such as vLLM's layers ((hidden, residual)), are unchanged. The docs and docstrings now use.outputthroughout (#796).
- The 0.8 migration page covers two more breaking changes:
.nns_output/.nns_inputare now.E_output/.E_input, andautomodel=no longer selects the model class; useTransformersModel(repo, task=...)(#798). - On vLLM, a block under
tracer.iterortracer.all()can see one step past a stop token or stop string, because async scheduling starts the next step before the token is known (#816).
What's Changed
- tp: support transformers 5.21's embedding and uneven-shard changes by @JadenFiotto-Kaufman in #791
- backward: unwind a block still parked on a gradient when the backward fails by @JadenFiotto-Kaufman in #793
- remote: hold httpx below 1.0 by @JadenFiotto-Kaufman in #792
- A swallowed tracer.stop() no longer keeps the frames it unwound by @JadenFiotto-Kaufman in #790
- tracing: free what an edit binds when its trace ends by @tivris in #783
- transformers: read the generate-argument names once per pipeline class by @JadenFiotto-Kaufman in #779
- ndif: cache the remote environment per host by @JadenFiotto-Kaufman in #729
- docs: read a block's .output as the tensor it is, not output[0] by @JadenFiotto-Kaufman in #796
- docs: add the
.E_outputmove andautomodel=to the 0.8 migration page by @JadenFiotto-Kaufman in #798 - vllm: simplify the GPU model runner by @JadenFiotto-Kaufman in #795
- vllm: keep a raise after tracer.result to its own request by @JadenFiotto-Kaufman in #803
- vllm: unwind a block that is dropped while still suspended by @JadenFiotto-Kaufman in #806
- vllm: let go of out-of-batch workers once their request is collected by @JadenFiotto-Kaufman in #807
- vllm: abort what an interrupted synchronous run leaves in the engine by @JadenFiotto-Kaufman in #808
- vllm: abort the request when an async trace's stream is closed early by @JadenFiotto-Kaufman in #809
- vllm: make a save that will not pickle its own request's error by @JadenFiotto-Kaufman in #810
- vllm: give each request's copy of an edit its own containers by @JadenFiotto-Kaufman in #812
- vllm: scope logits_processor to its request's row by @JadenFiotto-Kaufman in #813
- vllm: refuse data parallelism at construction by @JadenFiotto-Kaufman in #814
- vllm: refuse Ray's compiled-graph executor by @JadenFiotto-Kaufman in #815
- docs: say a block can see one step past a stop on vLLM by @JadenFiotto-Kaufman in #816
- vllm: keep a request's real error over an edit's stop by @JadenFiotto-Kaufman in #805
- serve: confine each build's block source to that build by @JadenFiotto-Kaufman in #811
- vllm: give each request's worker its own save-set by @JadenFiotto-Kaufman in #804
- batching: rebuild a batched skip's output as its own container type by @abhishekcpp007 in #800
- vllm: gather a fused projection in the layout tp=1 has by @JadenFiotto-Kaufman in #817
- vllm: gather a LoRA-wrapped layer's shard under tensor parallelism by @JadenFiotto-Kaufman in #820
- vllm: pin the vllm extra to 0.27 by @JadenFiotto-Kaufman in #801
- vllm: audit fixes (#804, #805, #808-#816) by @JadenFiotto-Kaufman in #819
- Dev by @JadenFiotto-Kaufman in #821
New Contributors
- @tivris made their first contribution in #783
- @abhishekcpp007 made their first contribution in #800
Full Changelog: v0.8.1...v0.8.2