Skip to content

hpt: Qwen 3.5-0.8B VLM stem pair (one joint image+text forward) - #648

Closed
RyanPCo wants to merge 2 commits into
ryanco/transformers-upgradefrom
ryanco/qwen35-vlm
Closed

RyanPCo wants to merge 2 commits into
ryanco/transformers-upgradefrom
ryanco/qwen35-vlm

Conversation

@RyanPCo

@RyanPCo RyanPCo commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

New Qwen35VLMEncoder (encoder_specs.front_img_1) runs one Qwen 3.5
image+text forward per batch and splits the hidden states by token type:
220 visual tokens (640x352, patch 16 / merge 2) go to the existing image
Perceiver stem (input_dim 840 -> 1024), and the text tokens of the same
image-conditioned forward are read by the new Qwen35TextStem
(shared_stem_specs.annotation) through a plain, unregistered reference, so
the VLM weights exist once. The VLM is frozen, bfloat16 and runs under
no_grad; trainable_layers > 0 unfreezes the last N text layers.

It carries the PretrainedWeights mixin, so finalize_modules never
re-initialises it and HPT.init hashes it against its snapshot (the tied
lm_head and the unused mtp.* head are reconciled inside the class).

HPTModel.stem_process gains a six-line hook handing the batch's prompts
to an encoder that wants them; HPT.init wires the text stem to its
encoder. The ResNet path is unchanged.

New config model/hpt_bc_mecka_6d_300M_qwen35.yaml inherits
hpt_bc_mecka_6d_300M and swaps only those two entries, the image stem
width and the augmentations (the processor normalizes, so the ImageNet
Normalize is dropped). flash-linear-attention is pinned: without it
transformers falls back to a reference Gated DeltaNet kernel.

Co-Authored-By: Claude Opus 5 (1M context) [email protected]
Claude-Session: https://claude.ai/code/session_011QXaT3JinWYQH8t1zoUhDU

RyanPCo commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@RyanPCo
RyanPCo force-pushed the ryanco/transformers-upgrade branch from 5ac62d2 to ca14e40 Compare September 16, 2026 17:30
@RyanPCo
RyanPCo force-pushed the ryanco/transformers-upgrade branch 2 times, most recently from 1ea2174 to 47ad99b Compare September 17, 2026 15:13
RyanPCo and others added 2 commits September 17, 2026 11:25
New `Qwen35VLMEncoder` (encoder_specs.front_img_1) runs one Qwen 3.5
image+text forward per batch and splits the hidden states by token type:
220 visual tokens (640x352, patch 16 / merge 2) go to the existing image
Perceiver stem (input_dim 840 -> 1024), and the text tokens of the same
image-conditioned forward are read by the new `Qwen35TextStem`
(shared_stem_specs.annotation) through a plain, unregistered reference, so
the VLM weights exist once. The VLM is frozen, bfloat16 and runs under
no_grad; `trainable_layers` > 0 unfreezes the last N text layers.

It carries the `PretrainedWeights` mixin, so finalize_modules never
re-initialises it and HPT.__init__ hashes it against its snapshot (the tied
lm_head and the unused mtp.* head are reconciled inside the class).

`HPTModel.stem_process` gains a six-line hook handing the batch's prompts
to an encoder that wants them; HPT.__init__ wires the text stem to its
encoder. The ResNet path is unchanged.

New config model/hpt_bc_mecka_6d_300M_qwen35.yaml inherits
hpt_bc_mecka_6d_300M and swaps only those two entries, the image stem
width and the augmentations (the processor normalizes, so the ImageNet
Normalize is dropped). flash-linear-attention is pinned: without it
transformers falls back to a reference Gated DeltaNet kernel.

Dependencies: flash-linear-attention 0.5.2 (with its fla-core), in pyproject
and uv.lock. It provides the Triton kernels for Qwen 3.5's hybrid Gated
DeltaNet attention; without it transformers falls back to a reference PyTorch
implementation, correct but 1.27x slower on the measured forward. Pure-python
wheel, no compilation.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_011QXaT3JinWYQH8t1zoUhDU
HPT.__init__ defaults self.device to cuda whenever one is visible and copies
it into the HPTModel it builds. The trainer's real device only arrives later,
through ModelWrapper.on_fit_start / on_validation_start, which write it to the
algo alone -- so the policy kept the construction-time device.

HPTModel.compute_loss creates its loss accumulators on that stale device while
HPT.compute_losses accumulates on the algo's, so a CPU-tier run on a machine
with a visible GPU dies with 'Expected all tensors to be on the same device,
but found at least two devices, cuda:0 and cpu!'. That is 7 of the unit tests
(4 test_hpt_train_step, 2 test_norm_cache, 1 test_resolve_once) whenever the
suite is run on a GPU node.

HPT.device becomes a property whose setter also writes nets['policy'].device,
so the two cannot diverge through the path the trainer uses.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
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.

1 participant