vllm: rework #662 against 0.8 — token axis + NNSIGHT_VLLM_CLONE_READS - #727
Conversation
vLLM suite run — hakone, vLLM 0.27.1, 4x A100-80GBCloses the "
A set-diff of every Both residuals are pre-existing on
I checked the two mechanisms by which these commits could have touched that TP test before running the baseline: the dropped CPU suite is unchanged from the description: 1054 passed, and the 16 Unrelated finding worth its own issueThe first attempt reported 130 failed, all with Env: 🤖 Generated with Claude Code |
701e3d6 to
f1de399
Compare
Rework of #662 against current
0.8.What happened to the original eight
Four of #662's items already landed on
0.8and are dropped:0.8vllm.py:965,async_backend.py:153,serve/backend.py:68nnsight-servepackagingpyproject.tomlserveextra +[project.scripts]docs/developing/vllm-integration.mdintervention-gaps/into the docsTwo more are dropped deliberately:
enable_prefix_caching=Falseby default — obsolete.0.8solves this per request:_attach_mediatorssetsparam.skip_reading_prefix_cache = Trueon every traced request (vllm.py:1042). That is strictly better — the cache stays on for untraced traffic while traced requests recompute. The one case it cannot cover, anedit()registration riding a request nnsight did not create, already warns at registration time (registration.py:77). Flipping the engine-wide default now would give back throughput for nothing.async_backend.py:81still refuses more than one prompt) and the design in vllm: follow-ups from the v0.7.0 merges #662 is sound: one engine request per invoke, fan the streams into one queue, merge invoke-shared saves onto the last finished output. But0.8moved collection out of the backend intoengines/engine.py(acollect/attach), and vllm: follow-ups from the v0.7.0 merges #662's implementation reverts to rawcollective_rpc("collect_nnsight", ...). That drops two things: theRequestOutputthatacollectpasses down so a block parked ontracer.resultcan be served, andattach's merge ofregisteredvalues and per-sequence (n > 1) saves. It needs re-plumbing rather than redesigning, and that is better reviewed on its own.What is here
fix(vllm): narrow and widen on the token axis, not always dim 0A model vLLM has no definition of its own is served through its Transformers backend, which runs the wrapped HuggingFace module with a leading singleton batch dim. Its decoder layers emit
[1, total_tokens, hidden], so tokens sit on dim 1.VLLMBatcherinherited the base's dim-0-only row math, which gates onshape[0] == total. On a 3-D activation that reads1 != total, the tensor is called unbatched and passes through whole: every read hands the block every in-flight request's tokens, and every write is discarded. Nothing surfaces — a passthrough is indistinguishable from a tensor that legitimately is not batched — so an intervention quietly does nothing as soon as a second request shares the step.Unlike #662, this does not touch
src/nnsight/intervention/batching.py. The base already documents_narrow_tensor/_widen_tensoras the extension point for non-dim-0 layouts, andDiffusionBatcheralready uses it that way, so the new_batch_dimhook in core was not needed.VLLMBatcheroverrides both and locates the axis with_token_dim.The override drops the base's
_nnsight_batchview marker, which exists for backward to redirect a batch slice through its storage-owning base. Backward is not supported on the vLLM path, so it had no reader.Known gap, documented not guessed at:
Interleaver.replaytrims a padded tap tensor witht[:total], still dim-0-only, so taps over a Transformers-backend model can be served the step's padding rows. Deciding the token axis of a padded tensor needs a rule_token_dimcannot supply — its whole test isshape[dim] == total, which a padded tensor fails by construction. Noted in the dev doc and in Limitations.feat(vllm): NNSIGHT_VLLM_CLONE_READS serves copies instead of viewsReplaces #662's clone-inside-
tracing.save()approach to #661.What a block reads is a view into engine memory, and vLLM's fused kernels (
fused_add_rms_norm, MLA's in-place rotation of theq_projoutput) overwrite those buffers a few ops later, so a value kept past its read point comes back holding a later layer's data.VLLMBatcher.narrownow clones what it serves whenNNSIGHT_VLLM_CLONE_READSis set.Two reasons for moving it out of
save():narrowis the single chokepoint every read passes through —.save(),tracer.cache()(which narrows atinterleaver.py:839), appends undertracer.iter, and taps, whose replay uses the same handoff. A clone at the save point only ever covered the first: a saved container is marked once while its elements keep aliasing, which is the failure modedocs/models/vllm.mdactually warns about.save()returns for every backend.An env var rather than a
CONFIG.APPfield because the batcher that narrows is built in the engine's worker process (GPUModelRunner.load_model); a field set client-side would never reach it. Read once per batcher, so it must be set beforeVLLM(...). Falsy spellings match the onesNNSIGHT_DISABLE_CPP_BACKTRACEalready accepts.Off by default, and here is the reason to review it. It costs in-place edits: with nothing aliasing engine memory,
layers[10].output[0][:] += vwrites to the copy and the model never sees it — as silent as the bug it fixes..output/.inputregister notransformwrite-back, so they rely purely on aliasing; anepropertythat does register one is unaffected, and replacement (module.output = out) routes throughwidenand lands either way. Both halves are pinned by tests and both docs give the form that works under either setting.The cheap alternative that would keep in-place edits — copying the clone back into the view after the read — needs a hook in core
interleaver.pyand collides with theepropertytransform slot, so it is not attempted here.Testing
23 new CPU-only tests in
tests/test_batching.py, no engine required. The three Transformers-backend cases fail on the old batcher (the in-place one writes all 8 tokens instead of its 5); the three native cases pass either way and pin no regression.Full CPU suite: 1054 passed, 16 failed — all 16 are
tests/test_tensor_parallel_rules.pyand reproduce unchanged on0.8, so they pre-date this branch.tests/vllm/was not run. The box available had a GPU but only vLLM 0.15.1, and the integration targets 0.27.x. The end-to-end vLLM behaviour of both commits is unverified and wants a run before merge.🤖 Generated with Claude Code