Skip to content

[Bugfix]Hunyuan image3 ar batch sampler#3589

Closed
bjf-frz wants to merge 8 commits into
vllm-project:mainfrom
bjf-frz:hunyuan-image3-ar-batch-sampler
Closed

[Bugfix]Hunyuan image3 ar batch sampler#3589
bjf-frz wants to merge 8 commits into
vllm-project:mainfrom
bjf-frz:hunyuan-image3-ar-batch-sampler

Conversation

@bjf-frz
Copy link
Copy Markdown
Contributor

@bjf-frz bjf-frz commented May 14, 2026

PLEASE FILL IN THE PR DESCRIPTION HERE ENSURING ALL CHECKLIST ITEMS (AT THE BOTTOM) HAVE BEEN CONSIDERED.

Purpose

This PR aims to remove the assertion of batch_size in the sampler of Hunyuan-Image-3.0. It enables single ar and multiple DiTs.

Test Plan

After merging pr #3569, set the deploy yaml like:

  • 8 GPUs
  • rank0, 1, 2, 3 AR replica0 TP4
  • rank4, 5 DiT replica0 TP2
  • rank6, 7 DiT replica1 TP2
# HunyuanImage-3.0-Instruct AR + DiT deploy for one 8-GPU host.
#
# Legacy stage_args format with explicit stage types:
#   - Stage 0: llm / AR, 1 replica, TP=4 on GPUs 0,1,2,3.
#   - Stage 1: diffusion / DiT+VAE, 2 replicas, TP=2 each on
#     GPUs 4,5 / 6,7.
#
# Use this file with --stage-configs-path. The sibling
# hunyuan_image3_it2i_kv_reuse_replica_stages.yaml is the new --deploy-config
# stages-format version.
async_chunk: false

stage_args:
  - stage_id: 0
    stage_type: llm
    model_stage: AR
    runtime:
      process: true
      devices: "0,1,2,3"
      num_replicas: 1
      max_batch_size: 1
      requires_multimodal_data: true
    engine_args:
      model_stage: AR
      model_arch: HunyuanImage3ForCausalMM
      worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker
      scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
      engine_output_type: latent
      trust_remote_code: true
      gpu_memory_utilization: 0.45
      enforce_eager: true
      enable_prefix_caching: false
      max_num_seqs: 2
      max_num_batched_tokens: 16384
      tensor_parallel_size: 4
      pipeline_parallel_size: 1
      hf_overrides:
        rope_parameters:
          mrope_section: [0, 32, 32]
          rope_type: default
      omni_kv_config:
        need_send_cache: true
    is_comprehension: false
    final_output: true
    final_output_type: text
    output_connectors:
      to_stage_1: shared_memory_connector
    default_sampling_params:
      temperature: 0.0
      top_p: 1
      top_k: -1
      max_tokens: 1024
      stop_token_ids: [128025]  # <answer>
      detokenize: true
      skip_special_tokens: false

  - stage_id: 1
    stage_type: diffusion
    runtime:
      process: true
      devices: "4,5,6,7"
      num_replicas: 2
      max_batch_size: 1
      requires_multimodal_data: true
    engine_args:
      model_stage: dit
      model_arch: HunyuanImage3ForCausalMM
      trust_remote_code: true
      gpu_memory_utilization: 0.55
      enforce_eager: true
      distributed_executor_backend: mp
      quantization: fp8
      omni_kv_config:
        need_recv_cache: true
      parallel_config:
        pipeline_parallel_size: 1
        data_parallel_size: 1
        tensor_parallel_size: 2
        enable_expert_parallel: true
        sequence_parallel_size: 1
        ulysses_degree: 1
        ring_degree: 1
        cfg_parallel_size: 1
        vae_patch_parallel_size: 1
        use_hsdp: false
        hsdp_shard_size: -1
        hsdp_replicate_size: 1
    engine_input_source: [0]
    custom_process_input_func: vllm_omni.model_executor.stage_input_processors.hunyuan_image3.ar2diffusion
    final_output: true
    final_output_type: image
    input_connectors:
      from_stage_0: shared_memory_connector
    default_sampling_params:
      num_inference_steps: 50
      guidance_scale: 0
      seed: 42

