Skip to content

fix(bridge): apply dtype, device and eval() when load_weights=False - #2020

Open
pablocs116 wants to merge 1 commit into
TransformerLensOrg:devfrom
pablocs116:fix/load-weights-false-dtype-device-eval-2017
Open

pablocs116 wants to merge 1 commit into
TransformerLensOrg:devfrom
pablocs116:fix/load-weights-false-dtype-device-eval-2017

Conversation

@pablocs116

Copy link
Copy Markdown
Contributor

Description

TransformerBridge.boot_transformers(..., load_weights=False) built the model with from_config and left it on the CPU, in the config's dtype and in training mode. device was only recorded in cfg.device, and dtype was ignored, so cfg.dtype and the weights could disagree. Callers had to cast, move and .eval() the model themselves. With GPT-2's default dropout, two identical forward passes gave different outputs.

The no-weights path now does what the weights path does:

  • eval mode: hf_model.eval(). from_pretrained returns an eval-mode model, but from_config doesn't.
  • dtype: maybe_cast_floating_params(hf_model, dtype), the same call the weights path uses. The model is still initialised in the config's dtype and then cast, so float32 buffers such as RotaryEmbedding.inv_freq are kept.
  • device: the bridge is moved to device right after it's built. The weights path moves the HF model before building the bridge, but on the no-weights path I move it after. Splitting fused QKV at boot reads the weight values, so moving a GPT-2 to meta first fails with NotImplementedError. meta is what test_random_init_bridge_puts_its_weights_on_the_device_it_records uses to stand in for a GPU, and it's what random_init_bridge itself did (bridge.to(bridge.cfg.device)). from_config already builds on the CPU, so a CPU target is not moved, and the move doesn't print.
  • device_map is still not applied, as agreed in the issue. The parameters stay on the CPU.

The load_weights docstring is updated to match. random_init_bridge in tools/analysis/interchange.py drops its cast / .to / .eval() workaround and just seeds and boots. Its existing tests pass unchanged.

Closes #2017

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • This change requires a documentation update

Checklist:

  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have not rewritten tests relating to key interfaces which would affect backward compatibility

Tests

New TestBridgeWithoutWeights in tests/integration/model_bridge/test_bridge_creation_modes.py. It uses a tiny GPT-2 config through hf_config_overrides, so no weights are downloaded:

  • float16 / bfloat16: every floating parameter has the requested dtype, matching cfg.dtype
  • hf-internal-testing/tiny-random-LlamaForCausalLM in bfloat16: the inv_freq buffers stay float32
  • no module in the bridge or the HF model is in training mode, and two identical forward passes are equal
  • device="meta": cfg.device and every parameter are on meta

Local gates:

  • make check-format and uv run mypy .: clean
  • make unit-test: 8199 passed. The one failure in the first run (test_qwen3_5_adapter.py::TestQwen3_5LoadingGuards::test_load_weights_false_uses_prepared_text_config) came from this change: its stub bridge has no cfg, and .to() reads it. It's fixed by skipping the CPU move, and the test now passes.
  • test_bridge_creation_modes.py, test_interchange.py, test_qwen3_5_adapter.py, test_multi_gpu_bridge.py, test_n_ctx_override.py, test_remote_bridge.py: 162 passed, 3 skipped

Hardware: CPU only (WSL2).

🤖 Generated with Claude Code

boot_transformers(load_weights=False) built the model with from_config and
left it on the CPU, in the config's dtype and in training mode. It now mirrors
the weights path: eval(), maybe_cast_floating_params(dtype), and a move to
device once the bridge is built (fused-QKV splitting at boot needs real
values, so the move can't precede it). device_map is still not applied.

random_init_bridge drops its cast/.to/.eval() workaround.

Closes TransformerLensOrg#2017

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jlarson4 jlarson4 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 quick turnaround on this, the eval and device handling are solid. A couple comments, and you will need to pull in the latest dev elements before this can merge. Let me know if you have any questions!

hf_model.eval()
# Initialise in the config's dtype, then cast like the weights path, so float32
# buffers (e.g. RotaryEmbedding.inv_freq) are preserved
maybe_cast_floating_params(hf_model, dtype)

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.

from_pretrained builds the model under the requested dtype, so buffers created at init follow it. Here, the model is built in the config's dtype and only the parameters are cast, which leaves some architectures mixed. For example, no-weights XGLM or Falcon-H1 booted with dtype=torch.float16 now fail in its first forward pass, where the weights path runs. Can the model be built in the requested dtype, mirroring the weights path?

but not applied, and neither are ``dtype`` nor ``device_map``; move, cast and
``eval()`` the model before running it on another device or in another dtype.
checkpoint: real parameters with the architecture's random initialisation, moved to
``device``, cast to ``dtype`` and in evaluation mode, as with loaded weights.

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.

benchmarks/main_benchmark.py:750 boots without weights only to read attn_implementation, and passes the run's device and dtype. With this change that probe casts a full random-init model and moves it to the accelerator on every benchmark run. Could you take a look at that call so it stays a cheap CPU probe?

torch.default_generator.manual_seed(seed)
bridge = TransformerBridge.boot_transformers(
model_name, load_weights=False, device=device, **boot_kwargs
return TransformerBridge.boot_transformers(

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.

Some of my work, PR #2018 landed on dev and rewrote this block. boot is no longer passed dtype, and cfg.dtype is set by hand after the cast. Please pull in the latest dev and adjust accordingly, apologies for the conflict I did not mean to overlap with you.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants