fix: align visual-embedding feature selection with LLaVA semantics#113
Merged
Conversation
The visual-embedding path selected vision-tower hidden states directly, diverging from transformers' LlavaModel.get_image_features: it never cropped the leading CLS token under the "default" select strategy and could not handle a list-valued vision_feature_layer. Mirror that logic so the embeddings match LLaVA's own image features. Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
kaiitunnz
force-pushed
the
kaiitunnz/fix/vlm-visual-feature-selection
branch
from
July 23, 2026 12:44
678706c to
45a04f4
Compare
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
kaiitunnz
marked this pull request as ready for review
July 23, 2026 12:54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
The
visual-embeddingpath built LLaVA image embeddings by indexing the vision tower's hidden states directly, diverging from transformers' ownLlavaModel.get_image_features: it never dropped the leading CLS token under thedefaultvision-feature-select strategy, and it could not handle a list-valuedvision_feature_layer. This aligns the selection with LLaVA semantics so the emitted embeddings match the model's canonical image features.This change builds on #112 (merged), which relocated
vision_tower/multi_modal_projectorunderself._model.model; the divergence is only reachable once that relocation is in place.Changes
src/worker/executors/transformers_executor.py— add_select_vision_features, mirroringget_image_features(CLS crop on thedefaultstrategy; per-layer gather + last-dim concat for a list-valuedvision_feature_layer), and route the visual-embedding block through it.tests/worker/test_transformers_visual_embedding_features.py— cover int/list feature layer against default/full select strategy.Design
Replicates only the feature-selection logic rather than calling
base_model.get_image_features, which returns aBaseModelOutputWithPoolingwithimage_sizes-driven splitting — a different shape and artifact contract than the current tensor path. The grouping/split and savedvisual_embeddings.ptformat are unchanged.Test Plan
An isolated local-stack E2E builds current-branch server and GPU-worker images, brings up the stack with a single GPU worker, and submits a LLaVA visual-embedding workflow (
llava-hf/llava-1.5-7b-hf) over one image. It waits for the task to reach a terminal state, downloads the resultingvisual_embeddings.ptartifact, and inspects the embedding tensor to confirm the leading CLS token was dropped under thedefaultstrategy.Test Result
uv run pre-commit run --all-filesall passed.uv run pytest tests/ --ignore=tests/worker/test_mp_executor_cleanup_gpu.pypassed: 1416 tests, including the 4 new selection tests.DONE, thevisual_embeddings.ptartifact was downloadable, and the first embedding tensor had shape(1, 576, 4096)— sequence length 576, confirming the CLS token was cropped (forllava-1.5the CLIP tower emits 576 patches plus one CLS token, so an un-cropped run would report 577).Pre-submission Checklist
pre-commit run --all-filesand fixed any issues.uv run pytest tests/passes locally.uv sync --all-packages --group ci --frozen).[BREAKING]and described migration steps above.