Skip to content

Move vLLM to 0.29.0 and transformers to v5 - #1127

Closed
tengomucho wants to merge 43 commits into
mainfrom
vllm-update
Closed

tengomucho wants to merge 43 commits into
mainfrom
vllm-update

Conversation

@tengomucho

@tengomucho tengomucho commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

This PR is to move vLLM from 0.23.0 to 0.29.0. The reason is to close the security advisories raised by dependabot and prepare a release with less CVE risks.
Since vLLM 0.23 was the last one accepting transformers 4.x, and update to transformers v5 was also necessary, together with the update to huggingface_hub v1.

Even if recent vLLM declares a dependency to torch==2.13.0, those seem to be metadata only, since vLLM only tests with one version of pytorch and supports only that version, but that can be overridden and that is what is done here, because the latest torch supported by SDK 2.31 is 2.9.1.

All the changes were done with an agent. The Claude description details for this PR follows.

Dependencies

package before after
transformers 4.57.1 5.17.0
vllm 0.23.0 0.29.0
huggingface_hub >= 0.35.3 >= 1.0.0
peft 0.17.0 0.18.1
diffusers 0.35.* 0.37.*
compel 2.1.1 2.4.0

peft, diffusers and compel move to their first releases that work with transformers v5 —
the previous pins fail at import.

The neuronx_distributed shim

neuronx_distributed/pipeline/trace.py:43 imports transformers.utils.fx, deleted in
transformers v5 along with the rest of torch.fx support, so import neuronx_distributed
fails outright. The newest published wheel (0.19.28492) still has it.

_optimum_neuron_fx_shim.py appends a finder to sys.meta_path that serves a minimal
stand-in module. Being appended, it is consulted only once the regular finders have failed,
so transformers 4.x keeps its real module and the shim retires itself the day AWS guards
that import.

It is installed at interpreter startup by optimum-neuron-fx-shim.pth, because the
processes parallel_model_trace spawns import neuronx_distributed while unpickling their
work item, before any optimum module: tensor parallel export of encoder-decoder models dies
without it. Nothing is imported until something actually asks for transformers.utils.fx,
so the hook costs a few milliseconds and pulls in neither torch nor transformers.

Note that an editable install does not get the path configuration file, so on a development
checkout the shim only covers the current process.

Two bugs this surfaced

  • SmolVLM vision positions. The patch position ids were bucketed from
    i/N * (1 - 1e-6), copied from transformers 4.57. The bucket boundaries sit exactly at
    multiples of 1/N, so that factor pushes every coordinate just below its own boundary:
    31 of 32 positions were shifted by one, in both dimensions. transformers v5 replaced the
    scaling with a clamp, which is what made the copy visibly wrong — the vision features
    differed from the reference by 58.8 against a magnitude of 114, and agree to 8e-05 now.
  • --data_parallel_size reached vLLM. Every other option of optimum-cli neuron serve
    is declared in both spellings; this one only in the dashed form, so the underscore
    spelling fell through to vLLM, which served the model from its own data parallel engines,
    all of them claiming every visible core.

API changes

  • RoPE: config.rope_theta and config.rope_scaling collapsed into a single
    config.rope_parameters dict, which Gemma3 nests per layer type. All call sites go
    through one helper. The deprecated config.rope_scaling now always returns a dict, so
    every is not None check on it had to move to the rope type — silently, had it been left.
  • Traced outputs: transformers v4 turned its model outputs into a tuple while tracing
    (through config.torchscript), and the neuron models read them positionally. v5 dropped
    that, so the traced models returned dicts.
  • Mixture of experts: the expert weights are now a single fused tensor per projection
    instead of one linear layer per expert (mixtral, qwen3_moe).
  • Beam search is removed from the legacy seq2seq generation mixin: it was vendored
    transformers v4 code built on BeamSearchScorer. generate() rejects num_beams > 1
    explicitly. Exporting with num_beams > 1 still works, but the result can no longer
    generate.
  • The question-answering pipeline is dropped, following its removal from transformers.
    NeuronModelForQuestionAnswering itself is untouched.
  • Smaller replacements: download_url/is_remote_url, is_safetensors_available,
    HfFolder, get_preprocessor, find_pruneable_heads_and_indices,
    EncoderDecoderCache.from_legacy_cache, CLIPFeatureExtractor, the pipeline framework
    attribute, the GreedySearch*/Sample* output aliases, and configs no longer carrying
    pad_token_id or resolved generation parameters.

