Skip to content

Add LLaVA-OneVision-1.5 model and related configurations - #41095

Open
g1050 wants to merge 3 commits into
huggingface:mainfrom
g1050:llava_onevision1_5_support
Open

g1050 wants to merge 3 commits into
huggingface:mainfrom
g1050:llava_onevision1_5_support

Conversation

@g1050

@g1050 g1050 commented Sep 23, 2025 •

Copy link
Copy Markdown

What does this PR do?

This PR adds LLaVA-OneVision-1.5
Fixes #41081

Before submitting

  • This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
  • Did you read the contributor guideline,
    Pull Request section?
  • Was this discussed/approved via a Github issue or the forum? Please add a link
    to it if that's the case.
  • Did you make sure to update the documentation with your changes? Here are the
    documentation guidelines, and
    here are tips on formatting docstrings.
  • Did you write any new necessary tests?

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.

@g1050
g1050 marked this pull request as draft September 23, 2025 09:25
@Rocketknight1

Copy link
Copy Markdown
Member

cc @zucchini-nlp

@zucchini-nlp

Copy link
Copy Markdown
Member

Hey @g1050 !

Nice addition, I suggest to add the model with modular (see docs) to make it easier to review and maintain. I believe llava-onevision 1.5 has a lot of similarities with other LlaVa series models, and modular will help you to reduce LOC in the PR and iterate faster :)

@g1050 g1050 changed the title Add LLaVA-One-Vision-1.5 model and related configurations Add LLaVA-OneVision-1.5 model and related configurations Sep 24, 2025
@g1050
g1050 marked this pull request as ready for review September 30, 2025 06:35
@zucchini-nlp

Copy link
Copy Markdown
Member

I see the PR went to ready for review. Ping me when it is actually ready for review and the CI is green :)

@tanhuajie

Copy link
Copy Markdown
Contributor

Hi @zucchini-nlp, it’s ready for review when you have a chance. Thanks!

@HuggingFaceDocBuilderDev

HuggingFaceDocBuilderDev commented Oct 1, 2025 •

Copy link
Copy Markdown

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:

@zucchini-nlp zucchini-nlp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for working on the model. I think we can use modular in more cases and thus reduce LOC to review and maintain. I left comments below

Comment thread utils/check_docstrings.py Resolved
@tanhuajie

Copy link
Copy Markdown
Contributor

Hi @zucchini-nlp, thanks for your review~

I’ve addressed your review comments point by point and this should be ready for your review again. The main changes are:

  1. Refactored the modeling code to use the modular utilities wherever possible, making it easier to reuse and modify.
  2. Re-enabled the previously skipped but necessary CI test cases and fixed the failures, including: test_can_init_all_missing_weights, test_feed_forward_chunking, test_generate_from_inputs_embeds_1_beam_search
  3. Renamed all Vision model classes from Rice to LlavaOnevision1_5Vision, and fixed the docs (LlavaOnevision1_5Config) instead of skipping.
  4. Simplified the CLS/RoPE handling without creating extra empty tensor in LlavaOnevision1_5VisionPretrainedModel.forward.
  5. Various minor fixes.

If there’s anything else that needs changes, please ping me anytime🤗.

@zucchini-nlp

Copy link
Copy Markdown
Member

Thanks, reviewing today-tomorrow

@zucchini-nlp zucchini-nlp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for iterating, looks much cleaner. I left a few questions and suggestions below, but approving so it can be reviewed by a core maintainer next

After addressing the comments and fixing the CI, you can ping Cyrilvallez for final review

# Initialize weights and apply final processing
self.post_init()

# modified from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLModel.forward

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can copy. The diff is only in position ids and tbh I don't think it is needed, as long as the generation prepares the position ids for us

@tanhuajie tanhuajie Oct 22, 2025 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct. Our primary motivation is to reuse the existing functions from Qwen2.5-VL, such as get_rope_index and prepare_inputs_for_generation. The key modification in the forward function is the insertion of the line position_ids = position_ids[0] if len(position_ids.shape) == 3 else position_ids before the data is passed to the Qwen3 model. However, as the current modular tool does not support this specific operation, I had to directly copy the relevant code and implement the modification. Do you have any better solution?

