Repository navigation
Add Xiaomi-Lens-0 - #50028
Add Xiaomi-Lens-0 #50028guarin wants to merge 37 commits into
Conversation
| image_features = model.get_image_features( | ||
| inputs.pop("pixel_values"), inputs.image_grid_thw, return_dict=True | ||
| ) | ||
| generated = model.generate( | ||
| **inputs, | ||
| mm_encoder_outputs={"image": image_features}, | ||
| return_dict_in_generate=True, | ||
| output_hidden_states=[model.config.text_config.num_hidden_layers - 1], | ||
| use_cache=True, | ||
| num_beams=1, | ||
| num_return_sequences=1, | ||
| max_new_tokens=1024, | ||
| ) | ||
| hidden_states = torch.cat([step[-1] for step in generated.hidden_states], dim=1) | ||
| mask_logits, mask_token_indices = model.decode_masks( | ||
| generated.sequences, hidden_states, image_features.last_hidden_state, | ||
| inputs.image_grid_thw, ignore_thinking=True, | ||
| ) |
There was a problem hiding this comment.
I had a very long plan to return image/video hidden states same way as we return encoder_hidden_states. It would be done auto-magically by routing model outputs directly into generation outputs so we dont have to manually infer/collect outputs per model
The person working on it left HF and the task got buried 😢
So the general plan i had was:
- All multimodal models return same format
mm_outputs, e.g. rn some models return only pooled images from VLM and some return a whole ModelOutput. IMO returning ModelOutput as in old blip models is the better way to allow flexibility - GenerationMixin split out the part of output collection into a smaller helper that mostly re-rereouts most fields returned from model to Mixin's output (we still dont return MoE router-logits and similar)
output = model.generate(
**inputs,
return_dict_in_generate=True,
output_hidden_states=[model.config.text_config.num_hidden_layers - 1],
max_new_tokens=1024,
)
hidden_states = torch.cat([step[-1] for step in output.hidden_states], dim=1)
image_hidden_states = output.image_hidden_states[-1] # it is already only last one returned in list
mask_logits, mask_token_indices = model.decode_masks(
output.sequences, hidden_states, image_hidden_states,
inputs.image_grid_thw, ignore_thinking=True,
)
|
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. Pages changed in this PR: |
molbap
left a comment
There was a problem hiding this comment.
hey, thanks! left an initial review, looks clean so far, not sure about the tracing. I like your proposal @zucchini-nlp , worth tackling IMO
| @require_vision | ||
| class XiaomiLens0IntegrationTest(MemoryCleanupMixin, unittest.TestCase): | ||
| # TODO: Update before merge | ||
| model_id = "guarin/Xiaomi-Lens-0" # "SeerRay-Lab/Xiaomi-Lens-0" |
There was a problem hiding this comment.
do we need a hub PR as well?
There was a problem hiding this comment.
Yes, will open it once it is clear that no major changes are required
There was a problem hiding this comment.
ok! let's ping on slack as well
molbap
left a comment
There was a problem hiding this comment.
few more generic comments
| image_valid_mask = None if valid_targets_mask is None else valid_targets_mask[index] | ||
| num_masks += image_logits.shape[0] if image_valid_mask is None else image_valid_mask.flatten(1).any(-1).sum() | ||
|
|
||
| for start in range(0, image_logits.shape[0], chunk_size): |
There was a problem hiding this comment.
IIUC: this chunking only lowers memory under no grad (eval loss), in training autograd keeps every chunk's fp32 tensors until backward anyway. the original also checkpoints each chunk, that's where it saves, so maybe we can we drop the loop and mask_loss_chunk_size?
There was a problem hiding this comment.
Fair, will drop it but then training will require a lot more compute.
There was a problem hiding this comment.
ah really? I hadn't measured such a big loss tbh (just 7% more memory), but if you think it's worse we keep it as it was
| elif labels is not None: | ||
| # Match the collective on ranks with mask supervision without connecting the decoder to the loss. | ||
| _normalize_mask_count(logits.new_zeros((), dtype=torch.float32)) |
There was a problem hiding this comment.
not too familiar with the DDP side, if I follow correctly, a rank that only gets text batches never calls the decoder, so its params get no grad there. and with gradient checkpointing I think the Trainer sets find_unused_parameters=False, which I think crashes?
There was a problem hiding this comment.
Exactly, it would only work with find_unused_parameters=True, also with other trainers than hf
There was a problem hiding this comment.
ok so the docstring note is enough for me
molbap
left a comment
There was a problem hiding this comment.
Nice work 🚀
Approving for the "standard" stuff, for the custom generate maybe worth exploring an initial PR that does the returning of img hidden states cleanly and stack this one on top of it? I think it would be preferred against having a standalone class that does just one thing that is mostly a wrapper on utils we have
| elif labels is not None: | ||
| # Match the collective on ranks with mask supervision without connecting the decoder to the loss. | ||
| _normalize_mask_count(logits.new_zeros((), dtype=torch.float32)) |
There was a problem hiding this comment.
ok so the docstring note is enough for me
| @require_vision | ||
| class XiaomiLens0IntegrationTest(MemoryCleanupMixin, unittest.TestCase): | ||
| # TODO: Update before merge | ||
| model_id = "guarin/Xiaomi-Lens-0" # "SeerRay-Lab/Xiaomi-Lens-0" |
There was a problem hiding this comment.
ok! let's ping on slack as well
zucchini-nlp
left a comment
There was a problem hiding this comment.
adding my 2 cents 😄
btw, i will be making proper model output in multmodals. I got a local branch tho it still waits for an update for 40 existing models 😭
Left a comment re naming of model output tho to make refactor easier
| mask_logits = mask_embeddings @ pixel_embeddings.flatten(2) / self.config.mask_embed_dim**0.5 | ||
| mask_logits = mask_logits.view(batch_size, -1, height, width) | ||
| # The fixed 4x upsampling brings the masks to the resolution of the image fed to the vision encoder | ||
| return F.interpolate(mask_logits, scale_factor=self.scale_factor, mode="bilinear", align_corners=False) |
There was a problem hiding this comment.
vision_utils.get_interpolte_indices for export friendliness? 😄
There was a problem hiding this comment.
Hmm don't think that is needed here as it is just a simple interpolate and all logits have the same size
There was a problem hiding this comment.
ah my bad, qwen ruined my life forever 😆
| return XiaomiLens0GenerateOutput(**result, image_hidden_states=image_hidden_states) | ||
| return result | ||
|
|
There was a problem hiding this comment.
image_last_hidden_state pls since it is only the last one returned
| Vision patch features before spatial merging, retaining gradients for mask supervision. | ||
| """ | ||
|
|
||
| image_hidden_states: torch.FloatTensor | None = None |
|
[For maintainers] Suggested jobs to run (before merge) run-slow: auto, xiaomi_lens0 |
There was a problem hiding this comment.
Model linter — first pass
transformers-mlinter found 2 item(s) in the model files this PR touches. These are structural conventions a maintainer would otherwise flag by hand.
This is automated and advisory — it does not block merging.
| rule | count | what it checks |
|---|---|---|
TRF055 |
2 | PreTrainedModel classes declare their config with a config: SomeConfig annotation, matched by __init__(config: SomeConfig); never config_class. |
| image_last_hidden_state: torch.Tensor | None = None | ||
|
|
||
|
|
||
| class XiaomiLens0Model(Qwen3_5Model): |
There was a problem hiding this comment.
TRF055 — TRF055: generated modeling_xiaomi_lens0.py:76: XiaomiLens0Model.__init__ must annotate its config argument: config: XiaomiLens0Config, matching the config attribute it inherits or overrides.
Why this matters
PreTrainedModel derives config_class from the config annotation. An assignment or a hand-written config_class keeps or drifts from the parent's, and a mismatched __init__ builds the model from one config while it advertises another.
class Gemma4VisionModel(Gemma4PreTrainedModel):
"""The Gemma 4 Vision Encoder."""
- config = Gemma4VisionConfig
+ config: Gemma4VisionConfig
- def __init__(self, config):
+ def __init__(self, config: Gemma4VisionConfig):Suppress with # trf-ignore: TRF055 if intentional.
|
|
||
|
|
||
| @auto_docstring | ||
| class XiaomiLens0ForConditionalGeneration(Qwen3_5ForConditionalGeneration, XiaomiLens0GenerationMixin): |
There was a problem hiding this comment.
TRF055 — TRF055: generated modeling_xiaomi_lens0.py:734: XiaomiLens0ForConditionalGeneration.__init__ must annotate its config argument: config: XiaomiLens0Config, matching the config attribute it inherits or overrides.
Why this matters
PreTrainedModel derives config_class from the config annotation. An assignment or a hand-written config_class keeps or drifts from the parent's, and a mismatched __init__ builds the model from one config while it advertises another.
class Gemma4VisionModel(Gemma4PreTrainedModel):
"""The Gemma 4 Vision Encoder."""
- config = Gemma4VisionConfig
+ config: Gemma4VisionConfig
- def __init__(self, config):
+ def __init__(self, config: Gemma4VisionConfig):Suppress with # trf-ignore: TRF055 if intentional.
What does this PR do?
Fixes # (issue)
Code Agent Policy
The Transformers repo is currently being overwhelmed by a large number of PRs and issue comments written by
code agents. These often are low-quality, or fix extremely minor issues that occur rarely or never in practice.
As a result, we're instituting a rule that first-time contributors should not use code agents to submit PRs or issues.
We'd also ask autonomous "OpenClaw"-like agents not to open any PRs or issues.
Issues/PRs from first-time contributors that violate this rule will probably just be closed without review, and we
might block you, especially if you open more than one or appear to be deliberately ignoring this. We especially do not
want new contributors to jump in on random issues to contribute an agent-written fix. This creates lots of noise
for reviewers and other users and will almost certainly get you blocked.
For more information, please read
CONTRIBUTING.md.Before submitting
Pull Request checks?
to it if that's the case.
Who can review?
Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
members/contributors who may be interested in your PR.
🤖 mlinter review state
dac7099a08c2fc23