vLLM 0.29

WorkerBase.load_model gained a load_dummy_weights keyword, the entrypoint modules moved
under vllm.entrypoints.launchers, and the engine core now asks every worker which KV cache
layouts it supports — answered through vLLM attention backends, of which a neuron worker has
none. The sampler override and the picklable ModelConfig patch still match 0.29 exactly.

For the record: vLLM 0.29 deprecates Model Runner V1 with removal targeted at 0.32. Our
runner is our own class so nothing breaks now, but it will need revisiting.

Training is out of scope

The training layer is not migrated and not tested. One guard was needed: neuron
configuration deserialization registered its classes by importing both the inference and
the training configurations, so a broken training import made loading any inference
configuration fail.

Before submitting

  • Did you write any new necessary tests?

tengomucho and others added 30 commits September 11, 2026 09:16
WorkerBase.compile_or_warm_up_model() now returns a CompilationTimes
named tuple, which the executor reduces with max(); returning None
raises AttributeError. Return a zeroed CompilationTimes instead.

set_random_seed() now delegates to Platform.manual_seed_all(), which
is abstract in the base class, so init_device() failed before the
model was ever loaded. Add a no-op override.

Also drop initialize_cache(): it no longer exists on WorkerBase and
has no callers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes 25 open advisories over 0.16.0 with no change to torch,
transformers or tokenizers.

vLLM 0.23.0 declares torch==2.11.0, but torch-neuronx requires
torch 2.9.x. That pin is resolver metadata only: vLLM imports and
runs fine on torch 2.9.1, so it is overridden via [tool.uv]
override-dependencies. Note this is a uv feature -- a plain
`pip install optimum-neuron[vllm]` will still hit the conflict.
Every install path in CI and docker/vllm already uses uv.

0.23.0 is the last vLLM that still accepts transformers 4.x; it
already warns that the v4 codepath is removed in 0.24.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings transformers v5 compatibility fixes in the exporters and the
read-only config handling for sentence-transformers >= 5, which is the
version already pinned in the test extra.

No code change is required: every optimum API used here (ExporterConfig,
OptimizedModel, TasksManager, the dummy input generators and the
normalized configs) is unchanged. The only removals are the AutoGPTQ
helpers, the TensorFlow framework detection and the ipex pipeline
accelerator, none of which are referenced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
neuronx_distributed unconditionally imports transformers.utils.fx, which
no longer exists in transformers v5, so `import neuronx_distributed`
fails outright. Register a minimal stand-in module when the real one is
absent, so this is a no-op on transformers 4.x and stops doing anything
once the import is guarded upstream.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- drop the remote URL branch when loading a neuron config: transformers
  removed download_url / is_remote_url,
- check safetensors availability with the local import helpers,
- read the token with huggingface_hub.get_token, HfFolder is gone in
  huggingface_hub v1,
- vendor get_preprocessor, removed along with transformers.onnx,
- drop the dead T5 head pruning path, head pruning is gone,
- drop the question-answering pipeline, removed from transformers,
- use the canonical Generate*Output generation output classes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 merged config.rope_theta and config.rope_scaling into a
single config.rope_parameters dict, with models using several attention
types (Gemma3) nesting one dict per layer type. Route every call site
through a single helper.

The deprecated config.rope_scaling now always returns a dict, so the
checks that relied on it being None had to move to the rope type.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The vendored generation code relies on BeamSearchScorer, removed in
transformers v5. Beam search is dropped from the traced seq2seq models
rather than vendored further: generate() now rejects num_beams > 1 with
an explicit error, and the beam tests are removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WorkerBase.load_model now takes a load_dummy_weights keyword argument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vLLM 0.24 and later require transformers v5, so both move together, and
transformers v5 in turn requires huggingface_hub v1. peft, diffusers and
compel are bumped to their first releases supporting transformers v5.