Comment thread utils/check_repo.py Outdated
"MllamaVisionModel", # Building part of bigger (tested) model. # TODO: add tests
"Llama4TextModel", # Building part of bigger (tested) model. # TODO: add tests
"Llama4VisionModel", # Building part of bigger (tested) model. # TODO: add tests
"LlavaOnevision1_5Model", # Building part of bigger (tested) model. Tested implicitly through LlavaOnevision1_5ForConditionalGeneration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be added in all_models_classes in the test file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. But I did not find all_models_classes in the test file.

Comment thread utils/check_repo.py Outdated
"BltLocalDecoder", # Building part of bigger (tested) model. Tested implicitly through BLTForCausalLM.
"BltGlobalTransformer", # Building part of bigger (tested) model. Tested implicitly through BLTForCausalLM.
"Ovis2VisionModel",
"LlavaOnevision1_5VisionPretrainedModel",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's expose it, usually PretrainedModel are not private. You need to add it in docs and in __all__

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LlavaOnevision1_5PreTrainedModel has been exposed in __all__, but LlavaOnevision1_5VisionPretrainedModel is only the vision part of LlavaOnevision1_5 and not an independent model. We might consider keeping it internal. For example, Qwen2_5_VisionTransformerPretrainedModel in Qwen2.5-VL is also not exposed, isn't that right?

@tanhuajie

Copy link
Copy Markdown
Contributor

Hi, @zucchini-nlp @Cyrilvallez. The comments have been addressed, I think... and this should be ready for final review. If there’s anything else that needs changes, please ping me anytime🤗.

@anxiangsir

Copy link
Copy Markdown

Dear @Cyrilvallez , apologies for the gentle nudge. Whenever convenient, could you please perform the final review? All feedback has been addressed and CI is passing. Thank you very much for your time and guidance.

@anxiangsir

Copy link
Copy Markdown

@Rocketknight1 @ArthurZucker @zucchini-nlp Sorry for the gentle nudge. Whenever convenient, could you please take a look for a final review/approval? All feedback has been addressed and CI is green. If there’s anything else you’d like me to adjust, I’m happy to update right away. Thank you very much for your time and guidance!

@Cyrilvallez

Copy link
Copy Markdown
Member

run-slow: llava_onevision1_5

@github-actions

Copy link
Copy Markdown
Contributor

This comment contains run-slow, running the specified jobs:

models: ["models/llava_onevision1_5"]
quantizations: []

@Cyrilvallez

Copy link
Copy Markdown
Member

Hey @anxiangsir! Deeply deeply sorry for the delay on this! We are really focused on the upcoming v5 currently, which explain my late answer on this. Next iterations will be much quicker now that things are calming down.
In the meantime, do you mind fixing the small issues that popped up when I merged the current main branch into the PR? Simply reapplying modular conversion should be enough I believe!

@Cyrilvallez Cyrilvallez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry again for the delay! here are a few things to still consider!

Comment on lines +40 to +46
# default: Load the model on the available device(s)
model = LlavaOnevision1_5ForConditionalGeneration.from_pretrained(
model_path, torch_dtype="auto", device_map="auto", trust_remote_code=True
)

# default processer
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No trust_remote_code in the examples! We use native hf format!

