Consistent output shape from get_image_features#46405
Conversation
|
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. |
|
run-slow: aya_vision, cohere2_vision, deepseek_ocr2, gemma4, paddleocr_vl, qwen2_5_omni, qwen3_omni_moe |
|
This comment contains models: ["models/aya_vision", "models/cohere2_vision", "models/deepseek_ocr2", "models/gemma4", "models/paddleocr_vl", "models/qwen2_5_omni", "models/qwen3_omni_moe"] |
vasqu
left a comment
There was a problem hiding this comment.
Some initial comments. I think it's overall fine to break but I would want @tomaarsen's opinion as well since he worked quite a bit on this and uses it downstream so just wanna make sure we coordinate
Other than that some design questions
- Do we not cat in get image features, why?
- We sometimes have to calculate the expected number of tokens produced by the vision tower; would it make sense to align across processor/modeling by having some global function?
| image_outputs.pooler_output = pooler_output.reshape( | ||
| selected_image_feature.shape[0], -1, self.config.text_config.hidden_size | ||
| ) | ||
|
|
There was a problem hiding this comment.
Does this reshape not affect the downstream forward?
There was a problem hiding this comment.
nope, if used in core VLM it won't affect. I reshaped inside VLMs forward when needed
| pixel_values, pixel_values_local, num_local_patches, return_dict=True | ||
| ).pooler_output | ||
| image_features = image_features.to(inputs_embeds.device, inputs_embeds.dtype) | ||
| image_features = torch.cat(image_features, dim=0).to(inputs_embeds.device, inputs_embeds.dtype) |
There was a problem hiding this comment.
any reason the cat is not directly in get img features?
There was a problem hiding this comment.
then we have a vague 2D tensor of (total_seq_length, dim) while I am moving towardsa unified output. So the output will show clearly how many images there are and make it easy to get an embeddings of image_i
There was a problem hiding this comment.
Ah true, yea that makes sense
| k_squared = int((image_position_ids.shape[1] // output_length) ** 0.5) ** 2 | ||
| non_pad_mask = (image_position_ids != -1).all(dim=-1) | ||
| split_sizes = (non_pad_mask.sum(dim=-1) // k_squared).tolist() | ||
| vision_outputs.pooler_output = torch.split(pooler_output, split_sizes) |
There was a problem hiding this comment.
Ig this is similar to how the processor calculates the number of images/videos? Would be nice if we could have a cross ref
There was a problem hiding this comment.
nah, it is taken from Gemma4Pooling layer but yeah, will add a ref
| image_embeds = self.projector(image_embeds, image_grid_thw) | ||
| vision_outputs.pooler_output = image_embeds | ||
| split_sizes = (image_grid_thw.prod(-1) // self.config.vision_config.spatial_merge_size**2).tolist() | ||
| vision_outputs.pooler_output = torch.split(image_embeds, split_sizes) |
There was a problem hiding this comment.
Hmm ok this will happen a few times; adjusting my previous comment a bit. Would it make sense to have a global function for this that is used across processor and model as here?
There was a problem hiding this comment.
haha, this happens for all qwen-based models which is indeed a lot atm. Wdym by a global fn since I'm not sure it's worth for a one-liner torch.split
There was a problem hiding this comment.
I thought maybe a static function that can be both consumed by the model / processor to get the expected number here. But yea indeed it's not too worthwhile for now
|
We also need to add 🚨 for sure |
You mean gemma4 (which I also replied is based on modeling, not processor)? Otherwise we always pass the number of grids (as in qwen) or just split based on input pixel shape |
|
Oh yeah, Tom requested this in the past for |
|
Yea I think we are mostly fine then, still want to wait on @tomaarsen if he has any opinions on this and maybe @remi-or using it for CB(?) |
|
yeah, let's wait for ppl affected. I also commented on CB to let Remi know :) |
|
In general, anything that makes shapes more static or standardize inputs is good for CB, so this is great! The only problem that this could create is if |
indeed, the number if splits would be dynamic each call depending on input image. Even though it is part of @vasqu I'll tag you after checking in with exporters and finding a better way to make everyone happy |
|
I think we can revisit this after #41992? I'm not sure but I don't think it would interfere with Ilyas work as we forward the inputs once and capture the necessary metadata based on that - so yea it should be fine on that side |
|
@vasqu , let's merge? I rebased main and fixed new models |
There was a problem hiding this comment.
One small comment, but
- Let's add 🚨 as this is technically still breaking (in a good way)
- Get everything on board cc @tomaarsen @IlyasMoutawwakil if you could have a final check as well so we don't break exporters and ST
- Fix fast CI 😬
But yes, I think it's mergable. Just want everyone to be aware that it happens
tomaarsen
left a comment
There was a problem hiding this comment.
I did some tests for Sentence Transformers, and found that this PR shouldn't affect it. ST only really uses the get_..._features methods when the forward does not contain a last_hidden_state (e.g. CLIP, SigLIP, etc.), as then the get_..._features methods must be used to get per-modality outputs.
For all of these modern architectures, the forward accepts any combination of modalities already, so I don't have to rely on the get_..._features methods.
I did write two small comments though, based on my and Fable's review of the work.
|
Thanks for review, fixing that and hopefully merging later in the day |
|
[For maintainers] Suggested jobs to run (before merge) run-slow: aya_vision, cohere2_vision, deepseek_ocr2, diffusion_gemma, gemma4, inkling, kimi_k25, paddleocr_vl, qwen2_5_omni, qwen3_omni_moe |
CI recapDashboard: View test results in Grafana |
* upstream/main: (39 commits) Remove deprecated training args and `is_fast` property (huggingface#46917) Consistent output shape from `get_image_features` (huggingface#46405) Fix multi-device mxfp4 dequantization race in `_convert_moe_packed_tensors` (huggingface#47423) fix failed test cases for qwen3_omni_moe model (huggingface#47449) Fix Hunyuan-VL PIL image resize parity with reference preprocessing (huggingface#47233) Move `value` padding into the attention interfaces that need it (huggingface#47451) Simplify function dispatch for linear attention (huggingface#47450) [cache] Allow sliding window layers to be roll-backed for speculative decoding (huggingface#47447) Fix double-shifted training loss in GitForCausalLM (huggingface#47395) Fix CohereASR training-loss double-shift (same as Moonshine fix huggingface#46784) (huggingface#46895) Warn when `group_by_length` is silently ignored for iterable datasets (huggingface#47379) Update bug report list (huggingface#46607) Fix shape mismatch in KyutaiSpeechToText `generate()` last window (huggingface#46952) Optimize flash attention max seqlen computation in vision attention (huggingface#47170) fix: remove unreachable return in special token builder (huggingface#47420) Add Harry to slow CI (huggingface#47454) BLT: vectorize patch length processing (huggingface#47385) Fix `TrackioCallback` fails to log evaluation metrics after training ends (huggingface#46935) [Kimi] add integration tests (huggingface#47383) Fix typo in `MusicgenForCausalLM.generate()` (huggingface#46974) ...
What does this PR do?
as per title, branches off from #45783
The pooled image (OR video, NOT audio yet) feature output will now always satisfy three cond, where image features can be a list or a 3D tensor:
Could make it complete BC and return the "correct" output when a certain flag is passed, but for now decided to update directly smaller utility fn in a breaking way