vLLM declares torch 2.13 and a matching torchcodec, which torch-neuronx
does not allow: both stay overridden to the versions torch-neuronx
requires, as they are resolver metadata only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vllm.entrypoints.openai.cli_args is gone and the OpenAI api_server module
is deprecated: both moved under vllm.entrypoints.launchers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neuron configuration classes are registered by importing both the
inference and the training configurations. The training models do not
support transformers v5, so importing them made loading any inference
configuration fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers removed the CLIPFeatureExtractor alias of CLIPImageProcessor,
and diffusers moved the controlnet models under models/controlnets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 no longer sets the token id attributes on every
configuration, so reading config.pad_token_id raises instead of
returning None for a model that does not define one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 types Llama4 attn_temperature_tuning as a boolean, and
the configuration of tiny-random/llama-4 still holds the integer the
previous version expected, which is now rejected when loading it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 stores the mixture of experts weights as a single tensor
per projection, shaped (num_experts, out_features, in_features) with the
gate and up projections stacked along the output dimension, instead of
one linear layer per expert. Transposing the two fused tensors replaces
the per-expert copies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EncoderDecoderCache.from_legacy_cache is gone, and iterating a cache no
longer yields the flat per-layer tuples the traced decoder relied on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers no longer falls back to the eager attention when attention
weights are requested: the other implementations return none at all, so
the traced model was missing its attention outputs. Encoder and decoder
stacks keep their own configuration and have to be switched as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 always returns a ModelOutput, and CLIP text models no
longer run at all with return_dict=False, which is what used to give the
tracer the tuple it needs. Select the configured outputs in the export
wrapper instead, which also fixes their ordering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flaubert/flaubert_small_cased sets pre_norm, and that path of the
transformers modeling code cannot run at all: it subscripts an
EncoderDecoderCache. Use the tiny random model, as every other
architecture in the list does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same reason as in the exporter tests: the pre_norm path of the
transformers flaubert code cannot run at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v4 turned its model outputs into a tuple while tracing, and
the neuron models read them positionally. transformers v5 dropped that,
so the traced models returned a dict of named outputs instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 tokenizers no longer return token_type_ids, so a model
traced with them was called with one input missing. They default to
zeros. The traced inputs are now also ordered as they were traced,
rather than as the caller happened to build them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same reason as in the other model lists: the pre_norm path of the
transformers flaubert code cannot run at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 pipelines are PyTorch only and no longer carry a
`framework` attribute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers removed the question-answering pipeline, so the neuron
pipeline no longer supports that task either. The model tests are
unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The export CLI runs `python3 -m optimum.exporters.neuron` in a fresh
process, whose import root is that package: it only reaches the shim
installed by optimum.neuron through the order its imports happen to be
sorted in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The processes neuronx_distributed spawns to trace a tensor parallel
model import it while unpickling their work item, before any optimum
module: neither of the in-process install points is reached, and the
export dies. A path configuration file covers them, and every other
process that imports neuronx_distributed first.

The shim is now served by a meta path finder appended to sys.meta_path,
so the startup hook imports neither transformers nor torch, and the real
module still wins wherever transformers provides one. It lives in a top
level module for the same reason: importing anything under
optimum.neuron would pull transformers into every python process.

Editable installs do not get the path configuration file, so the shim
only covers spawned processes when optimum-neuron is installed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 leaves the generation configuration fields at None
rather than at their default value, and resolves them when generating.
The vendored generate compared them directly, so it recognised no
generation mode at all and rejected greedy decoding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The parallel T5 attention is a copy of the transformers implementation,
which changed: the relative position bias is now offset by the number of
cached tokens instead of indexing a cache position, the layers pass down
keyword arguments this implementation does not use, and they unpack
three values from every attention call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both divergences from the CPU model happen at a step where its two best
logits are exactly equal, so which token wins is decided by the order
argmax breaks the tie in. Every earlier token matches. Which
configuration lands on which side moves with any change to the
computation: qwen3 now diverges at 1x8192 rather than at 4x1024, and
gemma3 diverges on the long prompt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tengomucho and others added 7 commits September 14, 2026 12:55
The fractional coordinates the vision position ids are bucketed from are
clamped by transformers, not scaled: the (1 - 1e-6) factor this was
copied from moved every coordinate just below its bucket boundary, which
shifted all but the first position by one. It matched the transformers
implementation it mirrored until that one was fixed.

The vision features now match the reference to 8e-05, instead of
differing by half their own magnitude.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The neuron generation loop pads a sequence once it is done, so it can
carry trailing pad tokens transformers does not return: the cross chunk
case now generates the very same text and only differed by one of them.