Comment on lines +105 to +109
if isinstance(text_config, dict):
text_config["model_type"] = text_config.get("model_type", "qwen3")
self.sub_configs["text_config"] = CONFIG_MAPPING[text_config["model_type"]]
elif text_config is None:
self.sub_configs["text_config"] = CONFIG_MAPPING["qwen3"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We never set sub_configs like this! I think this can simply be removed as not useful

Comment on lines +163 to +165

class LlavaOnevision1_5VisionMlp(VisionMlp):
pass

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use another base class, or redefine the init: we want the modules to be fully configured by the config, not different kwargs coming from the config

Comment on lines +196 to +197
torch.nn.init.normal_(module.class_embedding, mean=0.0, std=std_cls)
torch.nn.init.normal_(module.class_pos_emb, mean=0.0, std=std_cls)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use transformer inits: from ... import initialization as init and use this instead of torch.nn

Comment on lines +199 to +200

class LlavaOnevision1_5VisionPretrainedModel(Qwen2_5_VisionTransformerPretrainedModel):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename, PreTrained is always kept for the actual PreTrainedModel base class

Suggested change
class LlavaOnevision1_5VisionPretrainedModel(Qwen2_5_VisionTransformerPretrainedModel):
class LlavaOnevision1_5VisionModel(Qwen2_5_VisionTransformerPretrainedModel):

Comment on lines +292 to +322
def __init__(self, config: LlavaOnevision1_5Config):
PreTrainedModel.__init__(self, config)
self.visual = LlavaOnevision1_5VisionPretrainedModel._from_config(config.vision_config)
self.language_model = AutoModel.from_config(config.text_config)
self.rope_deltas = None
# Initialize weights and apply final processing
self.post_init()

@auto_docstring
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
attention_mask: Optional[torch.Tensor] = None,
position_ids: Optional[torch.LongTensor] = None,
past_key_values: Optional[Cache] = None,
inputs_embeds: Optional[torch.FloatTensor] = None,
use_cache: Optional[bool] = None,
output_attentions: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
pixel_values: Optional[torch.Tensor] = None,
pixel_values_videos: Optional[torch.FloatTensor] = None,
image_grid_thw: Optional[torch.LongTensor] = None,
video_grid_thw: Optional[torch.LongTensor] = None,
rope_deltas: Optional[torch.LongTensor] = None,
cache_position: Optional[torch.LongTensor] = None,
second_per_grid_ts: Optional[torch.Tensor] = None,
**kwargs: Unpack[TransformersKwargs],
) -> Union[tuple, LlavaOnevision1_5ModelOutputWithPast]:
r"""
image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those 2 different from Qwen ones?

Comment on lines +268 to +294
@unittest.skip(
reason="This architecture seem to not compute gradients properly when using GC, LlavaOnevision1_5VisionPretrainedModel does not support standalone training"
)
def test_training_gradient_checkpointing(self):
pass

@unittest.skip
def test_training(self):
pass

@unittest.skip(
reason="This architecture seem to not compute gradients properly when using GC, LlavaOnevision1_5VisionPretrainedModel does not support standalone training"
)
def test_training_gradient_checkpointing_use_reentrant(self):
pass

@unittest.skip(
reason="This architecture seem to not compute gradients properly when using GC, LlavaOnevision1_5VisionPretrainedModel does not support standalone training"
)
def test_training_gradient_checkpointing_use_reentrant_false(self):
pass

@unittest.skip(
"VLMs need lots of steps to prepare images/mask correctly to get pad-free inputs. Can be tested as part of LLM test"
)
def test_flash_attention_2_padding_matches_padding_free_with_position_ids(self):
pass

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure those are not passing? Looks a lot like copy/paste

Comment on lines +211 to +223
def test_initialization(self):
config, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common()

configs_no_init = _config_zero_init(config)
for model_class in self.all_model_classes:
model = model_class(config=configs_no_init)
for name, param in model.named_parameters():
if param.requires_grad and "class_pos_emb" not in name and "class_embedding" not in name:
self.assertIn(
((param.data.mean() * 1e9).round() / 1e9).item(),
[0.0, 1.0],
msg=f"Parameter {name} of model {model_class} seems not properly initialized",
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to overload the main one?

Comment on lines +200 to +201
test_pruning = False
test_head_masking = False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove, they disappeared

@github-actions

Copy link
Copy Markdown
Contributor

CI Results

Workflow Run ⚙️

Model CI Report

❌ Failed tests

  • llava_onevision1_5:
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_can_load_with_global_device_set
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_attn_implementation_composite_models
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_bc_torch_dtype
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_can_load_with_device_context_manager
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_can_use_safetensors
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_cannot_load_with_meta_device_context_manager
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_cpu_offload
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_disk_offload_safetensors
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_generate
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_00_fp16_pad_left_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_01_fp16_pad_left
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_02_fp16_pad_left_no_attn_mask_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_03_fp16_pad_left_no_attn_mask
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_04_fp16_pad_right_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_05_fp16_pad_right
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_06_fp16_pad_right_no_attn_mask_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_07_fp16_pad_right_no_attn_mask
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_08_fp32_pad_left_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_09_fp32_pad_left
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_10_fp32_pad_left_no_attn_mask_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_11_fp32_pad_left_no_attn_mask
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_12_fp32_pad_right_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_13_fp32_pad_right
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_14_fp32_pad_right_no_attn_mask_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_15_fp32_pad_right_no_attn_mask
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_16_bf16_pad_left_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_17_bf16_pad_left
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_18_bf16_pad_left_no_attn_mask_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_19_bf16_pad_left_no_attn_mask
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_20_bf16_pad_right_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_21_bf16_pad_right
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_22_bf16_pad_right_no_attn_mask_sdpa_kernels
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_23_bf16_pad_right_no_attn_mask
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_eager_matches_sdpa_inference_24_fp32_pad_left_output_attentions
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_load_save_without_tied_weights
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_model_weights_reload_no_missing_tied_weights
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_save_load
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_sdpa_can_compile_dynamic
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_sdpa_can_dispatch_composite_models
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_sdpa_can_dispatch_on_flash
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationModelTest::test_tied_weights_keys
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationIntegrationTest::test_small_model_integration_test
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationIntegrationTest::test_small_model_integration_test_batch
    tests/models/llava_onevision1_5/test_modeling_llava_onevision1_5.py::LlavaOnevision1_5ForConditionalGenerationIntegrationTest::test_small_model_integration_test_batch_different_resolutions

@ArthurZucker

Copy link
Copy Markdown
Collaborator

Thanks for the work on LLaVA-OneVision-1.5! This branch predates v5 and now conflicts with main (auto mappings, model init and docs moved a lot since Nov 2025). The model is still not on main, so it is still welcome: could you rebase on current main and regenerate the modeling file from the modular file (ideally reusing the shared Qwen/LLaVA components) so we can do a fresh review?

@ZijunLi402
ZijunLi402 force-pushed the llava_onevision1_5_support branch from 7d10c0b to b07d0cd Compare October 25, 2026 04:13
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution 🤗!

CI Security Gate — waiting for maintainer approval

CI is approved automatically only for authors who already have a contribution merged in this repository. A maintainer will review this PR and approve CI to run on it.

Use modular generation and shared model components for the native model,
Auto registrations and checkpoint loading. Add documentation, vision export
support and tests for inference, training and frame isolation.
@ZijunLi402
ZijunLi402 force-pushed the llava_onevision1_5_support branch from b07d0cd to 0ed53a9 Compare November 11, 2026 03:32
@ZijunLi402

Copy link
Copy Markdown

Hi @ArthurZucker @Cyrilvallez, I’m one of the developers of the LLaVA-OneVision 2 series, helping update this OV1.5 PR.

I’ve rebased the branch and regenerated the modeling file from the modular source, reusing shared components. Local CPU/GPU model tests and checkpoint integration tests passed.

CI auto-approval is blocked by the two .gitattributes entries required for the generated files. Could you please review these changes, manually approve CI, and take another look at the PR? The bot also suggests running run-slow: auto, llava_onevision1_5.

Thanks for your help!

@zucchini-nlp

Copy link
Copy Markdown
Member

@ZijunLi402 hey 👋🏻

very niice, i will take a look now. We had a few other PRs for 1.5 tho made entirely by an AI and thus got stale. IIRC the model arch is quite straghtforward - a lot of copies from QwenViT and i think an existing LM with AutoModel

Revewing in a sec 👁️

@zucchini-nlp zucchini-nlp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I might have repeated myself and commented on each nit, overall it looks good :)

Re the tests - lets clean up and remove some that arent really important or covered implicitly via IntegrationTest/ModelTesterMixin

@@ -0,0 +1,68 @@
<!--Copyright 2025 The HuggingFace Team. All rights reserved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to re-check that it's 2026 everywhere :)

LLaVA-OneVision-1.5 was introduced in [LLaVA-OneVision-1.5: Fully Open Framework for Democratized Multimodal Training](https://huggingface.co/papers/2509.23661).
It combines a native resolution vision encoder with a Qwen3 language backbone. The vision encoder adds a learned class token to each image, applies full attention, removes the class token, and merges spatial patches into visual features for the language model. The language backbone uses ordinary one dimensional rotary positions for the combined image and text sequence.

The implementation reuses the Qwen2.5-VL processor and supports the converted [4B checkpoint](https://huggingface.co/Deep-VLM/LLaVA-OneVision-1.5-4B-Instruct-hf). Use left padding for batched generation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to mentioned code implem here, can just say we support these ckpts/hub collection


The implementation reuses the Qwen2.5-VL processor and supports the converted [4B checkpoint](https://huggingface.co/Deep-VLM/LLaVA-OneVision-1.5-4B-Instruct-hf). Use left padding for batched generation.

## Abstract

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no new section needed, abstract is part of overview and is usually formatted in italics

## Usage

```python
from transformers import AutoProcessor, LlavaOnevision15ForConditionalGeneration

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use AutoForImageTextToText in model

Comment on lines +29 to +41
model_id = "Deep-VLM/LLaVA-OneVision-1.5-4B-Instruct-hf"
processor = AutoProcessor.from_pretrained(model_id)
model = LlavaOnevision15ForConditionalGeneration.from_pretrained(model_id, device_map="auto")
messages = [{"role": "user", "content": [
{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"},
{"type": "text", "text": "Describe the image."},
]}]
inputs = processor.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True, return_dict=True, return_tensors="pt"
).to(model.device)
output = model.generate(**inputs, max_new_tokens=64)
processor.batch_decode(output[:, inputs.input_ids.shape[1]:], skip_special_tokens=True)
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

niit: just a little more spacing for readability would be nice

Comment on lines +294 to +301
def test_mismatching_num_image_tokens(self):
"""
Tests that VLMs through an error with explicit message saying what is wrong
when number of images don't match number of image tokens in the text.
Also we need to test multi-image cases when one prompr has multiple image tokens.
"""
config, input_dict = self.model_tester.prepare_config_and_inputs_for_common()
for model_class in self.all_model_classes:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with VLMTester this one would not need to be overriden

Comment on lines +338 to +343
@require_torch
class LlavaOnevision15VisionModelTest(unittest.TestCase):
all_model_classes = (LlavaOnevision15VisionModel,) if is_torch_available() else ()

@slow
@require_torch_greater_or_equal("2.11")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need a single test class with all_model_class = (both classes here,)

Comment on lines +440 to +450
self.messages = [
{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": "What kind of dog is this?"},
],
}
]
url = "https://qianwen-res.oss-accelerate-overseas.aliyuncs.com/Qwen2-VL/demo_small.jpg"
self.image = Image.open(requests.get(url, stream=True).raw)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we put url inside a conversation dict and use ...test_processing.url_to_local_peth to load from cached location

If possible, also lets use an existing image from other tests, we are trying to keep small number of testing images and use it across all models

Comment on lines +466 to +480
expected_input_ids = [151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198, 151644, 872, 198, 151652, 151655, 151655] # fmt: skip
torch.testing.assert_close(expected_input_ids, inputs.input_ids[0].tolist()[:17])

expected_pixel_slice = torch.tensor(
[
[0.8792, 0.8792, 0.9084],
[1.1858, 1.1858, 1.2296],
[1.2004, 1.2004, 1.2150],
[1.4340, 1.4340, 1.4194],
[1.3902, 1.4048, 1.4194],
[1.5216, 1.5362, 1.5362],
],
dtype=torch.float32,
device="cpu",
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expectations class pls to get expectation for given hardware

Comment on lines +680 to +682
messages = [{"role": "user", "content": [{"type": "video"}, {"type": "text", "text": "Describe the video."}]}]
text = self.processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
video = np.stack([np.asarray(self.image), np.asarray(self.image.transpose(Image.Transpose.FLIP_LEFT_RIGHT))])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use real video from https://huggingface.co/datasets/hf-internal-testing/fixtures_videos and same comment about url-to-local-path

Reuse shared model components and VLM tests, simplify configuration and
checkpoint mappings, and use cached image and real video fixtures. Preserve
learned CLS gradients and compatibility with published and native checkpoints.
Preserve explicit weight tying in new configurations while retaining legacy checkpoint compatibility. Restore standalone vision loading from full checkpoints and cover both paths with small regression tests.
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution 🤗!

CI Security Gate — automatic approval blocked

This PR was not automatically approved for CI because the security gate failed.

Possible reasons:

  • The PR touches 50 or more files — only PRs with fewer than 50 changed files are automatically approved
  • A changed file is outside the allowed directories (src/, tests/, docs/, utils/), has a disallowed extension (only .py, .txt, .md permitted outside tests/ and docs/), or is not .md/.yml inside docs/ — this covers files the PR deletes or renames, not only the ones it edits
  • A new security issue was detected in the changed Python files (Bandit check) — a high-severity one, shell execution, exec/eval, a URL fetch, or disabled TLS verification
  • The PR touches a path this repository protects from untrusted PRs, such as the file that decides who reviews it — a maintainer must make that change in a separate PR

See the workflow run for the exact violations.

A maintainer can review and manually approve CI if a finding is a false positive.

@github-actions

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: auto, llava_onevision1_5

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.

Add support for LLaVA-OneVision-1.5 Multi-Modal Model

9 participants