runtime:
  enabled: true
  connectors:
    shared_memory_connector:
      name: SharedMemoryConnector
      extra:
        shm_threshold_bytes: 65536
  edges:
    - from: 0
      to: 1
      window_size: -1
      max_inflight: 1

I command concurrent requests 3 times in a row.

Test Result

The output shows:

Request Stage 0 enter AR finished / ar2diffusion DiT stated DiT finished
req1 b13d... 02:23:50 02:24:44 02:24:44 rep-0 about 02:25:16
req2 a9c... 02:23:51 02:24:45 02:24:45 rep-1 about 02:25:17
req3 b858... 02:23:54 02:25:36 02:25:36 rep-0 about 02:26:08

Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan. Please provide the test scripts & test commands. Please state the reasons if your codes don't require additional test scripts. For test file guidelines, please check the test style doc
  • The test results. Please paste the results comparison before and after, or the e2e results.
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model. Please run mkdocs serve to sync the documentation editions to ./docs.
  • (Optional) Release notes update. If your change is user-facing, please update the release notes draft.

BEFORE SUBMITTING, PLEASE READ https://github.com/vllm-project/vllm-omni/blob/main/CONTRIBUTING.md (anything written below this line will be removed by GitHub Actions)

chickeyton and others added 8 commits May 13, 2026 11:46
Signed-off-by: chickeyton <ngton2014@gmail.com>
LeastQueueLengthBalancer relies on heartbeats as the only periodic
source of live load, because StageEngineCoreProc/StageDiffusionProc
refresh ``queue_length`` just-in-time via the ``_on_heartbeat`` hook
before each heartbeat send. The coordinator's heartbeat handler was
only updating ``last_heartbeat`` though, so it kept publishing the
initial queue_length (usually 0) and the least-queue policy could
pick busy replicas as if they were idle.

Copy ``event.queue_length`` into ``info.queue_length`` on heartbeat
events and request a broadcast when it changes so subscribers see
fresh load promptly. Coalescing in the periodic loop keeps the wire
traffic bounded.

Also corrects the now-outdated docstring on ``_send_event`` that
claimed heartbeats sent ``queue_length=null``.

Signed-off-by: chickeyton <ngton2014@gmail.com>
- Drop unused ``vllm_config`` local in ``StageEngineCoreProc.run_stage_core``
  (F841); the comment about the removed hardcoded data_parallel_size is
  retained.
- Wrap the long ``[Headless] Launching ... OmniMasterServer`` log line
  in serve.py to keep it under the 120-char limit (E501).
- Reflow multi-line ``raise`` / ``logger`` calls that fit on one line
  per ``ruff format`` rules in stage_diffusion_proc, async_omni_engine,
  omni_coord_client_for_hub, omni_core_engine_proc_manager, orchestrator,
  stage_engine_core_proc and serve.

Signed-off-by: chickeyton <ngton2014@gmail.com>
Signed-off-by: chickeyton <ngton2014@gmail.com>
Signed-off-by: bjf-frz <frz123db@gmail.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e687ca1ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

def compute_replica_layout(
stage_configs: Sequence[Any],
*,
allow_zero: bool = False,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor zero-replica stages in head mode

This adds an allow_zero mode for the documented head-distributed case where non-self stages are filled by dynamic registrations, but the new parameter defaults to False and the head initialization still calls compute_replica_layout(self.stage_configs) without overriding it. As a result, a stage configured with runtime.num_replicas: 0 is still clamped to one remote replica, so the head pre-allocates and waits for a registration instead of starting with an empty pool and attaching later.

Useful? React with 👍 / 👎.

Task,
)
from .messages import InstanceEvent, InstanceInfo, InstanceList, StageStatus
from .messages import ReplicaEvent, ReplicaInfo, ReplicaList, StageStatus
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the coordinator Instance aliases

Removing the exported InstanceEvent / InstanceInfo / InstanceList names breaks existing coordinator imports in this repo (for example the tests/distributed/omni_coordinator tests still import InstanceInfo and InstanceList) and any downstream code using the public package export. Unless all call sites are migrated in the same change, keep aliases to the new Replica* classes so those imports continue to work.

Useful? React with 👍 / 👎.

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.

2 participants