SmolVLM also picks the last of its twenty tokens out of two candidates
the CPU model separates by less than a bfloat16 step, which is recorded
the way the other known divergences are.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The engine core now asks every worker which KV cache layouts it
supports, and the base implementation answers by looking up the vLLM
attention backends, of which a neuron worker has none. The compiled
model owns its KV cache, so any layout the engine can resolve will do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The image only carried the package and pyproject.toml, so the build hook
placing the startup hook in site-packages, and the module it installs,
were missing and the install failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every other option of the serve command is declared in both spellings,
and the arguments the command does not declare are forwarded to vLLM:
--data_parallel_size reached vLLM instead, which served the model from
its own data parallel engines, all of them claiming every visible core.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
huggingface_hub v1, which transformers v5 requires, moved to httpx and no
longer installs requests. optimum.exporters.tasks imports it at module
level, and so does our own neuron exporter, so a plain install of the
package could not run a single optimum-cli command.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The training layer is not migrated to transformers v5, but it still has to
import: the documentation autodocs models.training and trainers, and the
doc build fails to resolve those objects otherwise.

- get_parameter_dtype is gone, the PreTrainedModel dtype property does the
  same thing,
- no_init_weights moved to transformers.initialization, default_logdir to
  transformers.integrations.integration_utils and PreTrainedTokenizerBase
  to transformers.tokenization_utils_base,
- TensorFlow and Flax are gone along with their weight file names, so the
  branches reporting checkpoints in those formats go with them,
- so are download_url and is_remote_url, dropping the remote URL branch,
  and is_safetensors_available, safetensors being always available now.

Training configurations can be deserialized again, so the guard added
while they could not is removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

tengomucho and others added 6 commits September 15, 2026 12:41
huggingface_hub v1 switched from requests to httpx and made the
`response` argument of `HfHubHTTPError` a required keyword-only
argument that is dereferenced in the constructor. An error without a
response can no longer exist, so drop the now unreachable guard in
`_is_commit_conflict` and build the test errors from httpx objects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
huggingface_hub v1 uses httpx instead of requests, so a network failure
while inferring the task now surfaces as an httpx.TransportError. It
escaped the requests.ConnectionError handler, and the user got a raw
connection error instead of the hint to pass --task.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
uv does not apply [tool.uv.sources] to override-dependencies, so the
torch and torchvision overrides no longer resolved from the PyTorch CPU
index: every install pulled the CUDA builds from PyPI, along with the
nvidia runtime wheels and triton.

Pin the local version in the overrides and make the index non explicit,
which is what the overrides need to reach it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pipelines are kept alive by reference cycles, so their Neuron models
are only unloaded when the garbage collector runs. When that happens in
the middle of the next pipeline load, the runtime fails to unload a
model whose cores have been reinitialized, and torch-neuronx throws from
a destructor, which aborts the process.

Collect after every export fixture and after every test, so the models
are unloaded while nothing else is loading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
transformers v5 fuses the mixtral and qwen3-moe expert weights into
per-layer tensors and renames the mixtral sparse block from
block_sparse_moe to mlp, but it only applies that conversion when it
loads a checkpoint. The neuron exporter reads the safetensors files
directly, so checkpoints saved earlier still hold one tensor per expert
and per projection, and exporting them failed with a KeyError.

Extract the expert weights through a helper that handles both layouts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vLLM stops the spawned EngineCore process only when the LLM object is
finalized, and the engine tests left that to the garbage collector. When
the object survives the test, the engine core is still running at
interpreter exit, where multiprocessing joins that non-daemon child with
no timeout, so the session reports its results and then hangs forever.
That is what parked the INF2 vLLM workflow right after "1 passed".

Create the engines through a fixture that shuts them down after each
test, so the teardown no longer depends on when the collector runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tengomucho
tengomucho marked this pull request as ready for review September 17, 2026 07:12

@JingyaHuang JingyaHuang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix!

help="The port on which to serve the model.",
)
parser.add_argument(
"--data_parallel_size",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you want to enable both flags here? Is it related to the fix?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because some users expects underscores and others expect dashes, and this is an easy way to support both

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

This PR is stale because it has been open 15 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions Bot added the Stale label Oct 7, 2026
@github-actions

Copy link
Copy Markdown

This PR was closed because it has been stalled for 5 days with no activity.

@github-actions github-actions Bot closed this Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants