Skip to content

Commit 1ffa423

Browse files
HaozheZhang6claudedg845
authored
ovis_image: fix guidance_scale / max_sequence_length / batched CFG / precomputed embeds + add pipeline test (huggingface#13944)
* ovis_image: fix guidance_scale / max_sequence_length / batched-CFG / precomputed embeds + add pipeline test Addresses items 3/4/5/6/7 of huggingface#13630. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ovis_image: complete pipeline review (huggingface#13630) - thread joint_attention_kwargs through the transformer forward + blocks (item 2) and pass it from the pipeline's transformer calls. - encode_prompt now returns both positive and negative embeds (the z_image / PixArt convention) so precomputed embeds work end-to-end and the prompt is encoded in a single call. - switch the pipeline test to the full PipelineTesterMixin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ovis_image: remove now-unused tokenizer_max_length The pipeline computes max_length inline from max_sequence_length, so the attribute is dead. Addresses review feedback. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>
1 parent 26ec30e commit 1ffa423

3 files changed

Lines changed: 233 additions & 26 deletions

File tree

src/diffusers/models/transformers/transformer_ovis_image.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from ...configuration_utils import ConfigMixin, register_to_config
2323
from ...loaders import FromOriginalModelMixin, PeftAdapterMixin
24-
from ...utils import logging
24+
from ...utils import apply_lora_scale, logging
2525
from ...utils.torch_utils import maybe_adjust_dtype_for_device, maybe_allow_in_graph
2626
from ..attention import AttentionModuleMixin, FeedForward
2727
from ..attention_dispatch import dispatch_attention_fn
@@ -473,13 +473,15 @@ def __init__(
473473

474474
self.gradient_checkpointing = False
475475

476+
@apply_lora_scale("joint_attention_kwargs")
476477
def forward(
477478
self,
478479
hidden_states: torch.Tensor,
479480
encoder_hidden_states: torch.Tensor = None,
480481
timestep: torch.LongTensor = None,
481482
img_ids: torch.Tensor = None,
482483
txt_ids: torch.Tensor = None,
484+
joint_attention_kwargs: dict[str, Any] | None = None,
483485
return_dict: bool = True,
484486
) -> torch.Tensor | Transformer2DModelOutput:
485487
"""
@@ -496,6 +498,10 @@ def forward(
496498
The position ids for image tokens.
497499
txt_ids (`torch.Tensor`):
498500
The position ids for text tokens.
501+
joint_attention_kwargs (`dict`, *optional*):
502+
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
503+
`self.processor` in
504+
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
499505
return_dict (`bool`, *optional*, defaults to `True`):
500506
Whether or not to return a [`~models.transformer_2d.Transformer2DModelOutput`] instead of a plain
501507
tuple.
@@ -538,6 +544,7 @@ def forward(
538544
encoder_hidden_states,
539545
temb,
540546
image_rotary_emb,
547+
joint_attention_kwargs,
541548
)
542549

543550
else:
@@ -546,6 +553,7 @@ def forward(
546553
encoder_hidden_states=encoder_hidden_states,
547554
temb=temb,
548555
image_rotary_emb=image_rotary_emb,
556+
joint_attention_kwargs=joint_attention_kwargs,
549557
)
550558

551559
for index_block, block in enumerate(self.single_transformer_blocks):
@@ -556,6 +564,7 @@ def forward(
556564
encoder_hidden_states,
557565
temb,
558566
image_rotary_emb,
567+
joint_attention_kwargs,
559568
)
560569

561570
else:
@@ -564,6 +573,7 @@ def forward(
564573
encoder_hidden_states=encoder_hidden_states,
565574
temb=temb,
566575
image_rotary_emb=image_rotary_emb,
576+
joint_attention_kwargs=joint_attention_kwargs,
567577
)
568578

569579
hidden_states = self.norm_out(hidden_states, temb)

src/diffusers/pipelines/ovis_image/pipeline_ovis_image.py

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ def __init__(
176176
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor * 2)
177177
self.system_prompt = "Describe the image by detailing the color, quantity, text, shape, size, texture, spatial relationships of the objects and background: "
178178
self.user_prompt_begin_id = 28
179-
self.tokenizer_max_length = 256 + self.user_prompt_begin_id
180179
self.default_sample_size = 128
181180

182181
def _get_messages(
@@ -202,11 +201,12 @@ def _get_ovis_prompt_embeds(
202201
self,
203202
prompt: str | list[str] = None,
204203
num_images_per_prompt: int = 1,
204+
max_sequence_length: int = 256,
205205
device: torch.device | None = None,
206206
dtype: torch.dtype | None = None,
207207
):
208208
device = device or self._execution_device
209-
dtype = dtype or self.text_encoder.dtype
209+
dtype = dtype or (self.text_encoder.dtype if self.text_encoder is not None else self.transformer.dtype)
210210

211211
messages = self._get_messages(prompt)
212212
batch_size = len(messages)
@@ -215,7 +215,7 @@ def _get_ovis_prompt_embeds(
215215
messages,
216216
padding="max_length",
217217
truncation=True,
218-
max_length=self.tokenizer_max_length,
218+
max_length=max_sequence_length + self.user_prompt_begin_id,
219219
return_tensors="pt",
220220
add_special_tokens=False,
221221
)
@@ -237,41 +237,86 @@ def _get_ovis_prompt_embeds(
237237

238238
return prompt_embeds
239239

240+
def _prepare_prompt_embeds(
241+
self,
242+
prompt: str | list[str],
243+
prompt_embeds: torch.FloatTensor | None,
244+
num_images_per_prompt: int,
245+
max_sequence_length: int,
246+
device: torch.device,
247+
):
248+
if prompt_embeds is None:
249+
prompt_embeds = self._get_ovis_prompt_embeds(
250+
prompt=prompt,
251+
device=device,
252+
num_images_per_prompt=num_images_per_prompt,
253+
max_sequence_length=max_sequence_length,
254+
)
255+
else:
256+
dtype = self.text_encoder.dtype if self.text_encoder is not None else self.transformer.dtype
257+
prompt_embeds = prompt_embeds.to(device=device, dtype=dtype)
258+
batch_size, seq_len, _ = prompt_embeds.shape
259+
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
260+
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
261+
262+
dtype = self.text_encoder.dtype if self.text_encoder is not None else self.transformer.dtype
263+
text_ids = torch.zeros(prompt_embeds.shape[1], 3)
264+
text_ids[..., 1] = text_ids[..., 1] + torch.arange(prompt_embeds.shape[1])[None, :]
265+
text_ids[..., 2] = text_ids[..., 2] + torch.arange(prompt_embeds.shape[1])[None, :]
266+
text_ids = text_ids.to(device=device, dtype=dtype)
267+
return prompt_embeds, text_ids
268+
240269
def encode_prompt(
241270
self,
242271
prompt: str | list[str],
272+
negative_prompt: str | list[str] | None = None,
273+
do_classifier_free_guidance: bool = True,
243274
device: torch.device | None = None,
244275
num_images_per_prompt: int = 1,
276+
max_sequence_length: int = 256,
245277
prompt_embeds: torch.FloatTensor | None = None,
278+
negative_prompt_embeds: torch.FloatTensor | None = None,
246279
):
247280
r"""
248281
249282
Args:
250-
prompt (`str`, *optional*):
283+
prompt (`str` or `list[str]`, *optional*):
251284
prompt to be encoded
285+
negative_prompt (`str` or `list[str]`, *optional*):
286+
The prompt or prompts not to guide the image generation. Used only when `do_classifier_free_guidance`
287+
is `True`. If not defined, an empty string is used.
288+
do_classifier_free_guidance (`bool`, *optional*, defaults to `True`):
289+
Whether to also encode the `negative_prompt` for classifier-free guidance.
252290
device: (`torch.device`):
253291
torch device
254292
num_images_per_prompt (`int`):
255293
number of images that should be generated per prompt
294+
max_sequence_length (`int`, *optional*, defaults to 256):
295+
Maximum sequence length to use for the `prompt`.
256296
prompt_embeds (`torch.FloatTensor`, *optional*):
257297
Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
258298
provided, text embeddings will be generated from `prompt` input argument.
299+
negative_prompt_embeds (`torch.FloatTensor`, *optional*):
300+
Pre-generated negative text embeddings. If not provided, they are generated from `negative_prompt`.
259301
"""
260302
device = device or self._execution_device
261303

262-
if prompt_embeds is None:
263-
prompt_embeds = self._get_ovis_prompt_embeds(
264-
prompt=prompt,
265-
device=device,
266-
num_images_per_prompt=num_images_per_prompt,
304+
prompt_embeds, text_ids = self._prepare_prompt_embeds(
305+
prompt, prompt_embeds, num_images_per_prompt, max_sequence_length, device
306+
)
307+
308+
negative_text_ids = None
309+
if do_classifier_free_guidance:
310+
if negative_prompt is None:
311+
negative_prompt = ""
312+
if isinstance(negative_prompt, str):
313+
batch_size = prompt_embeds.shape[0] // num_images_per_prompt
314+
negative_prompt = batch_size * [negative_prompt]
315+
negative_prompt_embeds, negative_text_ids = self._prepare_prompt_embeds(
316+
negative_prompt, negative_prompt_embeds, num_images_per_prompt, max_sequence_length, device
267317
)
268318

269-
dtype = self.text_encoder.dtype if self.text_encoder is not None else self.transformer.dtype
270-
text_ids = torch.zeros(prompt_embeds.shape[1], 3)
271-
text_ids[..., 1] = text_ids[..., 1] + torch.arange(prompt_embeds.shape[1])[None, :]
272-
text_ids[..., 2] = text_ids[..., 2] + torch.arange(prompt_embeds.shape[1])[None, :]
273-
text_ids = text_ids.to(device=device, dtype=dtype)
274-
return prompt_embeds, text_ids
319+
return prompt_embeds, negative_prompt_embeds, text_ids, negative_text_ids
275320

276321
def check_inputs(
277322
self,
@@ -516,6 +561,7 @@ def __call__(
516561
max_sequence_length=max_sequence_length,
517562
)
518563

564+
self._guidance_scale = guidance_scale
519565
self._joint_attention_kwargs = joint_attention_kwargs
520566
self._current_timestep = None
521567
self._interrupt = False
@@ -533,23 +579,19 @@ def __call__(
533579
do_classifier_free_guidance = guidance_scale > 1
534580
(
535581
prompt_embeds,
582+
negative_prompt_embeds,
536583
text_ids,
584+
negative_text_ids,
537585
) = self.encode_prompt(
538586
prompt=prompt,
587+
negative_prompt=negative_prompt,
588+
do_classifier_free_guidance=do_classifier_free_guidance,
539589
prompt_embeds=prompt_embeds,
590+
negative_prompt_embeds=negative_prompt_embeds,
540591
device=device,
541592
num_images_per_prompt=num_images_per_prompt,
593+
max_sequence_length=max_sequence_length,
542594
)
543-
if do_classifier_free_guidance:
544-
(
545-
negative_prompt_embeds,
546-
negative_text_ids,
547-
) = self.encode_prompt(
548-
prompt=negative_prompt,
549-
prompt_embeds=negative_prompt_embeds,
550-
device=device,
551-
num_images_per_prompt=num_images_per_prompt,
552-
)
553595

554596
# 4. Prepare latent variables
555597
num_channels_latents = self.transformer.config.in_channels // 4
@@ -609,6 +651,7 @@ def __call__(
609651
encoder_hidden_states=prompt_embeds,
610652
txt_ids=text_ids,
611653
img_ids=latent_image_ids,
654+
joint_attention_kwargs=self.joint_attention_kwargs,
612655
return_dict=False,
613656
)[0]
614657

@@ -620,6 +663,7 @@ def __call__(
620663
encoder_hidden_states=negative_prompt_embeds,
621664
txt_ids=negative_text_ids,
622665
img_ids=latent_image_ids,
666+
joint_attention_kwargs=self.joint_attention_kwargs,
623667
return_dict=False,
624668
)[0]
625669
noise_pred = neg_noise_pred + guidance_scale * (noise_pred - neg_noise_pred)

0 commit comments

Comments
 (0)