[Bugfix] enforce max_sequence_length for Qwen-Image and Wan2.2 series before encoding (#2847)

Signed-off-by: david6666666 <530634352@qq.com>
This commit is contained in:
WeiQing Chen authored and GitHub committed 2026-04-17 10:40:36 +08:00
1 parent 1e3bb36521
commit 3079e943de
11 files changed
+744 -27

No files matched your search

@@ -0,0 +1,260 @@
import inspect
from types import SimpleNamespace
import pytest
import torch
from torch import nn
from vllm_omni.diffusion.models.qwen_image.pipeline_qwen_image import (
QwenImagePipeline,
)
from vllm_omni.diffusion.models.qwen_image.pipeline_qwen_image_edit import (
QwenImageEditPipeline,
)
from vllm_omni.diffusion.models.qwen_image.pipeline_qwen_image_edit_plus import (
QwenImageEditPlusPipeline,
)
from vllm_omni.diffusion.models.qwen_image.pipeline_qwen_image_layered import (
QwenImageLayeredPipeline,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _RejectingTextEncoder:
dtype = torch.float32
def __call__(self, *args, **kwargs):
raise AssertionError("text encoder should not run for prompts that exceed max_sequence_length")
class _FakeModelInputs:
def __init__(self, total_sequence_length: int):
attention_mask = torch.ones((1, total_sequence_length), dtype=torch.long)
self.input_ids = attention_mask.clone()
self.attention_mask = attention_mask
self.pixel_values = None
self.image_grid_thw = None
def to(self, device):
return self
class _FakeTokenizer:
def __init__(self, total_sequence_length: int | list[int]):
if isinstance(total_sequence_length, list):
self.total_sequence_lengths = list(total_sequence_length)
else:
self.total_sequence_lengths = [total_sequence_length]
def __call__(self, *args, **kwargs):
if len(self.total_sequence_lengths) > 1:
total_sequence_length = self.total_sequence_lengths.pop(0)
else:
total_sequence_length = self.total_sequence_lengths[0]
return _FakeModelInputs(total_sequence_length)
class _FakeProcessor(_FakeTokenizer):
pass
class _FakeScheduler:
def __init__(self):
self.begin_index = None
def set_begin_index(self, begin_index: int):
self.begin_index = begin_index
PIPELINE_CASES = [
pytest.param(QwenImagePipeline, 34, "tokenizer", id="qwen-image"),
pytest.param(QwenImageLayeredPipeline, 34, "tokenizer", id="qwen-image-layered"),
pytest.param(QwenImageEditPipeline, 64, "processor", id="qwen-image-edit"),
pytest.param(QwenImageEditPlusPipeline, 64, "processor", id="qwen-image-edit-plus"),
]
def _make_pipeline(
pipeline_class: type,
*,
total_sequence_length: int,
drop_idx: int,
input_kind: str,
):
pipeline = object.__new__(pipeline_class)
nn.Module.__init__(pipeline)
pipeline.device = torch.device("cpu")
pipeline.text_encoder = _RejectingTextEncoder()
pipeline.tokenizer_max_length = 1024
pipeline.prompt_template_encode = "{}"
pipeline.prompt_template_encode_start_idx = drop_idx
pipeline.tokenizer = _FakeTokenizer([total_sequence_length, 0])
if input_kind == "processor":
pipeline.processor = _FakeProcessor(total_sequence_length)
return pipeline
@pytest.mark.parametrize(("pipeline_class", "drop_idx", "input_kind"), PIPELINE_CASES)
def test_encode_prompt_rejects_prompt_longer_than_default_max_sequence_length(
pipeline_class: type,
drop_idx: int,
input_kind: str,
):
pipeline = _make_pipeline(
pipeline_class,
total_sequence_length=1025,
drop_idx=drop_idx,
input_kind=input_kind,
)
with pytest.raises(ValueError, match=r"got 1025 tokens, but `max_sequence_length` is 1024"):
pipeline.encode_prompt(prompt="prompt")
@pytest.mark.parametrize(("pipeline_class", "drop_idx", "input_kind"), PIPELINE_CASES)
def test_encode_prompt_rejects_prompt_longer_than_explicit_max_sequence_length(
pipeline_class: type,
drop_idx: int,
input_kind: str,
):
pipeline = _make_pipeline(
pipeline_class,
total_sequence_length=17,
drop_idx=drop_idx,
input_kind=input_kind,
)
with pytest.raises(ValueError, match=r"got 17 tokens, but `max_sequence_length` is 16"):
pipeline.encode_prompt(prompt="prompt", max_sequence_length=16)
def test_prepare_encode_defaults_to_tokenizer_max_length():
pipeline = object.__new__(QwenImagePipeline)
nn.Module.__init__(pipeline)
pipeline.tokenizer_max_length = 1024
pipeline.vae_scale_factor = 8
pipeline.default_sample_size = 128
pipeline.scheduler = _FakeScheduler()
pipeline._extract_prompts = lambda prompts: (["prompt"], None)
captured = {}
def _fake_prepare_generation_context(**kwargs):
captured["max_sequence_length"] = kwargs["max_sequence_length"]
embeds = torch.ones((1, 1, 1))
mask = torch.ones((1, 1), dtype=torch.long)
return {
"prompt_embeds": embeds,
"prompt_embeds_mask": mask,
"negative_prompt_embeds": None,
"negative_prompt_embeds_mask": None,
"latents": embeds,
"timesteps": torch.tensor([1]),
"do_true_cfg": False,
"guidance": None,
"img_shapes": [[(1, 1, 1)]],
"txt_seq_lens": [1],
"negative_txt_seq_lens": None,
}
pipeline._prepare_generation_context = _fake_prepare_generation_context
state = SimpleNamespace(
prompts=["prompt"],
sampling=SimpleNamespace(
height=None,
width=None,
num_inference_steps=None,
sigmas=None,
guidance_scale_provided=False,
num_outputs_per_prompt=0,
generator=None,
true_cfg_scale=None,
max_sequence_length=None,
),
)
pipeline.prepare_encode(state)
assert captured["max_sequence_length"] == 1024
@pytest.mark.parametrize(
("pipeline_class", "drop_idx"),
[
pytest.param(QwenImageEditPipeline, 64, id="qwen-image-edit"),
pytest.param(QwenImageEditPlusPipeline, 64, id="qwen-image-edit-plus"),
],
)
def test_edit_pipelines_validate_text_prompt_length_before_image_token_expansion(
pipeline_class: type,
drop_idx: int,
):
pipeline = object.__new__(pipeline_class)
nn.Module.__init__(pipeline)
pipeline.device = torch.device("cpu")
pipeline.text_encoder = _RejectingTextEncoder()
pipeline.tokenizer_max_length = 1024
pipeline.prompt_template_encode = "{}"
pipeline.prompt_template_encode_start_idx = drop_idx
pipeline.tokenizer = _FakeTokenizer([8, 0])
pipeline.processor = _FakeProcessor(drop_idx + 1500)
with pytest.raises(AssertionError, match="text encoder should not run"):
pipeline.encode_prompt(prompt="short prompt")
@pytest.mark.parametrize(
"pipeline_class",
[
pytest.param(QwenImagePipeline, id="qwen-image"),
pytest.param(QwenImageLayeredPipeline, id="qwen-image-layered"),
],
)
def test_qwen_generation_validator_excludes_template_suffix_from_budget(pipeline_class: type):
pipeline = object.__new__(pipeline_class)
nn.Module.__init__(pipeline)
pipeline.device = torch.device("cpu")
pipeline.text_encoder = _RejectingTextEncoder()
pipeline.tokenizer_max_length = 1024
pipeline.prompt_template_encode = "{}"
pipeline.prompt_template_encode_start_idx = 34
pipeline.tokenizer = _FakeTokenizer([1029, 5])
with pytest.raises(AssertionError, match="text encoder should not run"):
pipeline.encode_prompt(prompt="boundary prompt")
@pytest.mark.parametrize(
"pipeline_class",
[
pytest.param(QwenImageEditPipeline, id="qwen-image-edit"),
pytest.param(QwenImageEditPlusPipeline, id="qwen-image-edit-plus"),
],
)
def test_qwen_edit_validator_excludes_image_placeholders_from_budget(pipeline_class: type):
pipeline = object.__new__(pipeline_class)
nn.Module.__init__(pipeline)
pipeline.device = torch.device("cpu")
pipeline.text_encoder = _RejectingTextEncoder()
pipeline.tokenizer_max_length = 1024
pipeline.prompt_template_encode = "{}"
pipeline.prompt_template_encode_start_idx = 64
pipeline.tokenizer = _FakeTokenizer([30, 20])
pipeline.processor = _FakeProcessor(1500)
with pytest.raises(AssertionError, match="text encoder should not run"):
pipeline.encode_prompt(prompt="short prompt")
@pytest.mark.parametrize(
"pipeline_class",
[
QwenImagePipeline,
QwenImageLayeredPipeline,
QwenImageEditPipeline,
QwenImageEditPlusPipeline,
],
)
def test_forward_max_sequence_length_default_is_1024(pipeline_class: type):
assert inspect.signature(pipeline_class.forward).parameters["max_sequence_length"].default == 1024
@@ -0,0 +1,150 @@
from types import SimpleNamespace
import PIL.Image
import pytest
import torch
from torch import nn
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
WAN22_MAX_SEQUENCE_LENGTH,
Wan22Pipeline,
)
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2_i2v import (
Wan22I2VPipeline,
)
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2_ti2v import (
Wan22TI2VPipeline,
)
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2_vace import (
Wan22VACEPipeline,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _RejectingTextEncoder:
dtype = torch.float32
def __call__(self, *args, **kwargs):
raise AssertionError("text encoder should not run for prompts that exceed max_sequence_length")
class _FakeTokenBatch:
def __init__(self, total_sequence_length: int):
attention_mask = torch.ones((1, total_sequence_length), dtype=torch.long)
self.input_ids = attention_mask.clone()
self.attention_mask = attention_mask
class _FakeTokenizer:
def __init__(self, total_sequence_length: int):
self.total_sequence_length = total_sequence_length
def __call__(self, *args, **kwargs):
return _FakeTokenBatch(self.total_sequence_length)
PIPELINE_CASES = [
pytest.param(Wan22Pipeline, id="wan22-t2v"),
pytest.param(Wan22I2VPipeline, id="wan22-i2v"),
pytest.param(Wan22TI2VPipeline, id="wan22-ti2v"),
pytest.param(Wan22VACEPipeline, id="wan22-vace"),
]
def _make_pipeline(pipeline_class: type, *, total_sequence_length: int):
pipeline = object.__new__(pipeline_class)
nn.Module.__init__(pipeline)
pipeline.device = torch.device("cpu")
pipeline.text_encoder = _RejectingTextEncoder()
pipeline.tokenizer = _FakeTokenizer(total_sequence_length)
pipeline.tokenizer_max_length = WAN22_MAX_SEQUENCE_LENGTH
return pipeline
@pytest.mark.parametrize("pipeline_class", PIPELINE_CASES)
def test_encode_prompt_rejects_prompt_longer_than_default_max_sequence_length(pipeline_class: type):
pipeline = _make_pipeline(pipeline_class, total_sequence_length=WAN22_MAX_SEQUENCE_LENGTH + 1)
with pytest.raises(ValueError, match=r"got 513 tokens, but `max_sequence_length` is 512"):
pipeline.encode_prompt(prompt="prompt")
@pytest.mark.parametrize("pipeline_class", PIPELINE_CASES)
def test_encode_prompt_rejects_prompt_longer_than_explicit_max_sequence_length(pipeline_class: type):
pipeline = _make_pipeline(pipeline_class, total_sequence_length=17)
with pytest.raises(ValueError, match=r"got 17 tokens, but `max_sequence_length` is 16"):
pipeline.encode_prompt(prompt="prompt", max_sequence_length=16)
def _sampling_params(**overrides):
defaults = dict(
height=None,
width=None,
num_frames=None,
num_inference_steps=None,
generator=None,
guidance_scale_provided=False,
guidance_scale_2=None,
boundary_ratio=None,
num_outputs_per_prompt=0,
max_sequence_length=None,
seed=None,
extra_args={},
prompt_embeds=None,
negative_prompt_embeds=None,
)
defaults.update(overrides)
return SimpleNamespace(**defaults)
@pytest.mark.parametrize(
("pipeline_class", "prompt_value", "forward_kwargs"),
[
pytest.param(Wan22Pipeline, "prompt", {}, id="wan22-t2v"),
pytest.param(
Wan22I2VPipeline,
{"prompt": "prompt", "multi_modal_data": {"image": PIL.Image.new("RGB", (64, 64))}},
{"image": PIL.Image.new("RGB", (64, 64))},
id="wan22-i2v",
),
pytest.param(
Wan22TI2VPipeline,
{"prompt": "prompt", "multi_modal_data": {"image": PIL.Image.new("RGB", (64, 64))}},
{"image": PIL.Image.new("RGB", (64, 64))},
id="wan22-ti2v",
),
pytest.param(Wan22VACEPipeline, "prompt", {}, id="wan22-vace"),
],
)
def test_forward_defaults_to_wan22_tokenizer_max_length(
pipeline_class: type,
prompt_value,
forward_kwargs,
):
pipeline = object.__new__(pipeline_class)
nn.Module.__init__(pipeline)
pipeline.tokenizer_max_length = WAN22_MAX_SEQUENCE_LENGTH
pipeline.boundary_ratio = None
pipeline.vae_scale_factor_temporal = 4
pipeline.vae_scale_factor_spatial = 8
pipeline.transformer_config = SimpleNamespace(patch_size=(1, 2, 2))
captured = {}
def _fake_check_inputs(*args, **kwargs):
captured["max_sequence_length"] = kwargs["max_sequence_length"]
raise RuntimeError("stop after capture")
pipeline.check_inputs = _fake_check_inputs
req = SimpleNamespace(
prompts=[prompt_value],
sampling_params=_sampling_params(),
)
with pytest.raises(RuntimeError, match="stop after capture"):
pipeline.forward(req, **forward_kwargs)
assert captured["max_sequence_length"] == WAN22_MAX_SEQUENCE_LENGTH
@@ -34,6 +34,9 @@ from vllm_omni.diffusion.models.qwen_image.qwen_image_transformer import (
)
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.utils.prompt_utils import (
validate_prompt_sequence_lengths,
)
from vllm_omni.diffusion.utils.size_utils import (
normalize_min_aligned_size,
)
@@ -363,8 +366,10 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
"that was used to generate `negative_prompt_embeds`."
)
if max_sequence_length is not None and max_sequence_length > 1024:
raise ValueError(f"`max_sequence_length` cannot be greater than 1024 but is {max_sequence_length}")
if max_sequence_length is not None and max_sequence_length > self.tokenizer_max_length:
raise ValueError(
f"`max_sequence_length` cannot be greater than {self.tokenizer_max_length} but is {max_sequence_length}"
)
def _extract_masked_hidden(self, hidden_states: torch.Tensor, mask: torch.Tensor):
bool_mask = mask.bool()
@@ -378,6 +383,8 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
self,
prompt: str | list[str] = None,
dtype: torch.dtype | None = None,
max_sequence_length: int | None = None,
prompt_name: str = "prompt",
):
dtype = dtype or self.text_encoder.dtype
@@ -388,12 +395,27 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
txt = [template.format(e) for e in prompt]
txt_tokens = self.tokenizer(
txt,
max_length=self.tokenizer_max_length + drop_idx,
padding=True,
truncation=True,
truncation=False,
return_tensors="pt",
).to(self.device)
# print(f"attention mask: {txt_tokens.attention_mask}")
# Validate only the user prompt contribution. The Qwen template also
# adds a fixed suffix after the user text, so subtracting only
# prompt_template_encode_start_idx would overcount near-limit prompts.
template_tokens = self.tokenizer(
[template.format("")],
padding=True,
truncation=False,
return_tensors="pt",
).to(self.device)
validate_prompt_sequence_lengths(
txt_tokens.attention_mask,
max_sequence_length=max_sequence_length or self.tokenizer_max_length,
supported_max_sequence_length=self.tokenizer_max_length,
prompt_name=prompt_name,
baseline_attention_mask=template_tokens.attention_mask,
error_context="after applying the Qwen prompt template",
)
encoder_hidden_states = self.text_encoder(
input_ids=txt_tokens.input_ids,
attention_mask=txt_tokens.attention_mask,
@@ -422,6 +444,7 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
prompt_embeds: torch.Tensor | None = None,
prompt_embeds_mask: torch.Tensor | None = None,
max_sequence_length: int = 1024,
prompt_name: str = "prompt",
):
r"""
@@ -439,7 +462,11 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
batch_size = len(prompt) if prompt_embeds is None else prompt_embeds.shape[0]
if prompt_embeds is None:
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(prompt)
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(
prompt,
max_sequence_length=max_sequence_length,
prompt_name=prompt_name,
)
prompt_embeds = prompt_embeds[:, :max_sequence_length]
prompt_embeds_mask = prompt_embeds_mask[:, :max_sequence_length]
@@ -632,6 +659,7 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
prompt_embeds_mask=negative_prompt_embeds_mask,
num_images_per_prompt=num_images_per_prompt,
max_sequence_length=max_sequence_length,
prompt_name="negative_prompt",
)
else:
negative_prompt_embeds = None
@@ -703,7 +731,7 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
num_images_per_prompt=sampling.num_outputs_per_prompt if sampling.num_outputs_per_prompt > 0 else 1,
generator=sampling.generator,
true_cfg_scale=sampling.true_cfg_scale or 4.0,
max_sequence_length=sampling.max_sequence_length or 512,
max_sequence_length=sampling.max_sequence_length or self.tokenizer_max_length,
attention_kwargs=kwargs.get("attention_kwargs"),
)
@@ -934,7 +962,7 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin, DiffusionPipelineP
output_type: str | None = "pil",
attention_kwargs: dict[str, Any] | None = None,
callback_on_step_end_tensor_inputs: list[str] = ["latents"],
max_sequence_length: int = 512,
max_sequence_length: int = 1024,
) -> DiffusionOutput:
extracted_prompt, negative_prompt = self._extract_prompts(req.prompts)
prompt = extracted_prompt or prompt
@@ -37,6 +37,9 @@ from vllm_omni.diffusion.models.qwen_image.qwen_image_transformer import (
)
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.utils.prompt_utils import (
validate_prompt_sequence_lengths,
)
from vllm_omni.diffusion.utils.size_utils import (
normalize_min_aligned_size,
)
@@ -323,8 +326,10 @@ class QwenImageEditPipeline(nn.Module, SupportImageInput, QwenImageCFGParallelMi
"that was used to generate `negative_prompt_embeds`."
)
if max_sequence_length is not None and max_sequence_length > 1024:
raise ValueError(f"`max_sequence_length` cannot be greater than 1024 but is {max_sequence_length}")
if max_sequence_length is not None and max_sequence_length > self.tokenizer_max_length:
raise ValueError(
f"`max_sequence_length` cannot be greater than {self.tokenizer_max_length} but is {max_sequence_length}"
)
def _extract_masked_hidden(self, hidden_states: torch.Tensor, mask: torch.Tensor):
bool_mask = mask.bool()
@@ -384,6 +389,8 @@ class QwenImageEditPipeline(nn.Module, SupportImageInput, QwenImageCFGParallelMi
prompt: str | list[str] = None,
image: PIL.Image.Image | torch.Tensor | None = None,
dtype: torch.dtype | None = None,
max_sequence_length: int | None = None,
prompt_name: str = "prompt",
):
"""Get prompt embeddings with image support for editing."""
dtype = dtype or self.text_encoder.dtype
@@ -393,6 +400,33 @@ class QwenImageEditPipeline(nn.Module, SupportImageInput, QwenImageCFGParallelMi
template = self.prompt_template_encode
drop_idx = self.prompt_template_encode_start_idx
txt = [template.format(e) for e in prompt]
txt_tokens = self.tokenizer(
txt,
padding=True,
truncation=False,
return_tensors="pt",
).to(self.device)
# The edit template contains fixed multimodal scaffolding around the
# instruction. Validate against the empty-template baseline so image
# placeholder text does not consume the user's text budget.
template_tokens = self.tokenizer(
[template.format("")],
padding=True,
truncation=False,
return_tensors="pt",
).to(self.device)
# Qwen-Image-Edit expands image placeholders into many vision tokens
# inside the processor. `max_sequence_length` is meant to constrain the
# prompt text length, so validate on the text template before image
# token expansion.
validate_prompt_sequence_lengths(
txt_tokens.attention_mask,
max_sequence_length=max_sequence_length or self.tokenizer_max_length,
supported_max_sequence_length=self.tokenizer_max_length,
prompt_name=prompt_name,
baseline_attention_mask=template_tokens.attention_mask,
error_context="after applying the Qwen prompt template",
)
# Use processor to handle both text and image inputs
model_inputs = self.processor(
@@ -434,6 +468,7 @@ class QwenImageEditPipeline(nn.Module, SupportImageInput, QwenImageCFGParallelMi
prompt_embeds: torch.Tensor | None = None,
prompt_embeds_mask: torch.Tensor | None = None,
max_sequence_length: int = 1024,
prompt_name: str = "prompt",
):
r"""
@@ -453,7 +488,12 @@ class QwenImageEditPipeline(nn.Module, SupportImageInput, QwenImageCFGParallelMi
batch_size = len(prompt) if prompt_embeds is None else prompt_embeds.shape[0]
if prompt_embeds is None:
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(prompt, image)
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(
prompt,
image,
max_sequence_length=max_sequence_length,
prompt_name=prompt_name,
)
_, seq_len, _ = prompt_embeds.shape
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
@@ -624,7 +664,7 @@ class QwenImageEditPipeline(nn.Module, SupportImageInput, QwenImageCFGParallelMi
output_type: str | None = "pil",
attention_kwargs: dict[str, Any] | None = None,
callback_on_step_end_tensor_inputs: list[str] = ["latents"],
max_sequence_length: int = 512,
max_sequence_length: int = 1024,
) -> DiffusionOutput:
"""Forward pass for image editing."""
# TODO: In online mode, sometimes it receives [{"negative_prompt": None}, {...}], so cannot use .get("...", "")
@@ -739,6 +779,7 @@ class QwenImageEditPipeline(nn.Module, SupportImageInput, QwenImageCFGParallelMi
prompt_embeds_mask=negative_prompt_embeds_mask,
num_images_per_prompt=num_images_per_prompt,
max_sequence_length=max_sequence_length,
prompt_name="negative_prompt",
)
num_channels_latents = self.transformer.in_channels // 4
@@ -40,6 +40,9 @@ from vllm_omni.diffusion.models.qwen_image.qwen_image_transformer import (
)
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.utils.prompt_utils import (
validate_prompt_sequence_lengths,
)
from vllm_omni.diffusion.utils.size_utils import (
normalize_min_aligned_size,
)
@@ -283,8 +286,10 @@ class QwenImageEditPlusPipeline(
"that was used to generate `negative_prompt_embeds`."
)
if max_sequence_length is not None and max_sequence_length > 1024:
raise ValueError(f"`max_sequence_length` cannot be greater than 1024 but is {max_sequence_length}")
if max_sequence_length is not None and max_sequence_length > self.tokenizer_max_length:
raise ValueError(
f"`max_sequence_length` cannot be greater than {self.tokenizer_max_length} but is {max_sequence_length}"
)
def _extract_masked_hidden(self, hidden_states: torch.Tensor, mask: torch.Tensor):
bool_mask = mask.bool()
@@ -299,6 +304,8 @@ class QwenImageEditPlusPipeline(
prompt: str | list[str],
image: list[torch.Tensor] | torch.Tensor | None = None,
dtype: torch.dtype | None = None,
max_sequence_length: int | None = None,
prompt_name: str = "prompt",
):
"""Get prompt embeddings with support for multiple images."""
dtype = dtype or self.text_encoder.dtype
@@ -319,6 +326,32 @@ class QwenImageEditPlusPipeline(
template = self.prompt_template_encode
drop_idx = self.prompt_template_encode_start_idx
txt = [template.format(base_img_prompt + e) for e in prompt]
txt_tokens = self.tokenizer(
txt,
padding=True,
truncation=False,
return_tensors="pt",
).to(self.device)
# Multi-image edit prepends "Picture N" placeholders before the user
# instruction. Subtract the placeholder-aware baseline so attached
# images do not reduce the remaining prompt budget.
template_tokens = self.tokenizer(
[template.format(base_img_prompt)],
padding=True,
truncation=False,
return_tensors="pt",
).to(self.device)
# The processor expands image placeholders into many vision tokens.
# `max_sequence_length` should guard the prompt text length before that
# multimodal expansion happens.
validate_prompt_sequence_lengths(
txt_tokens.attention_mask,
max_sequence_length=max_sequence_length or self.tokenizer_max_length,
supported_max_sequence_length=self.tokenizer_max_length,
prompt_name=prompt_name,
baseline_attention_mask=template_tokens.attention_mask,
error_context="after applying the Qwen prompt template",
)
# Use processor to handle both text and image inputs
model_inputs = self.processor(
@@ -360,6 +393,7 @@ class QwenImageEditPlusPipeline(
prompt_embeds: torch.Tensor | None = None,
prompt_embeds_mask: torch.Tensor | None = None,
max_sequence_length: int = 1024,
prompt_name: str = "prompt",
):
r"""
@@ -379,7 +413,12 @@ class QwenImageEditPlusPipeline(
batch_size = len(prompt) if prompt_embeds is None else prompt_embeds.shape[0]
if prompt_embeds is None:
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(prompt, image)
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(
prompt,
image,
max_sequence_length=max_sequence_length,
prompt_name=prompt_name,
)
_, seq_len, _ = prompt_embeds.shape
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
@@ -557,7 +596,7 @@ class QwenImageEditPlusPipeline(
output_type: str | None = "pil",
attention_kwargs: dict[str, Any] | None = None,
callback_on_step_end_tensor_inputs: list[str] = ["latents"],
max_sequence_length: int = 512,
max_sequence_length: int = 1024,
) -> DiffusionOutput:
"""Forward pass for image editing with support for multiple images."""
# TODO: In online mode, sometimes it receives [{"negative_prompt": None}, {...}], so cannot use .get("...", "")
@@ -692,6 +731,7 @@ class QwenImageEditPlusPipeline(
prompt_embeds_mask=negative_prompt_embeds_mask,
num_images_per_prompt=num_images_per_prompt,
max_sequence_length=max_sequence_length,
prompt_name="negative_prompt",
)
num_channels_latents = self.transformer.in_channels // 4
@@ -36,6 +36,9 @@ from vllm_omni.diffusion.models.qwen_image.qwen_image_transformer import (
)
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.utils.prompt_utils import (
validate_prompt_sequence_lengths,
)
from vllm_omni.diffusion.utils.size_utils import (
normalize_min_aligned_size,
)
@@ -340,8 +343,10 @@ the image\n<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n<|im_start|>as
"generate `negative_prompt_embeds`."
)
if max_sequence_length is not None and max_sequence_length > 1024:
raise ValueError(f"`max_sequence_length` cannot be greater than 1024 but is {max_sequence_length}")
if max_sequence_length is not None and max_sequence_length > self.tokenizer_max_length:
raise ValueError(
f"`max_sequence_length` cannot be greater than {self.tokenizer_max_length} but is {max_sequence_length}"
)
def _extract_masked_hidden(self, hidden_states: torch.Tensor, mask: torch.Tensor):
bool_mask = mask.bool()
@@ -356,6 +361,8 @@ the image\n<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n<|im_start|>as
prompt: str | list[str] | None = None,
device: torch.device | None = None,
dtype: torch.dtype | None = None,
max_sequence_length: int | None = None,
prompt_name: str = "prompt",
):
device = device or self.device
dtype = dtype or self.text_encoder.dtype
@@ -368,8 +375,26 @@ the image\n<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n<|im_start|>as
txt_tokens = self.tokenizer(
txt,
padding=True,
truncation=False,
return_tensors="pt",
).to(device)
# The layered template also appends fixed non-user tokens after the
# editable text, so use the empty-template tokenized baseline instead of
# counting everything after prompt_template_encode_start_idx.
template_tokens = self.tokenizer(
[template.format("")],
padding=True,
truncation=False,
return_tensors="pt",
).to(device)
validate_prompt_sequence_lengths(
txt_tokens.attention_mask,
max_sequence_length=max_sequence_length or self.tokenizer_max_length,
supported_max_sequence_length=self.tokenizer_max_length,
prompt_name=prompt_name,
baseline_attention_mask=template_tokens.attention_mask,
error_context="after applying the Qwen prompt template",
)
encoder_hidden_states = self.text_encoder(
input_ids=txt_tokens.input_ids,
attention_mask=txt_tokens.attention_mask,
@@ -399,6 +424,7 @@ the image\n<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n<|im_start|>as
prompt_embeds: torch.Tensor | None = None,
prompt_embeds_mask: torch.Tensor | None = None,
max_sequence_length: int = 1024,
prompt_name: str = "prompt",
):
r"""
@@ -419,7 +445,11 @@ the image\n<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n<|im_start|>as
batch_size = len(prompt) if prompt_embeds is None else prompt_embeds.shape[0]
if prompt_embeds is None:
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(prompt)
prompt_embeds, prompt_embeds_mask = self._get_qwen_prompt_embeds(
prompt,
max_sequence_length=max_sequence_length,
prompt_name=prompt_name,
)
prompt_embeds = prompt_embeds[:, :max_sequence_length]
prompt_embeds_mask = prompt_embeds_mask[:, :max_sequence_length]
@@ -603,7 +633,7 @@ the image\n<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n<|im_start|>as
negative_prompt_embeds_mask: torch.Tensor | None = None,
output_type: str | None = "pil",
attention_kwargs: dict[str, Any] | None = None,
max_sequence_length: int = 512,
max_sequence_length: int = 1024,
resolution: int = 640,
cfg_normalize: bool = False,
use_en_prompt: bool = False,
@@ -736,6 +766,7 @@ the image\n<|vision_start|><|image_pad|><|vision_end|><|im_end|>\n<|im_start|>as
device=self.device,
num_images_per_prompt=num_images_per_prompt,
max_sequence_length=max_sequence_length,
prompt_name="negative_prompt",
)
# 4. Prepare latent variables
@@ -29,12 +29,16 @@ from vllm_omni.diffusion.models.wan2_2.wan2_2_transformer import WanTransformer3
from vllm_omni.diffusion.postprocess import interpolate_video_tensor
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.utils.prompt_utils import (
validate_prompt_sequence_lengths,
)
from vllm_omni.inputs.data import OmniTextPrompt
from vllm_omni.platforms import current_omni_platform
logger = logging.getLogger(__name__)
DEBUG_PERF = False
WAN_SAMPLE_SOLVER_CHOICES = {"unipc", "euler"}
WAN22_MAX_SEQUENCE_LENGTH = 512
def build_wan_scheduler(sample_solver: str, flow_shift: float) -> Any:
@@ -289,6 +293,7 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
pass
self.boundary_ratio = od_config.boundary_ratio
self.tokenizer_max_length = WAN22_MAX_SEQUENCE_LENGTH
# Determine which transformers to load based on boundary_ratio
# boundary_ratio=1.0: only load transformer_2 (low-noise stage only)
@@ -464,6 +469,7 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
negative_prompt_embeds=negative_prompt_embeds,
guidance_scale_2=guidance_high if boundary_ratio is not None else None,
boundary_ratio=boundary_ratio,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
)
if num_frames % self.vae_scale_factor_temporal != 1:
@@ -497,7 +503,7 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
negative_prompt=negative_prompt,
do_classifier_free_guidance=guidance_low > 1.0 or guidance_high > 1.0,
num_videos_per_prompt=req.sampling_params.num_outputs_per_prompt or 1,
max_sequence_length=req.sampling_params.max_sequence_length or 512,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
device=device,
dtype=dtype,
)
@@ -801,6 +807,20 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
prompt = [prompt] if isinstance(prompt, str) else prompt
prompt_clean = [self._prompt_clean(p) for p in prompt]
batch_size = len(prompt_clean)
text_inputs_untruncated = self.tokenizer(
prompt_clean,
padding=True,
truncation=False,
add_special_tokens=True,
return_attention_mask=True,
return_tensors="pt",
)
validate_prompt_sequence_lengths(
text_inputs_untruncated.attention_mask,
max_sequence_length=max_sequence_length,
supported_max_sequence_length=self.tokenizer_max_length,
error_context="for Wan2.2 text encoding",
)
text_inputs = self.tokenizer(
prompt_clean,
@@ -829,8 +849,24 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
if do_classifier_free_guidance:
negative_prompt = negative_prompt or ""
negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt
negative_prompt_clean = [self._prompt_clean(p) for p in negative_prompt]
neg_text_inputs_untruncated = self.tokenizer(
negative_prompt_clean,
padding=True,
truncation=False,
add_special_tokens=True,
return_attention_mask=True,
return_tensors="pt",
)
validate_prompt_sequence_lengths(
neg_text_inputs_untruncated.attention_mask,
max_sequence_length=max_sequence_length,
supported_max_sequence_length=self.tokenizer_max_length,
prompt_name="negative_prompt",
error_context="for Wan2.2 text encoding",
)
neg_text_inputs = self.tokenizer(
[self._prompt_clean(p) for p in negative_prompt],
negative_prompt_clean,
padding="max_length",
max_length=max_sequence_length,
truncation=True,
@@ -902,6 +938,7 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
negative_prompt_embeds=None,
guidance_scale_2=None,
boundary_ratio=None,
max_sequence_length=None,
):
if height % 16 != 0 or width % 16 != 0:
raise ValueError(f"`height` and `width` have to be divisible by 16 but are {height} and {width}.")
@@ -928,5 +965,10 @@ class Wan22Pipeline(nn.Module, CFGParallelMixin, ProgressBarMixin, DiffusionPipe
):
raise ValueError(f"`negative_prompt` has to be of type `str` or `list` but is {type(negative_prompt)}")
if max_sequence_length is not None and max_sequence_length > self.tokenizer_max_length:
raise ValueError(
f"`max_sequence_length` cannot be greater than {self.tokenizer_max_length} but is {max_sequence_length}"
)
if boundary_ratio is None and guidance_scale_2 is not None:
raise ValueError("`guidance_scale_2` is only supported when `boundary_ratio` is set.")
@@ -25,6 +25,7 @@ from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineL
from vllm_omni.diffusion.models.interface import SupportImageInput
from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin, _is_rank_zero
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
WAN22_MAX_SEQUENCE_LENGTH,
build_wan_scheduler,
create_transformer_from_config,
load_transformer_config,
@@ -35,6 +36,9 @@ from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
from vllm_omni.diffusion.postprocess import interpolate_video_tensor
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.utils.prompt_utils import (
validate_prompt_sequence_lengths,
)
from vllm_omni.inputs.data import OmniTextPrompt
from vllm_omni.platforms import current_omni_platform
@@ -213,6 +217,7 @@ class Wan22I2VPipeline(
# Text encoder
self.tokenizer = AutoTokenizer.from_pretrained(model, subfolder="tokenizer", local_files_only=local_files_only)
self.tokenizer_max_length = WAN22_MAX_SEQUENCE_LENGTH
self.text_encoder = UMT5EncoderModel.from_pretrained(
model, subfolder="text_encoder", torch_dtype=dtype, local_files_only=local_files_only
).to(self.device)
@@ -392,6 +397,7 @@ class Wan22I2VPipeline(
image_embeds=image_embeds,
guidance_scale_2=guidance_high if boundary_ratio is not None else None,
boundary_ratio=boundary_ratio,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
)
# Adjust num_frames to be compatible with VAE temporal scaling
@@ -420,7 +426,7 @@ class Wan22I2VPipeline(
negative_prompt=negative_prompt,
do_classifier_free_guidance=guidance_low > 1.0 or guidance_high > 1.0,
num_videos_per_prompt=req.sampling_params.num_outputs_per_prompt or 1,
max_sequence_length=req.sampling_params.max_sequence_length or 512,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
device=device,
dtype=dtype,
)
@@ -671,6 +677,20 @@ class Wan22I2VPipeline(
prompt = [prompt] if isinstance(prompt, str) else prompt
prompt_clean = [self._prompt_clean(p) for p in prompt]
batch_size = len(prompt_clean)
text_inputs_untruncated = self.tokenizer(
prompt_clean,
padding=True,
truncation=False,
add_special_tokens=True,
return_attention_mask=True,
return_tensors="pt",
)
validate_prompt_sequence_lengths(
text_inputs_untruncated.attention_mask,
max_sequence_length=max_sequence_length,
supported_max_sequence_length=self.tokenizer_max_length,
error_context="for Wan2.2 text encoding",
)
text_inputs = self.tokenizer(
prompt_clean,
@@ -699,8 +719,24 @@ class Wan22I2VPipeline(
if do_classifier_free_guidance:
negative_prompt = negative_prompt or ""
negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt
negative_prompt_clean = [self._prompt_clean(p) for p in negative_prompt]
neg_text_inputs_untruncated = self.tokenizer(
negative_prompt_clean,
padding=True,
truncation=False,
add_special_tokens=True,
return_attention_mask=True,
return_tensors="pt",
)
validate_prompt_sequence_lengths(
neg_text_inputs_untruncated.attention_mask,
max_sequence_length=max_sequence_length,
supported_max_sequence_length=self.tokenizer_max_length,
prompt_name="negative_prompt",
error_context="for Wan2.2 text encoding",
)
neg_text_inputs = self.tokenizer(
[self._prompt_clean(p) for p in negative_prompt],
negative_prompt_clean,
padding="max_length",
max_length=max_sequence_length,
truncation=True,
@@ -842,6 +878,7 @@ class Wan22I2VPipeline(
image_embeds=None,
guidance_scale_2=None,
boundary_ratio=None,
max_sequence_length=None,
):
if image is None and image_embeds is None:
raise ValueError("Provide either `image` or `image_embeds`. Cannot leave both undefined.")
@@ -863,6 +900,11 @@ class Wan22I2VPipeline(
if prompt is None and prompt_embeds is None:
raise ValueError("Provide either `prompt` or `prompt_embeds`.")
if max_sequence_length is not None and max_sequence_length > self.tokenizer_max_length:
raise ValueError(
f"`max_sequence_length` cannot be greater than {self.tokenizer_max_length} but is {max_sequence_length}"
)
if boundary_ratio is None and guidance_scale_2 is not None:
raise ValueError("`guidance_scale_2` is only supported when `boundary_ratio` is set.")
@@ -37,6 +37,7 @@ from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineL
from vllm_omni.diffusion.models.interface import SupportImageInput
from vllm_omni.diffusion.models.progress_bar import ProgressBarMixin
from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
WAN22_MAX_SEQUENCE_LENGTH,
build_wan_scheduler,
create_transformer_from_config,
load_transformer_config,
@@ -46,6 +47,9 @@ from vllm_omni.diffusion.models.wan2_2.pipeline_wan2_2 import (
)
from vllm_omni.diffusion.postprocess import interpolate_video_tensor
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.utils.prompt_utils import (
validate_prompt_sequence_lengths,
)
from vllm_omni.inputs.data import OmniTextPrompt
from vllm_omni.platforms import current_omni_platform
@@ -185,6 +189,7 @@ class Wan22TI2VPipeline(nn.Module, SupportImageInput, CFGParallelMixin, Progress
# Text encoder
self.tokenizer = AutoTokenizer.from_pretrained(model, subfolder="tokenizer", local_files_only=local_files_only)
self.tokenizer_max_length = WAN22_MAX_SEQUENCE_LENGTH
self.text_encoder = UMT5EncoderModel.from_pretrained(
model, subfolder="text_encoder", torch_dtype=dtype, local_files_only=local_files_only
).to(self.device)
@@ -301,6 +306,7 @@ class Wan22TI2VPipeline(nn.Module, SupportImageInput, CFGParallelMixin, Progress
width=width,
prompt_embeds=prompt_embeds,
negative_prompt_embeds=negative_prompt_embeds,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
)
# Adjust num_frames to be compatible with VAE temporal scaling
@@ -324,7 +330,7 @@ class Wan22TI2VPipeline(nn.Module, SupportImageInput, CFGParallelMixin, Progress
negative_prompt=negative_prompt,
do_classifier_free_guidance=guidance_scale > 1.0,
num_videos_per_prompt=req.sampling_params.num_outputs_per_prompt or 1,
max_sequence_length=req.sampling_params.max_sequence_length or 512,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
device=device,
dtype=dtype,
)
@@ -518,6 +524,20 @@ class Wan22TI2VPipeline(nn.Module, SupportImageInput, CFGParallelMixin, Progress
prompt = [prompt] if isinstance(prompt, str) else prompt
prompt_clean = [self._prompt_clean(p) for p in prompt]
batch_size = len(prompt_clean)
text_inputs_untruncated = self.tokenizer(
prompt_clean,
padding=True,
truncation=False,
add_special_tokens=True,
return_attention_mask=True,
return_tensors="pt",
)
validate_prompt_sequence_lengths(
text_inputs_untruncated.attention_mask,
max_sequence_length=max_sequence_length,
supported_max_sequence_length=self.tokenizer_max_length,
error_context="for Wan2.2 text encoding",
)
text_inputs = self.tokenizer(
prompt_clean,
@@ -546,8 +566,24 @@ class Wan22TI2VPipeline(nn.Module, SupportImageInput, CFGParallelMixin, Progress
if do_classifier_free_guidance:
negative_prompt = negative_prompt or ""
negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt
negative_prompt_clean = [self._prompt_clean(p) for p in negative_prompt]
neg_text_inputs_untruncated = self.tokenizer(
negative_prompt_clean,
padding=True,
truncation=False,
add_special_tokens=True,
return_attention_mask=True,
return_tensors="pt",
)
validate_prompt_sequence_lengths(
neg_text_inputs_untruncated.attention_mask,
max_sequence_length=max_sequence_length,
supported_max_sequence_length=self.tokenizer_max_length,
prompt_name="negative_prompt",
error_context="for Wan2.2 text encoding",
)
neg_text_inputs = self.tokenizer(
[self._prompt_clean(p) for p in negative_prompt],
negative_prompt_clean,
padding="max_length",
max_length=max_sequence_length,
truncation=True,
@@ -672,6 +708,7 @@ class Wan22TI2VPipeline(nn.Module, SupportImageInput, CFGParallelMixin, Progress
width,
prompt_embeds=None,
negative_prompt_embeds=None,
max_sequence_length=None,
):
if height % 16 != 0 or width % 16 != 0:
raise ValueError(f"`height` and `width` have to be divisible by 16 but are {height} and {width}.")
@@ -687,6 +724,11 @@ class Wan22TI2VPipeline(nn.Module, SupportImageInput, CFGParallelMixin, Progress
if prompt is None and prompt_embeds is None:
raise ValueError("Provide either `prompt` or `prompt_embeds`.")
if max_sequence_length is not None and max_sequence_length > self.tokenizer_max_length:
raise ValueError(
f"`max_sequence_length` cannot be greater than {self.tokenizer_max_length} but is {max_sequence_length}"
)
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
"""Load weights using AutoWeightsLoader for vLLM integration."""
loader = AutoWeightsLoader(self)
@@ -187,6 +187,7 @@ class Wan22VACEPipeline(Wan22Pipeline, SupportImageInput):
video=None,
mask=None,
reference_images=None,
max_sequence_length=None,
):
super().check_inputs(
prompt=prompt,
@@ -195,6 +196,7 @@ class Wan22VACEPipeline(Wan22Pipeline, SupportImageInput):
width=width,
prompt_embeds=prompt_embeds,
negative_prompt_embeds=negative_prompt_embeds,
max_sequence_length=max_sequence_length,
)
# VACE-specific: validate video/mask/reference_images consistency
@@ -491,6 +493,7 @@ class Wan22VACEPipeline(Wan22Pipeline, SupportImageInput):
video=source_video,
mask=source_mask,
reference_images=reference_images,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
)
device = self.device
@@ -509,7 +512,7 @@ class Wan22VACEPipeline(Wan22Pipeline, SupportImageInput):
negative_prompt=negative_prompt,
do_classifier_free_guidance=guidance_scale > 1.0,
num_videos_per_prompt=req.sampling_params.num_outputs_per_prompt or 1,
max_sequence_length=req.sampling_params.max_sequence_length or 512,
max_sequence_length=req.sampling_params.max_sequence_length or self.tokenizer_max_length,
device=device,
dtype=dtype,
)
+38
View File
@@ -0,0 +1,38 @@
import torch
def validate_prompt_sequence_lengths(
attention_mask: torch.Tensor,
*,
max_sequence_length: int,
supported_max_sequence_length: int,
prompt_name: str = "prompt",
length_offset: int = 0,
baseline_attention_mask: torch.Tensor | None = None,
error_context: str,
) -> None:
sequence_lengths = attention_mask.sum(dim=1)
if baseline_attention_mask is not None:
# Some callers need to validate only the user-controlled portion of a
# templated prompt. In those cases we subtract the fully-tokenized
# template baseline instead of only removing a fixed prefix length,
# because the template may also contribute a suffix or image markers.
baseline_lengths = baseline_attention_mask.sum(dim=1)
if baseline_lengths.shape[0] == 1 and sequence_lengths.shape[0] > 1:
baseline_lengths = baseline_lengths.expand(sequence_lengths.shape[0])
sequence_lengths = sequence_lengths - baseline_lengths
if length_offset:
sequence_lengths = sequence_lengths - length_offset
sequence_lengths = torch.clamp(sequence_lengths, min=0)
too_long = torch.nonzero(sequence_lengths > max_sequence_length, as_tuple=False)
if too_long.numel() == 0:
return
batch_idx = int(too_long[0].item())
actual_length = int(sequence_lengths[batch_idx].item())
prompt_ref = f"`{prompt_name}` at batch index {batch_idx}" if attention_mask.shape[0] > 1 else f"`{prompt_name}`"
raise ValueError(
f"{prompt_ref} is too long {error_context}: got {actual_length} tokens, but "
f"`max_sequence_length` is {max_sequence_length}. Shorten the prompt or increase "
f"`max_sequence_length` up to {supported_max_sequence_length}."
)