[XPU][HunyuanImage3] Fix HunyuanImage-3.0 text-to-image on XPU (#7674)

Signed-off-by: Joshna Medisetty <joshna.medisetty@intel.com>
This commit is contained in:
Joshna-Medisetty authored and GitHub committed 2026-09-17 19:23:27 +00:00
1 parent b81f2c5d4b
commit 77d8de70da
4 files changed
+22 -12

No files matched your search

+8 -5
View File
@@ -106,11 +106,14 @@ platforms:
xpu:
stages:
# Both stages stay resident, so they cannot share cards.
- stage_id: 0
gpu_memory_utilization: 0.95
devices: "0,1,2,3,4,5,6,7"
tensor_parallel_size: 8
max_num_batched_tokens: 32784
devices: "0,1,2,3"
tensor_parallel_size: 4
# fp8 AR weights leave under 1 GiB for KV; 22800 tokens will not fit.
max_num_batched_tokens: 8192
max_model_len: 8192
quantization: fp8
enable_expert_parallel: true
worker_cls: vllm_omni.platforms.xpu.worker.xpu_ar_worker.XPUARWorker
@@ -120,12 +123,12 @@ platforms:
repetition_penalty: 1.1
- stage_id: 1
gpu_memory_utilization: 0.9
devices: "0,1,2,3,4,5,6,7"
devices: "4,5,6,7"
quantization: fp8
parallel_config:
pipeline_parallel_size: 1
data_parallel_size: 1
tensor_parallel_size: 8
tensor_parallel_size: 4
enable_expert_parallel: true
sequence_parallel_size: 1
ulysses_degree: 1
@@ -3040,7 +3040,7 @@ class HunyuanImage3Text2ImagePipeline(DiffusionPipeline):
# 3. negative cfg prefill
# For CFG distilled models, skip negative CFG prefill (cfg_factor=1, no negative prompt)
if self.do_classifier_free_guidance and not self.model.config.cfg_distilled:
if self.do_classifier_free_guidance and not getattr(self.model.config, "cfg_distilled", False):
self._maybe_run_negative_cfg_prefill(
input_ids=input_ids,
model_kwargs=model_kwargs,
@@ -3151,7 +3151,7 @@ class HunyuanImage3Text2ImagePipeline(DiffusionPipeline):
# For CFG distilled models, skip CFG parallel (cfg_factor=1, no negative branch)
cfg_parallel_ready = (
self.do_classifier_free_guidance
and not self.model.config.cfg_distilled
and not getattr(self.model.config, "cfg_distilled", False)
and get_classifier_free_guidance_world_size() == 2
)
@@ -3207,7 +3207,7 @@ class HunyuanImage3Text2ImagePipeline(DiffusionPipeline):
self._split_model_kwargs_for_cfg_parallel(model_kwargs, batch_size, cfg_rank)
else:
# For CFG distilled models, cfg_factor is always 1 (CFG embedded in model)
if self.model.config.cfg_distilled:
if getattr(self.model.config, "cfg_distilled", False):
cfg_factor = 1
else:
cfg_factor = 1 + self.do_classifier_free_guidance
@@ -3252,7 +3252,7 @@ class HunyuanImage3Text2ImagePipeline(DiffusionPipeline):
# Sequential CFG: double the batch
latent_model_input = torch.cat([latents] * cfg_factor)
if self.model.config.use_meanflow:
if getattr(self.model.config, "use_meanflow", False):
r = self.scheduler.get_timestep_r(t)
r_expand = r.repeat(latent_model_input.shape[0])
else:
@@ -3281,7 +3281,7 @@ class HunyuanImage3Text2ImagePipeline(DiffusionPipeline):
if should_compute:
# Handle guidance for CFG distilled models
if self.model.config.cfg_distilled:
if getattr(self.model.config, "cfg_distilled", False):
model_kwargs["guidance"] = torch.tensor(
[1000.0 * self._guidance_scale],
device=self.device,
@@ -3308,7 +3308,7 @@ class HunyuanImage3Text2ImagePipeline(DiffusionPipeline):
# Perform guidance
# For CFG distilled models, guidance is already embedded in the model,
# so we skip the explicit CFG computation
if self.model.config.cfg_distilled:
if getattr(self.model.config, "cfg_distilled", False):
# CFG distilled: guidance is handled internally via guidance_emb
pass
elif cfg_parallel_ready:
@@ -338,6 +338,9 @@ class HunyuanImage3Pipeline(
supports_step_execution: ClassVar[bool] = True
supports_request_batch = False
support_image_input = True
# The warmup request's blank image routes this AR+diffusion model down its
# image-edit path, where the token block cannot match the latent patch grid.
dummy_run_num_frames: ClassVar[int] = 0
_dit_modules: ClassVar[list[str]] = ["model"]
_encoder_modules: ClassVar[list[str]] = ["vision_model"]
_vae_modules: ClassVar[list[str]] = ["vae"]
@@ -1557,7 +1557,11 @@ class HunyuanImage3ForConditionalGeneration(nn.Module, SupportsMultiModal, Suppo
self._end_ratio_id = tokenizer.convert_tokens_to_ids("<img_ratio_32>")
ratio_33 = tokenizer.convert_tokens_to_ids("<img_ratio_33>")
ratio_36 = tokenizer.convert_tokens_to_ids("<img_ratio_36>")
self._ratio_other_slices = [(ratio_33, ratio_36 + 1)]
# The base checkpoint stops at <img_ratio_32>; only Instruct has 33-36.
if ratio_33 is None or ratio_36 is None:
self._ratio_other_slices = []
else:
self._ratio_other_slices = [(ratio_33, ratio_36 + 1)]
# Build the full set of ratio token IDs for use as stop tokens.
self._all_ratio_ids = set(range(self._start_ratio_id, self._end_ratio_id + 1))
for s, e in self._ratio_other_slices: