Compare commits
78
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e37c5aed40 | ||
|
|
704b8e29ae | ||
|
|
bf65a41ceb | ||
|
|
74b596cd05 | ||
|
|
67e177860a | ||
|
|
321e8bb47f | ||
|
|
e778d21f91 | ||
|
|
2f8363c5fc | ||
|
|
3e705fcdb1 | ||
|
|
6a70bda0f6 | ||
|
|
1c71a2c03a | ||
|
|
c32a26a845 | ||
|
|
8484d85d1f | ||
|
|
744c202d86 | ||
|
|
f3743ebdb6 | ||
|
|
3345fb7498 | ||
|
|
32ead8e5c8 | ||
|
|
c8e69c08fb | ||
|
|
e54e185593 | ||
|
|
3dc35694b3 | ||
|
|
8ef77be66e | ||
|
|
b21df3bcb6 | ||
|
|
de253412b0 | ||
|
|
d5a3380103 | ||
|
|
9482bf1e8c | ||
|
|
712b90de9c | ||
|
|
ed6bc5ae0c | ||
|
|
e61cd4290f | ||
|
|
a0d915254b | ||
|
|
bad88bca25 | ||
|
|
7be49aa0ce | ||
|
|
ee9ab9ea88 | ||
|
|
182b77da4c | ||
|
|
65699fe94c | ||
|
|
25fb257667 | ||
|
|
0677064b87 | ||
|
|
5f95115e70 | ||
|
|
1f63e97a40 | ||
|
|
c2cd13c009 | ||
|
|
2bcd1e449a | ||
|
|
5e34e1c97d | ||
|
|
0ad67f1d8f | ||
|
|
099f9b553d | ||
|
|
e4a8b2e858 | ||
|
|
cc444b8875 | ||
|
|
aad3090877 | ||
|
|
ae430dddc0 | ||
|
|
79562e97ad | ||
|
|
3aab12851f | ||
|
|
539c533762 | ||
|
|
9146284c16 | ||
|
|
a0e626caaf | ||
|
|
023716ecc4 | ||
|
|
5351cd6645 | ||
|
|
091b256674 | ||
|
|
6dd0d1f931 | ||
|
|
67d21b5fff | ||
|
|
0b0d2d7009 | ||
|
|
1f47fa2f29 | ||
|
|
5ca23106fd | ||
|
|
bbd539d2b6 | ||
|
|
c1e84ce946 | ||
|
|
f781f7bb08 | ||
|
|
3f20252151 | ||
|
|
855254d4af | ||
|
|
65f5a1d5b3 | ||
|
|
b9cf74352a | ||
|
|
c9c478dd07 | ||
|
|
c9bee166fb | ||
|
|
4934741df9 | ||
|
|
b65f97bc48 | ||
|
|
148cb943ce | ||
|
|
3ab726c126 | ||
|
|
1c1b831f58 | ||
|
|
dd765fbf64 | ||
|
|
b171731c81 | ||
|
|
09d704907f | ||
|
|
c150600896 |
No files matched your search
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""Prebuild the BF16 AITER MHA modules used by Cosmos3 on ROCm."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
|
||||
from vllm_omni.diffusion.attention.backends.utils import fa
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
ATTENTION_VARIANTS = (
|
||||
(True, "mha_fwd_bf16_nbias_mask_nlse_ndropout_nqscale"),
|
||||
(False, "mha_fwd_bf16_nbias_nmask_nlse_ndropout_nqscale"),
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not current_omni_platform.is_rocm():
|
||||
raise RuntimeError("The Cosmos3 AITER prewarm must run on ROCm")
|
||||
if fa.flash_attn_func is None:
|
||||
raise RuntimeError("ROCm FlashAttention/AITER is unavailable")
|
||||
|
||||
cache_dir_value = os.environ.get("AITER_JIT_DIR")
|
||||
if not cache_dir_value:
|
||||
raise RuntimeError("AITER_JIT_DIR must identify the job-local cache")
|
||||
cache_dir = Path(cache_dir_value)
|
||||
|
||||
device = torch.device(current_omni_platform.device_type)
|
||||
dtype = torch.bfloat16
|
||||
batch_size = 1
|
||||
sequence_length = 64
|
||||
num_heads = 8
|
||||
head_dim = 64
|
||||
|
||||
torch.manual_seed(42)
|
||||
query = torch.randn(
|
||||
batch_size,
|
||||
sequence_length,
|
||||
num_heads,
|
||||
head_dim,
|
||||
device=device,
|
||||
dtype=dtype,
|
||||
)
|
||||
key = torch.randn_like(query)
|
||||
value = torch.randn_like(query)
|
||||
|
||||
for causal, expected_module in ATTENTION_VARIANTS:
|
||||
output = fa.flash_attn_func(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
dropout_p=0.0,
|
||||
softmax_scale=head_dim**-0.5,
|
||||
causal=causal,
|
||||
)
|
||||
if isinstance(output, tuple):
|
||||
output = output[0]
|
||||
current_omni_platform.synchronize()
|
||||
|
||||
if output.shape != query.shape:
|
||||
raise RuntimeError(f"Unexpected AITER output shape: {output.shape}")
|
||||
if not torch.isfinite(output).all():
|
||||
raise RuntimeError("AITER prewarm produced non-finite output")
|
||||
|
||||
module_path = cache_dir / f"{expected_module}.so"
|
||||
if not module_path.is_file():
|
||||
raise RuntimeError(f"Expected AITER module was not built: {module_path}")
|
||||
print(f"Prebuilt Cosmos3 AITER module: {module_path}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -37,24 +37,22 @@ steps:
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: Blocking
|
||||
timeout_in_minutes: 45
|
||||
# Buildkite's status-based rule cannot prove every exit 134 is a GPU hang.
|
||||
# It retries 134 once; the Kubernetes job retry starts a fresh pod.
|
||||
# Assertions (1), timeouts (124), and OOMs (137) remain non-retryable.
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 134
|
||||
limit: 1
|
||||
commands:
|
||||
- export VLLM_ROCM_USE_AITER=0
|
||||
# ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM
|
||||
# that is not supported on CPU
|
||||
# Keep multi-GPU tests out of this one-GPU pool even when their module
|
||||
# also carries the cpu marker for unit tests.
|
||||
# Keep π0.5 CPU units out of these shards: CUDA READY/MERGE already run
|
||||
# the same suite mandatorily, and AMD covers it non-blocking in nightly.
|
||||
- "pytest -sv tests/diffusion -m 'core_model and cpu and not (cards_2 or cards_3 or cards_4 or cards_5 or cards_6 or cards_7 or cards_8)' --ignore=tests/diffusion/cache/test_teacache_extractors.py --ignore=tests/diffusion/models/pi05/test_pi05_units.py --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB"
|
||||
# This module takes about 22 minutes on MI300 and was consistently assigned to shard 4,
|
||||
# pushing that shard to the 45-minute timeout. Keep its module-scoped fixture in one lane.
|
||||
- label: "Simple · Pi0.5 CPU Test"
|
||||
agent_pool: mi300_1
|
||||
depends_on: amd-build
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: Blocking
|
||||
timeout_in_minutes: 45
|
||||
commands:
|
||||
- export VLLM_ROCM_USE_AITER=0
|
||||
- "pytest -sv tests/diffusion/models/pi05/test_pi05_units.py -m 'core_model and cpu'"
|
||||
|
||||
- label: "Diffusion · AuK VAE GPU Test"
|
||||
agent_pool: mi300_1
|
||||
@@ -91,6 +89,25 @@ steps:
|
||||
|
||||
- group: ":card_index_dividers: Diffusion Test"
|
||||
steps:
|
||||
# AR paged attention is real GPU work, including cold AITER compilation.
|
||||
# Keep its complete numerical matrix out of the CPU-marked Simple shards.
|
||||
- label: "ROCm · AR Diffusion Paged Attention GPU Test"
|
||||
agent_pool: mi300_1
|
||||
depends_on: amd-build
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: Blocking
|
||||
timeout_in_minutes: 20
|
||||
commands:
|
||||
- export VLLM_ROCM_USE_AITER=1
|
||||
- export VLLM_OMNI_AR_FA_REQUIRED=1
|
||||
- export AITER_JIT_DIR="/tmp/vllm-omni-ar-aiter-$$BUILDKITE_JOB_ID"
|
||||
- export TORCH_EXTENSIONS_DIR="/tmp/vllm-omni-ar-torch-extensions-$$BUILDKITE_JOB_ID"
|
||||
- mkdir -p "$$AITER_JIT_DIR" "$$TORCH_EXTENSIONS_DIR"
|
||||
- >-
|
||||
timeout --signal=TERM --kill-after=2m 15m
|
||||
pytest -sv tests/diffusion/ar_diffusion/test_paged_attention.py
|
||||
-m 'core_model and rocm and MI325 and cards_1' --run-level core_model
|
||||
|
||||
- label: "Diffusion · LoRA Test"
|
||||
agent_pool: mi300_1
|
||||
depends_on: amd-build
|
||||
@@ -109,6 +126,7 @@ steps:
|
||||
commands:
|
||||
- export VLLM_LOGGING_LEVEL=DEBUG
|
||||
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
||||
- export DIFFUSION_ATTENTION_BACKEND=TORCH_SDPA
|
||||
- pytest -s -v tests/diffusion/offloader/test_diffusion_cpu_offload.py
|
||||
- >-
|
||||
pytest -s -v tests/diffusion/offloader/test_diffusion_layerwise_offload.py
|
||||
|
||||
@@ -82,3 +82,89 @@ steps:
|
||||
limit: 1
|
||||
commands:
|
||||
- pytest -s -v tests/e2e/online_serving/test_cosyvoice3_tts_expansion.py -m "slow" --run-level "core_model"
|
||||
|
||||
# π0.5 CPU units are mandatory on CUDA READY/MERGE. On AMD they exercise the
|
||||
# ROCm container/import path with CPU tensors only, so keep them off the
|
||||
# blocking READY/MERGE critical path and retain non-blocking nightly coverage.
|
||||
- group: ":robot_face: Pi0.5 CPU Nightly"
|
||||
steps:
|
||||
- label: "Simple · Pi0.5 CPU Test"
|
||||
agent_pool: mi300_1
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: NonBlocking
|
||||
timeout_in_minutes: 60
|
||||
commands:
|
||||
- export VLLM_ROCM_USE_AITER=0
|
||||
- "pytest -sv tests/diffusion/models/pi05/test_pi05_units.py -m 'core_model and cpu'"
|
||||
|
||||
- group: ":full_moon: LingBot-Video MI300 Nightly"
|
||||
steps:
|
||||
- label: "LingBot-Video Dense CFG-off Smoke"
|
||||
agent_pool: mi300_1
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: NonBlocking
|
||||
timeout_in_minutes: 90
|
||||
artifact_paths:
|
||||
- artifacts/rocm-lingbot-nightly/**/*
|
||||
commands:
|
||||
- pytest --collect-only -q tests/e2e/online_serving/test_lingbot_video_expansion.py::test_cfg_off -m "full_model and diffusion and rocm and MI325 and cards_1" --run-level full_model
|
||||
- pytest -s -v tests/e2e/online_serving/test_lingbot_video_expansion.py::test_cfg_off -m "full_model and diffusion and rocm and MI325 and cards_1" --run-level full_model --junitxml=/tmp/lingbot-pytest.xml
|
||||
- |
|
||||
artifact_dir="$${BUILDKITE_BUILD_CHECKOUT_PATH:?}/artifacts/rocm-lingbot-nightly"
|
||||
mkdir -p "$$artifact_dir"
|
||||
cp -v /tmp/lingbot-pytest.xml "$$artifact_dir/"
|
||||
|
||||
- group: ":full_moon: Cosmos3 MI300 Nightly"
|
||||
steps:
|
||||
- label: "Cosmos3 T2I Function"
|
||||
agent_pool: mi300_1
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: NonBlocking
|
||||
timeout_in_minutes: 90
|
||||
artifact_paths:
|
||||
- artifacts/rocm-cosmos3-nightly/**/*
|
||||
commands:
|
||||
# Cosmos3 uses both masked and unmasked BF16 AITER MHA modules. Build
|
||||
# them before server startup, then reuse the same job-local cache
|
||||
# during the smoke test.
|
||||
- export VLLM_ROCM_USE_AITER=1
|
||||
- export AITER_JIT_DIR="/tmp/vllm-omni-cosmos3-aiter-$$BUILDKITE_JOB_ID"
|
||||
- export TORCH_EXTENSIONS_DIR="/tmp/vllm-omni-cosmos3-torch-extensions-$$BUILDKITE_JOB_ID"
|
||||
- mkdir -p "$$AITER_JIT_DIR" "$$TORCH_EXTENSIONS_DIR"
|
||||
- pytest --collect-only -q tests/e2e/online_serving/test_cosmos3.py::test_text_to_image_001 -m "core_model and rocm and MI325 and cards_1" --run-level full_model
|
||||
- >-
|
||||
timeout --signal=TERM --kill-after=2m 55m
|
||||
python .buildkite/amd/scripts/prewarm_cosmos3_aiter.py
|
||||
- >-
|
||||
timeout --signal=TERM --kill-after=2m 25m
|
||||
pytest -s -v
|
||||
tests/e2e/online_serving/test_cosmos3.py::test_text_to_image_001
|
||||
-m "core_model and rocm and MI325 and cards_1"
|
||||
--run-level full_model
|
||||
--junitxml=/tmp/cosmos3-pytest.xml
|
||||
- |
|
||||
artifact_dir="$${BUILDKITE_BUILD_CHECKOUT_PATH:?}/artifacts/rocm-cosmos3-nightly"
|
||||
mkdir -p "$$artifact_dir"
|
||||
cp -v /tmp/cosmos3-pytest.xml "$$artifact_dir/"
|
||||
|
||||
- group: ":full_moon: HunyuanImage3 MI300 Nightly"
|
||||
steps:
|
||||
- label: "HunyuanImage3 Offline Pixel Accuracy"
|
||||
agent_pool: mi300_4
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: NonBlocking
|
||||
timeout_in_minutes: 180
|
||||
env:
|
||||
HUNYUAN_IMAGE3_MODEL: "tencent/HunyuanImage-3.0-Instruct"
|
||||
HUNYUAN_IMAGE3_DEVICES: "0,1,2,3"
|
||||
DIFFUSION_ATTENTION_BACKEND: TORCH_SDPA
|
||||
artifact_paths:
|
||||
- artifacts/rocm-hunyuanimage3/**/*
|
||||
commands:
|
||||
- pytest --collect-only -q tests/e2e/accuracy/test_hunyuan_image3_pixel_accuracy.py::test_hunyuan_image3_pixel_accuracy_offline -m "full_model and rocm and MI325 and cards_4" --run-level full_model
|
||||
- pytest -s -v tests/e2e/accuracy/test_hunyuan_image3_pixel_accuracy.py::test_hunyuan_image3_pixel_accuracy_offline -m "full_model and rocm and MI325 and cards_4" --run-level full_model --junitxml=/tmp/hunyuanimage3-pytest.xml
|
||||
- |
|
||||
artifact_dir="$${BUILDKITE_BUILD_CHECKOUT_PATH:?}/artifacts/rocm-hunyuanimage3"
|
||||
mkdir -p "$$artifact_dir"
|
||||
cp -v tests/e2e/accuracy/artifacts/HunyuanImage-3_0-Instruct/*.png "$$artifact_dir/"
|
||||
cp -v /tmp/hunyuanimage3-pytest.xml "$$artifact_dir/"
|
||||
@@ -37,24 +37,22 @@ steps:
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: Blocking
|
||||
timeout_in_minutes: 45
|
||||
# Buildkite's status-based rule cannot prove every exit 134 is a GPU hang.
|
||||
# It retries 134 once; the Kubernetes job retry starts a fresh pod.
|
||||
# Assertions (1), timeouts (124), and OOMs (137) remain non-retryable.
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 134
|
||||
limit: 1
|
||||
commands:
|
||||
- export VLLM_ROCM_USE_AITER=0
|
||||
# ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM
|
||||
# that is not supported on CPU
|
||||
# Keep multi-GPU tests out of this one-GPU pool even when their module
|
||||
# also carries the cpu marker for unit tests.
|
||||
# Keep π0.5 CPU units out of these shards: CUDA READY/MERGE already run
|
||||
# the same suite mandatorily, and AMD covers it non-blocking in nightly.
|
||||
- "pytest -sv tests/diffusion -m 'core_model and cpu and not (cards_2 or cards_3 or cards_4 or cards_5 or cards_6 or cards_7 or cards_8)' --ignore=tests/diffusion/cache/test_teacache_extractors.py --ignore=tests/diffusion/models/pi05/test_pi05_units.py --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB"
|
||||
# This module takes about 22 minutes on MI300 and was consistently assigned to shard 4,
|
||||
# pushing that shard to the 45-minute timeout. Keep its module-scoped fixture in one lane.
|
||||
- label: "Simple · Pi0.5 CPU Test"
|
||||
agent_pool: mi300_1
|
||||
depends_on: amd-build
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: Blocking
|
||||
timeout_in_minutes: 45
|
||||
commands:
|
||||
- export VLLM_ROCM_USE_AITER=0
|
||||
- "pytest -sv tests/diffusion/models/pi05/test_pi05_units.py -m 'core_model and cpu'"
|
||||
|
||||
- label: "Diffusion · AuK VAE GPU Test"
|
||||
agent_pool: mi300_1
|
||||
@@ -128,6 +126,34 @@ steps:
|
||||
tests/diffusion/attention/
|
||||
-m 'core_model and rocm and MI325 and cards_1'
|
||||
--run-level "core_model"
|
||||
# MammothModa2 exercises the same two AITER MHA modules built by the
|
||||
# preceding suite. Keep its default-backend run in this job so a cold
|
||||
# worker compiles each module once instead of timing out the generic
|
||||
# model job.
|
||||
- >-
|
||||
timeout --signal=TERM --kill-after=2m 10m
|
||||
pytest -s -v -n 1
|
||||
tests/diffusion/models/mammoth_moda2/test_dit_attention_cuda.py
|
||||
--run-level "core_model"
|
||||
|
||||
# AR paged attention is real GPU work, including cold AITER compilation.
|
||||
# Keep its complete numerical matrix out of the CPU-marked Simple shards.
|
||||
- label: "ROCm · AR Diffusion Paged Attention GPU Test"
|
||||
agent_pool: mi300_1
|
||||
depends_on: amd-build
|
||||
mirror_hardwares: [amdproduction]
|
||||
grade: Blocking
|
||||
timeout_in_minutes: 20
|
||||
commands:
|
||||
- export VLLM_ROCM_USE_AITER=1
|
||||
- export VLLM_OMNI_AR_FA_REQUIRED=1
|
||||
- export AITER_JIT_DIR="/tmp/vllm-omni-ar-aiter-$$BUILDKITE_JOB_ID"
|
||||
- export TORCH_EXTENSIONS_DIR="/tmp/vllm-omni-ar-torch-extensions-$$BUILDKITE_JOB_ID"
|
||||
- mkdir -p "$$AITER_JIT_DIR" "$$TORCH_EXTENSIONS_DIR"
|
||||
- >-
|
||||
timeout --signal=TERM --kill-after=2m 15m
|
||||
pytest -sv tests/diffusion/ar_diffusion/test_paged_attention.py
|
||||
-m 'core_model and rocm and MI325 and cards_1' --run-level core_model
|
||||
|
||||
- label: "Diffusion · Batch Test"
|
||||
agent_pool: mi300_1
|
||||
@@ -197,9 +223,17 @@ steps:
|
||||
- >-
|
||||
timeout 15m pytest -sv tests/diffusion/models/
|
||||
--ignore=tests/diffusion/models/auk/test_auk_vae_fastpath.py
|
||||
--ignore=tests/diffusion/models/mammoth_moda2/test_dit_attention_cuda.py
|
||||
-m 'core_model and cuda and not
|
||||
(cards_2 or cards_3 or cards_4 or cards_5 or cards_6 or cards_7 or cards_8)'
|
||||
--run-level "core_model"
|
||||
# Keep the model-specific numerical coverage blocking without paying
|
||||
# the default ROCm backend's cold AITER compilation cost in this lane.
|
||||
- >-
|
||||
timeout 10m env DIFFUSION_ATTENTION_BACKEND=TORCH_SDPA
|
||||
pytest -sv
|
||||
tests/diffusion/models/mammoth_moda2/test_dit_attention_cuda.py
|
||||
--run-level "core_model"
|
||||
|
||||
- label: "Diffusion · Wan22 Test"
|
||||
agent_pool: mi300_1
|
||||
@@ -226,7 +260,7 @@ steps:
|
||||
# - export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
||||
# - timeout 20m pytest -s -v tests/e2e/offline_inference/test_stable_audio_expansion.py -m "advanced_model and diffusion and L4" --run-level advanced_model
|
||||
|
||||
- label: "ROCm · Engine & Model Executor GPU Coverage (R2-01)"
|
||||
- label: "ROCm · Model Executor GPU Coverage (R2-01)"
|
||||
agent_pool: mi300_1
|
||||
depends_on: amd-build
|
||||
mirror_hardwares: [amdproduction]
|
||||
@@ -239,18 +273,17 @@ steps:
|
||||
# artifacts relative to its checkout directory.
|
||||
- export R2_01_ARTIFACT_DIR="$$BUILDKITE_BUILD_CHECKOUT_PATH/artifacts/rocm-r2-01"
|
||||
- mkdir -p "$$R2_01_ARTIFACT_DIR"
|
||||
# The dedicated Engine Test below owns the only selected tests/engine case;
|
||||
# do not duplicate its model startup in this time-bounded coverage lane.
|
||||
# The CUDA ready job also covers tests/worker/test_batched_omni_output.py.
|
||||
# Keep the initial ROCm burn-in scoped to the requested directories.
|
||||
# Keep the initial ROCm burn-in scoped to tests/model_executor/.
|
||||
# Verbose pytest output and JUnit retain node IDs without a second
|
||||
# collection-only pass over the same GPU-backed test tree.
|
||||
# The CUDA/L4-only CosyVoice3 ragged-flow parity test cold-JITs multiple
|
||||
# AITER kernels on ROCm and is covered by the CUDA ready lane.
|
||||
- >-
|
||||
pytest -s -v -ra
|
||||
tests/engine/ tests/model_executor/
|
||||
-m 'core_model and cuda and not
|
||||
tests/model_executor/
|
||||
-m 'core_model and cuda and not nvidia_only and not
|
||||
(cards_2 or cards_3 or cards_4 or cards_5 or cards_6 or cards_7 or cards_8)'
|
||||
-k 'not test_code2wav_streaming_batch_matches_ragged_flow_numerics'
|
||||
--run-level "core_model"
|
||||
--durations=0
|
||||
--junitxml="$$R2_01_ARTIFACT_DIR/pytest.xml"
|
||||
|
||||
@@ -115,6 +115,10 @@ x-qwen-image: &qwen_image
|
||||
- vllm_omni/diffusion/models/dmd2/
|
||||
- vllm_omni/diffusion/distributed/autoencoders/autoencoder_kl_qwenimage.py
|
||||
|
||||
x-joy-image: &joy_image
|
||||
- vllm_omni/diffusion/models/joy_image/
|
||||
- vllm_omni/diffusion/distributed/autoencoders/autoencoder_kl_wan.py
|
||||
|
||||
x-hunyuan-image3: &hunyuan_image3
|
||||
- vllm_omni/diffusion/models/hunyuan_image3/
|
||||
- vllm_omni/model_executor/models/hunyuan_image3/
|
||||
@@ -149,6 +153,10 @@ x-lingbot-video: &lingbot_video
|
||||
x-lingbot-world: &lingbot_world
|
||||
- vllm_omni/diffusion/models/lingbot_world/
|
||||
|
||||
x-kandinsky6: &kandinsky6
|
||||
- vllm_omni/diffusion/models/kandinsky6/
|
||||
- vllm_omni/model_extras/kandinsky6.py
|
||||
|
||||
|
||||
source_file_dependencies:
|
||||
# Hardware presets and shared test helpers. Not a job key — do not attach this
|
||||
@@ -383,6 +391,12 @@ source_file_dependencies:
|
||||
- tests/e2e/accuracy/test_qwen_image.py
|
||||
- tests/e2e/accuracy/test_diffusers_backend_similarity.py
|
||||
|
||||
diffusion_joy_image_accuracy:
|
||||
- *joy_image
|
||||
- tests/diffusion/models/joy_image/
|
||||
- tests/e2e/accuracy/test_joy_image_edit.py
|
||||
- recipes/JD/JoyAI-Image-Edit.md
|
||||
|
||||
diffusion_qwen_image_perf:
|
||||
- *qwen_image
|
||||
- tests/dfx/perf/scripts/run_diffusion_benchmark.py
|
||||
@@ -491,6 +505,11 @@ source_file_dependencies:
|
||||
- tests/dfx/perf/scripts/run_benchmark.py
|
||||
- tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json
|
||||
|
||||
diffusion_kandinsky6_function:
|
||||
- *kandinsky6
|
||||
- tests/e2e/offline_inference/test_kandinsky6.py
|
||||
- tests/e2e/online_serving/test_kandinsky6.py
|
||||
|
||||
|
||||
# Invalid-param jobs: model business code only, plus the reliability scripts.
|
||||
reliability_invalid_param_h100:
|
||||
|
||||
@@ -102,6 +102,7 @@ CI_SOURCE_FILE_DEPENDENCIES_PATH = ROOT / ".buildkite/common/ci_source_file_depe
|
||||
SOURCE_FILTER_FALLBACK_KEY = "source_filter_fallback"
|
||||
CUDA_HF_TOKEN_ENV = "VLLM_CI_HF_TOKEN"
|
||||
CUDA_HF_TOKEN_EXPORT = f'if [ -n "$${{{CUDA_HF_TOKEN_ENV}:-}}" ]; then export HF_TOKEN="$${{{CUDA_HF_TOKEN_ENV}}}"; fi'
|
||||
CUDA_RUNTIME_CHECK = "python3 .buildkite/cuda/scripts/check_vllm_runtime.py"
|
||||
|
||||
# Bootstrap Buildkite ``if`` expressions.
|
||||
# ``*_MAIN_IF``: main + env schedule. ``*_LABEL_IF``: PR label (and/or composed with MAIN).
|
||||
@@ -620,11 +621,11 @@ def _expand_mirror_hardwares(step: dict[str, Any]) -> dict[str, Any] | None:
|
||||
if any(preset_name == chip or preset_name.startswith(f"{chip}_") for chip in _cuda_mirror_chips()):
|
||||
commands = merged.get("commands")
|
||||
if isinstance(commands, list):
|
||||
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, *commands]
|
||||
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, CUDA_RUNTIME_CHECK, *commands]
|
||||
elif commands is None:
|
||||
merged["commands"] = [CUDA_HF_TOKEN_EXPORT]
|
||||
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, CUDA_RUNTIME_CHECK]
|
||||
else:
|
||||
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, commands]
|
||||
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, CUDA_RUNTIME_CHECK, commands]
|
||||
# Preset retry (K8S_RETRY on l4_*) must not clobber a step that opted out.
|
||||
if "retry" in step:
|
||||
merged["retry"] = step["retry"]
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
"""Verify the pinned vLLM and compiled CUDA imports on a GPU CI worker."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
dockerfile = Path(__file__).resolve().parents[3] / "docker" / "Dockerfile.ci"
|
||||
match = re.search(
|
||||
r"^ARG VLLM_VERSION=([0-9]+\.[0-9]+\.[0-9]+)$",
|
||||
dockerfile.read_text(),
|
||||
re.MULTILINE,
|
||||
)
|
||||
if match is None:
|
||||
raise RuntimeError(f"Missing vLLM release version in {dockerfile}")
|
||||
expected_version = match.group(1)
|
||||
|
||||
import torch
|
||||
import vllm
|
||||
|
||||
assert vllm.__version__ == expected_version, vllm.__version__
|
||||
assert torch.cuda.is_available(), "vLLM runtime check requires a GPU worker"
|
||||
for module in ("flashinfer", "vllm._C_stable_libtorch"):
|
||||
importlib.import_module(module)
|
||||
print(
|
||||
f"Verified vLLM CUDA runtime: vllm={vllm.__version__} "
|
||||
f"expected={expected_version} torch={torch.__version__} "
|
||||
f"cuda={torch.version.cuda} device={torch.cuda.get_device_name()}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -38,6 +38,17 @@ steps:
|
||||
- group: ":card_index_dividers: Diffusion Test"
|
||||
depends_on: upload-merge-pipeline
|
||||
steps:
|
||||
- label: "Diffusion · AR Paged Attention GPU Test"
|
||||
timeout_in_minutes: 20
|
||||
commands:
|
||||
- export VLLM_OMNI_AR_FA_REQUIRED=1
|
||||
- >-
|
||||
timeout 15m pytest -sv
|
||||
tests/diffusion/ar_diffusion/test_paged_attention.py
|
||||
-m 'core_model and cuda and L4 and cards_1'
|
||||
--run-level "core_model"
|
||||
mirror_hardwares: l4_1
|
||||
|
||||
- label: "Diffusion · LoRA Test"
|
||||
timeout_in_minutes: 30
|
||||
commands:
|
||||
@@ -178,6 +189,15 @@ steps:
|
||||
'
|
||||
mirror_hardwares: h100_1
|
||||
|
||||
- label: "Diffusion · Kandinsky6 Test"
|
||||
source_file_dependencies: diffusion_kandinsky6_function
|
||||
commands:
|
||||
- |
|
||||
timeout 40m bash -c "
|
||||
pytest -s -v tests/e2e/offline_inference/test_kandinsky6.py tests/e2e/online_serving/test_kandinsky6.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
|
||||
"
|
||||
mirror_hardwares: h100_1
|
||||
|
||||
- label: "Diffusion · Wan22 Test"
|
||||
source_file_dependencies: diffusion_wan22_function
|
||||
commands:
|
||||
|
||||
@@ -48,6 +48,8 @@ steps:
|
||||
- label: ":full_moon: Omni · Doc Test with H100 · 2-GPU"
|
||||
source_file_dependencies: omni_qwen3_omni_doc
|
||||
timeout_in_minutes: 90
|
||||
artifact_paths:
|
||||
- audio_*.wav
|
||||
commands:
|
||||
- pytest -s -v tests/examples/ -m "full_model and omni and H100 and B200 and cards_2" --run-level "full_model"
|
||||
|
||||
@@ -156,6 +158,11 @@ steps:
|
||||
- label: ":full_moon: TTS · Function Test with L4"
|
||||
source_file_dependencies: tts_qwen3_tts_function
|
||||
timeout_in_minutes: 120
|
||||
env:
|
||||
VLLM_OMNI_FAILED_SPEECH_AUDIO_DIR: .
|
||||
artifact_paths:
|
||||
- failed_speech_*.wav
|
||||
- failed_speech_*.json
|
||||
commands:
|
||||
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
|
||||
- export VLLM_USE_DEEP_GEMM="0"
|
||||
@@ -261,6 +268,12 @@ steps:
|
||||
- pytest -s -v tests/e2e/accuracy/test_qwen_image.py -m "H100 and B200 and cards_1" --run-level full_model
|
||||
- pytest -s -v tests/e2e/accuracy/test_diffusers_backend_similarity.py -k '2i_matches_diffusers' -m "full_model and H100 and B200 and cards_1" --run-level full_model
|
||||
|
||||
- label: ":full_moon: JoyAI-Image-Edit · Accuracy Test"
|
||||
source_file_dependencies: diffusion_joy_image_accuracy
|
||||
timeout_in_minutes: 180
|
||||
commands:
|
||||
- pytest -s -v tests/e2e/accuracy/test_joy_image_edit.py -m "full_model and diffusion and H100 and B200 and cards_1" --run-level full_model
|
||||
|
||||
- label: ":full_moon: HunyuanImage3-DIT · Accuracy Test"
|
||||
source_file_dependencies: diffusion_hunyuan_image3_accuracy
|
||||
timeout_in_minutes: 180
|
||||
|
||||
@@ -105,7 +105,7 @@ steps:
|
||||
|
||||
- label: "Diffusion · Other Test"
|
||||
commands:
|
||||
- timeout 15m pytest -sv tests/diffusion/*.py tests/diffusion/ar_diffusion -m 'core_model and cuda' --run-level "core_model"
|
||||
- timeout 15m pytest -sv tests/diffusion/*.py tests/diffusion/ar_diffusion tests/diffusion/layers/test_adalayernorm_*.py -m 'core_model and cuda' --run-level "core_model"
|
||||
mirror_hardwares: l4_1
|
||||
|
||||
- group: ":card_index_dividers: Tiny Model Tests [Single GPU]"
|
||||
@@ -122,7 +122,7 @@ steps:
|
||||
commands:
|
||||
- |
|
||||
timeout 15m bash -c "
|
||||
pytest -sv tests/engine/ tests/model_executor/ tests/worker/test_batched_omni_output.py tests/worker_v2/ -m 'core_model and cuda' --run-level 'core_model'
|
||||
pytest -sv tests/engine/ tests/model_executor/ tests/worker/test_batched_omni_output.py tests/worker/test_gpu_generation_async_output.py tests/worker_v2/ -m 'core_model and cuda' --run-level 'core_model'
|
||||
"
|
||||
mirror_hardwares: l4_1
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ steps:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
# Buildkite will automatically replace this with the actual commit hash
|
||||
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}"
|
||||
VLLM_VERSION: "v0.30.0"
|
||||
VLLM_VERSION: "v0.31.0"
|
||||
priority: 100
|
||||
timeout_in_minutes: 60
|
||||
soft_fail: false
|
||||
@@ -271,10 +271,10 @@ The [WF-05 template](example_workflows/vLLM-Omni%20MiniMax-H3%20Latent%20Mask%20
|
||||
Connect a **Latent Mask Editing** node to **Generate Video → latent_edit** to edit a source clip instead of generating from scratch. It uploads the source media and serializes the video/audio noise masks the MiniMax H3 API accepts:
|
||||
|
||||
- `source_video` / `source_audio` — the media to edit.
|
||||
- `video_mask` — a ComfyUI mask image; `0` preserves a region, `1` regenerates it, fractional values blend. A 2D mask `[H, W]` is applied to every frame; a 3D mask `[T, H, W]` is treated as a temporal mask (one slice per frame) for continuation or extension. It is resized to the video latent grid.
|
||||
- `video_mask` — a ComfyUI mask image; `0` preserves a region, `1` regenerates it, fractional values blend. A 2D mask `[H, W]` is applied to every frame; a 3D mask `[T, H, W]` is treated as a temporal mask (one slice per frame) for continuation or extension. The client area-downsamples it by 16 to keep the upload small, and the server resizes it to the video latent grid.
|
||||
- `audio_mask` — a scalar in `[0, 1]`; `0` keeps the source audio, `1` regenerates it, fractional values blend.
|
||||
|
||||
A non-trivial mask requires its matching source, and a source without a mask is rejected. This node only forwards inputs to the server; the served model must declare latent-mask editing support (MiniMax-H3).
|
||||
The node requires at least one mask; the server then enforces the cross-field rules (a source requires a matching mask, and a non-trivial mask requires its source). This node only forwards inputs to the server; the served model must declare latent-mask editing support (MiniMax-H3).
|
||||
|
||||
### TTS (e.g., Qwen TTS series)
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ from .comfyui_vllm_omni.nodes import (
|
||||
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"VLLMOmniMiniMaxH3TemporalMask": VLLMOmniMiniMaxH3TemporalMask,
|
||||
# === Generation ===
|
||||
"VLLMOmniGenerateImage": VLLMOmniGenerateImage,
|
||||
"VLLMOmniGenerateVideo": VLLMOmniGenerateVideo,
|
||||
@@ -46,6 +45,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"VLLMOmniVoiceClone": VLLMOmniVoiceClone,
|
||||
"VLLMOmniVideoReferences": VLLMOmniVideoReferences,
|
||||
"VLLMOmniLatentMaskEditing": VLLMOmniLatentMaskEditing,
|
||||
"VLLMOmniMiniMaxH3TemporalMask": VLLMOmniMiniMaxH3TemporalMask,
|
||||
# === Params ===
|
||||
"VLLMOmniARSampling": VLLMOmniARSampling,
|
||||
"VLLMOmniDiffusionSampling": VLLMOmniDiffusionSampling,
|
||||
@@ -59,7 +59,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
|
||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VLLMOmniMiniMaxH3TemporalMask": "MiniMax-H3 Temporal Mask",
|
||||
# === Generation ===
|
||||
"VLLMOmniGenerateImage": "Generate Image",
|
||||
"VLLMOmniGenerateVideo": "Generate Video",
|
||||
@@ -69,6 +68,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VLLMOmniVoiceClone": "TTS Voice Cloning",
|
||||
"VLLMOmniVideoReferences": "Video References",
|
||||
"VLLMOmniLatentMaskEditing": "Latent Mask Editing",
|
||||
"VLLMOmniMiniMaxH3TemporalMask": "MiniMax-H3 Temporal Mask",
|
||||
# === Params ===
|
||||
"VLLMOmniARSampling": "AR Sampling Params",
|
||||
"VLLMOmniDiffusionSampling": "Diffusion Sampling Params",
|
||||
|
||||
@@ -8,7 +8,7 @@ import torch
|
||||
from comfy_api.input import AudioInput, VideoInput
|
||||
|
||||
from .utils.api_client import VLLMOmniClient
|
||||
from .utils.latent_mask import _align_frame_count, _video_latent_t
|
||||
from .utils.latent_mask import _align_frame_count
|
||||
from .utils.logger import get_logger
|
||||
from .utils.models import lookup_model_spec
|
||||
from .utils.types import (
|
||||
@@ -1133,13 +1133,13 @@ class VLLMOmniMiniMaxH3TemporalMask:
|
||||
else:
|
||||
raise ValueError(f"Unknown temporal mask mode: {mode}")
|
||||
available = min(frames, math.floor(boundary * 24 + 1e-8))
|
||||
# Snap the preserved prefix to whole VAE clips (5 + 17n frames) so every
|
||||
# latent it maps to is fully preserved.
|
||||
prefix = 0 if available < 5 else 5 + 17 * ((available - 5) // 17)
|
||||
preserved = _video_latent_t(prefix) if prefix else 0
|
||||
total = _video_latent_t(frames)
|
||||
mask = torch.ones(total, 1, 1)
|
||||
mask[:preserved] = 0
|
||||
# One slice per output frame; the server pools frames to the latent grid.
|
||||
mask = torch.ones(frames, 1, 1)
|
||||
mask[:prefix] = 0
|
||||
indices = torch.arange(frames, device=images.device)
|
||||
source_indices = (indices * (source_fps / 24)).floor().long().clamp(max=images.shape[0] - 1)
|
||||
preview_images = images.index_select(0, source_indices)
|
||||
preview_mask = mask.index_select(0, torch.arange(frames) * total // frames)
|
||||
return mask, 24.0, preview_images, preview_mask
|
||||
return mask, 24.0, preview_images, mask
|
||||
@@ -30,7 +30,7 @@ from .format import (
|
||||
video_to_base64,
|
||||
video_to_bytes,
|
||||
)
|
||||
from .latent_mask import scalar_mask_to_json, video_mask_to_grid_json
|
||||
from .latent_mask import scalar_mask_to_json, video_mask_to_json
|
||||
from .logger import get_logger, pretty_printer
|
||||
from .models import lookup_model_spec
|
||||
from .types import (
|
||||
@@ -388,6 +388,15 @@ class VLLMOmniClient:
|
||||
content_type="image/png",
|
||||
)
|
||||
|
||||
if len(keyframe_images) == 2:
|
||||
for image_filename, image in keyframe_images:
|
||||
form.add_field(
|
||||
"input_references",
|
||||
image_tensor_to_png_bytes(image, image_filename),
|
||||
filename=image_filename,
|
||||
content_type="image/png",
|
||||
)
|
||||
|
||||
# === latent-mask editing (MiniMax H3) ===
|
||||
if latent_edit is not None:
|
||||
source_video = latent_edit.get("source_video")
|
||||
@@ -398,21 +407,16 @@ class VLLMOmniClient:
|
||||
if video_mask is None and audio_mask is None:
|
||||
raise ValueError("Latent-mask editing requires at least one mask.")
|
||||
|
||||
video_mask_trivial = video_mask is None or bool((video_mask == 1.0).all().item())
|
||||
audio_mask_trivial = audio_mask is None or audio_mask == 1.0
|
||||
if not video_mask_trivial and source_video is None:
|
||||
raise ValueError("A non-trivial video mask requires a source video.")
|
||||
if not audio_mask_trivial and source_audio is None and source_video is None:
|
||||
raise ValueError("A non-trivial audio mask requires a source audio or a source video with audio.")
|
||||
|
||||
if source_video is not None:
|
||||
form.add_field(
|
||||
"source_video",
|
||||
video_to_bytes(source_video, "source.mp4"),
|
||||
video_to_bytes(source_video),
|
||||
filename="source.mp4",
|
||||
content_type="video/mp4",
|
||||
)
|
||||
if source_audio is not None:
|
||||
# The filename extension selects the audio codec (mp3), so it is
|
||||
# meaningful even though aiohttp uses the explicit filename below.
|
||||
form.add_field(
|
||||
"source_audio",
|
||||
audio_to_bytes(source_audio, "source_audio.mp3"),
|
||||
@@ -420,7 +424,7 @@ class VLLMOmniClient:
|
||||
content_type="audio/mpeg",
|
||||
)
|
||||
if video_mask is not None:
|
||||
mask_json = video_mask_to_grid_json(video_mask, width=width, height=height, num_frames=num_frames)
|
||||
mask_json = video_mask_to_json(video_mask)
|
||||
form.add_field(
|
||||
"video_noise_mask",
|
||||
mask_json.encode("utf-8"),
|
||||
@@ -435,15 +439,6 @@ class VLLMOmniClient:
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
if len(keyframe_images) == 2:
|
||||
for image_filename, image in keyframe_images:
|
||||
form.add_field(
|
||||
"input_references",
|
||||
image_tensor_to_png_bytes(image, image_filename),
|
||||
filename=image_filename,
|
||||
content_type="image/png",
|
||||
)
|
||||
|
||||
# === model specific params. Either use a specialized builder, or add flattened fields as-is ===
|
||||
if model_params is not None:
|
||||
model_params = dict(model_params)
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""Client-side serialization of MiniMax-H3 latent-edit masks.
|
||||
|
||||
A video mask is sent in frame space — a 2D spatial ``[H, W]`` (applied to every
|
||||
frame) or a 3D ``[T, H, W]`` (one slice per frame) — and the server resolves it
|
||||
to the latent grid. An audio mask is a scalar applied to all time steps.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
# The server rejects mask JSON above 8 MiB, so the spatial axes are
|
||||
# area-downsampled by the VAE spatial stride before upload. This depends only on
|
||||
# the stride, not on the H3 shape lattice, which the server still owns.
|
||||
_VAE_SPATIAL_STRIDE = 16
|
||||
_MASK_DECIMALS = 4
|
||||
|
||||
|
||||
def scalar_mask_to_json(value: float) -> str:
|
||||
if not 0.0 <= value <= 1.0:
|
||||
@@ -13,6 +27,27 @@ def scalar_mask_to_json(value: float) -> str:
|
||||
return str(value)
|
||||
|
||||
|
||||
def video_mask_to_json(mask: torch.Tensor) -> str:
|
||||
"""Serialize a frame-space video mask to JSON for the ``video_noise_mask`` field.
|
||||
|
||||
``mask`` is a 2D spatial mask or a 3D frame-space mask. Its spatial axes are
|
||||
area-downsampled by the VAE stride; the server resizes the result to the
|
||||
latent grid, so the client neither floors the canvas nor maps frames to
|
||||
latents.
|
||||
"""
|
||||
if mask.ndim not in (2, 3):
|
||||
raise ValueError(f"expected a 2D or 3D mask tensor, got {mask.ndim}D")
|
||||
frames = mask.unsqueeze(0) if mask.ndim == 2 else mask
|
||||
size = tuple(max(1, math.ceil(dim / _VAE_SPATIAL_STRIDE)) for dim in frames.shape[1:])
|
||||
frames = F.interpolate(frames.unsqueeze(1).float(), size=size, mode="area").squeeze(1)
|
||||
if mask.ndim == 2:
|
||||
frames = frames.squeeze(0)
|
||||
# Round in float64 so tolist() emits short decimals rather than float32 noise.
|
||||
return json.dumps(frames.double().round(decimals=_MASK_DECIMALS).tolist(), separators=(",", ":"))
|
||||
|
||||
|
||||
# The temporal-mask node snaps its preserve boundary to the H3 frame lattice;
|
||||
# this mirrors the server shape planner.
|
||||
def _align_frame_count(frame_count: int) -> int:
|
||||
if frame_count <= 0:
|
||||
return 1
|
||||
@@ -20,32 +55,3 @@ def _align_frame_count(frame_count: int) -> int:
|
||||
while current % 17 != 5:
|
||||
current += 1
|
||||
return current
|
||||
|
||||
|
||||
def _video_latent_t(frame_count: int) -> int:
|
||||
if frame_count <= 5:
|
||||
return 2
|
||||
return ((int(frame_count) - 5) // 17) * 5 + 2
|
||||
|
||||
|
||||
def video_mask_to_grid(mask: torch.Tensor, *, width: int, height: int, num_frames: int) -> torch.Tensor:
|
||||
tv = _video_latent_t(_align_frame_count(num_frames))
|
||||
height = int(height) // 32 * 32
|
||||
width = int(width) // 32 * 32
|
||||
gh, gw = height // 16, width // 16
|
||||
|
||||
if mask.ndim == 2:
|
||||
mask = mask.unsqueeze(0)
|
||||
elif mask.ndim != 3:
|
||||
raise ValueError(f"expected a 2D or 3D mask tensor, got {mask.ndim}D")
|
||||
|
||||
grid = F.interpolate(mask.unsqueeze(1).float(), size=(gh, gw), mode="area").squeeze(1)
|
||||
if grid.shape[0] == 1:
|
||||
grid = grid.expand(tv, gh, gw)
|
||||
elif grid.shape[0] != tv:
|
||||
grid = F.interpolate(grid.unsqueeze(0).unsqueeze(0), size=(tv, gh, gw), mode="nearest").squeeze(0).squeeze(0)
|
||||
return grid
|
||||
|
||||
|
||||
def video_mask_to_grid_json(mask: torch.Tensor, *, width: int, height: int, num_frames: int) -> str:
|
||||
return json.dumps(video_mask_to_grid(mask, width=width, height=height, num_frames=num_frames).tolist())
|
||||
@@ -12,6 +12,6 @@ Edit a source video with MiniMax-H3 using spatial or temporal masks. The workflo
|
||||
Open **vLLM-Omni MiniMax-H3 Latent Mask Editing.json** from the example workflows and set the Generate Video URL/model for the case you want to run.
|
||||
|
||||
1. Upload your source video in Load Video.
|
||||
2. Adjust the case's mask. For removal and inpainting, set Mask Canvas to the video dimensions, Mask Rectangle to the region size, and Combine Masks x/y to its position. For continuation and extension, set MiniMax-H3 Temporal Mask's mode and duration; continuation also uses `preserve_fraction`. Match the duration in Generate Video, and use a duration longer than the source for extension. Mask values are `0` to preserve and `1` to regenerate.
|
||||
2. Adjust the case's mask. For removal and inpainting, set Mask Canvas to the video dimensions, Mask Rectangle to the region size, and Combine Masks x/y to its position, or replace that chain with Load Image (as Mask) for an arbitrary-shape mask. For continuation and extension, set MiniMax-H3 Temporal Mask's mode and duration; continuation also uses `preserve_fraction`. Match the duration in Generate Video, and use a duration longer than the source for extension. Mask values are `0` to preserve and `1` to regenerate.
|
||||
3. Adjust the prompt to describe the desired content. Set the output dimensions and duration in Generate Video. If editing audio, update the sound description and audio mask (`0` preserves, `1` regenerates the whole clip).
|
||||
4. Select the case's Mask Preview Save Video node and choose **Execute to selected output nodes** to preview the mask as a red overlay without inference, or select its Result Save Video node to generate the edited video. Running the entire workflow generates all four cases.
|
||||
-499
@@ -1,499 +0,0 @@
|
||||
{
|
||||
"id": "latent-mask-editing-7380",
|
||||
"revision": 0,
|
||||
"last_node_id": 7,
|
||||
"last_link_id": 5,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
40,
|
||||
20
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
210
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "Latent-mask editing server prerequisite",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Requires a vLLM-Omni server that declares latent-mask editing support (MiniMax-H3, EDIT-01). The source clip and video/audio noise masks are uploaded to /v1/videos; 0 preserves, 1 regenerates, fractional blends."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadVideo",
|
||||
"pos": [
|
||||
40,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "file",
|
||||
"name": "file",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "file"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "LoadImageMask",
|
||||
"pos": [
|
||||
40,
|
||||
460
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
160
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "image"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "channel",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "channel"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImageMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"red"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "VLLMOmniLatentMaskEditing",
|
||||
"pos": [
|
||||
560,
|
||||
260
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
300
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "source_video",
|
||||
"name": "source_video",
|
||||
"type": "VIDEO",
|
||||
"link": 1,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "source_audio",
|
||||
"name": "source_audio",
|
||||
"type": "AUDIO",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "video_mask",
|
||||
"name": "video_mask",
|
||||
"type": "MASK",
|
||||
"link": 2,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "audio_mask",
|
||||
"name": "audio_mask",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "audio_mask"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "latent_edit",
|
||||
"name": "latent_edit",
|
||||
"type": "LATENT_MASK_EDITING",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniLatentMaskEditing"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "VLLMOmniMiniMaxH3Params",
|
||||
"pos": [
|
||||
40,
|
||||
680
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
140
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "audio_flow_shift",
|
||||
"name": "audio_flow_shift",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "audio_flow_shift"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "flow_shift",
|
||||
"name": "flow_shift",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "flow_shift"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video params",
|
||||
"name": "video params",
|
||||
"type": "VIDEO_PARAMS",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniMiniMaxH3Params"
|
||||
},
|
||||
"widgets_values": [
|
||||
3.0,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "VLLMOmniGenerateVideo",
|
||||
"pos": [
|
||||
1080,
|
||||
200
|
||||
],
|
||||
"size": [
|
||||
540,
|
||||
680
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "frame",
|
||||
"name": "frame",
|
||||
"type": "IMAGE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "references",
|
||||
"name": "references",
|
||||
"type": "VIDEO_REFERENCES",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "sampling_params",
|
||||
"name": "sampling_params",
|
||||
"type": "SAMPLING_PARAMS",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "lora",
|
||||
"name": "lora",
|
||||
"type": "REMOTE_LORA",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "model_params",
|
||||
"name": "model_params",
|
||||
"type": "VIDEO_PARAMS",
|
||||
"link": 4,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "fast_h3",
|
||||
"name": "fast_h3",
|
||||
"type": "FASTH3_DEPLOYMENT",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "latent_edit",
|
||||
"name": "latent_edit",
|
||||
"type": "LATENT_MASK_EDITING",
|
||||
"link": 3,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "url",
|
||||
"name": "url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "url"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "model",
|
||||
"name": "model",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "model"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "prompt",
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "negative_prompt",
|
||||
"name": "negative_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "negative_prompt"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "width",
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "height",
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "fps",
|
||||
"name": "fps",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "fps"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "duration",
|
||||
"name": "duration",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "duration"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniGenerateVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"http://127.0.0.1:8000/v1",
|
||||
"MiniMaxAI/MiniMax-H3",
|
||||
"Partially restyle the clip and soundtrack",
|
||||
"",
|
||||
160,
|
||||
120,
|
||||
24,
|
||||
4.458
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "SaveVideo",
|
||||
"pos": [
|
||||
1700,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
160
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"link": 5,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "filename_prefix",
|
||||
"name": "filename_prefix",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "filename_prefix"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "format",
|
||||
"name": "format",
|
||||
"type": "COMFY_DYNAMICCOMBO_V3",
|
||||
"widget": {
|
||||
"name": "format"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"vllm-omni/latent-edit",
|
||||
"mp4"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"VIDEO"
|
||||
],
|
||||
[
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
4,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
6,
|
||||
"LATENT_MASK_EDITING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
6,
|
||||
4,
|
||||
"VIDEO_PARAMS"
|
||||
],
|
||||
[
|
||||
5,
|
||||
6,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"VIDEO"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
-560
@@ -1,560 +0,0 @@
|
||||
{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 7,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadVideo",
|
||||
"pos": [
|
||||
40,
|
||||
100
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "file",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "file"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "SolidMask",
|
||||
"pos": [
|
||||
40,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
160
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "value"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SolidMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.0,
|
||||
160,
|
||||
120
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "SolidMask",
|
||||
"pos": [
|
||||
40,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
160
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "value"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SolidMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
1.0,
|
||||
160,
|
||||
120
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "BatchMasksNode",
|
||||
"pos": [
|
||||
250,
|
||||
380
|
||||
],
|
||||
"size": [
|
||||
200,
|
||||
100
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "masks.mask0",
|
||||
"type": "MASK",
|
||||
"link": 2,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "masks.mask1",
|
||||
"type": "MASK",
|
||||
"link": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "BatchMasksNode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "VLLMOmniLatentMaskEditing",
|
||||
"pos": [
|
||||
450,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source_video",
|
||||
"type": "VIDEO",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "source_audio",
|
||||
"type": "AUDIO",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "video_mask",
|
||||
"type": "MASK",
|
||||
"link": 4
|
||||
},
|
||||
{
|
||||
"name": "audio_mask",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "audio_mask"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent_edit",
|
||||
"type": "LATENT_MASK_EDITING",
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniLatentMaskEditing"
|
||||
},
|
||||
"widgets_values": [
|
||||
-1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "VLLMOmniMiniMaxH3Params",
|
||||
"pos": [
|
||||
40,
|
||||
700
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
160
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "audio_flow_shift",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "audio_flow_shift"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "flow_shift",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "flow_shift"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "video params",
|
||||
"type": "VIDEO_PARAMS",
|
||||
"links": [
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniMiniMaxH3Params"
|
||||
},
|
||||
"widgets_values": [
|
||||
3.0,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VLLMOmniGenerateVideo",
|
||||
"pos": [
|
||||
700,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
450,
|
||||
380
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "frame",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "references",
|
||||
"type": "VIDEO_REFERENCES",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "sampling_params",
|
||||
"type": "SAMPLING_PARAMS",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "lora",
|
||||
"type": "LORA",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "model_params",
|
||||
"type": "VIDEO_PARAMS",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "fast_h3",
|
||||
"type": "FAST_H3",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "latent_edit",
|
||||
"type": "LATENT_MASK_EDITING",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "url"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "model"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "negative_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "negative_prompt"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "fps",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "fps"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "duration"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniGenerateVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"http://127.0.0.1:8000/v1",
|
||||
"MiniMaxAI/MiniMax-H3",
|
||||
"temporal mask: regenerate the ending",
|
||||
"",
|
||||
160,
|
||||
120,
|
||||
24,
|
||||
4.458
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SaveVideo",
|
||||
"pos": [
|
||||
900,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "filename_prefix",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "filename_prefix"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "format"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"temporal_mask",
|
||||
"mp4"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"VIDEO"
|
||||
],
|
||||
[
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
6,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
5,
|
||||
6,
|
||||
0,
|
||||
8,
|
||||
6,
|
||||
"LATENT_MASK_EDITING"
|
||||
],
|
||||
[
|
||||
6,
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
4,
|
||||
"VIDEO_PARAMS"
|
||||
],
|
||||
[
|
||||
7,
|
||||
8,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"VIDEO"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,511 +0,0 @@
|
||||
{
|
||||
"id": "latent-mask-editing-7380",
|
||||
"revision": 0,
|
||||
"last_node_id": 7,
|
||||
"last_link_id": 5,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
40,
|
||||
20
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
210
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "Latent-mask editing server prerequisite",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Requires a vLLM-Omni server that declares latent-mask editing support (MiniMax-H3, EDIT-01). The source clip and video/audio noise masks are uploaded to /v1/videos; 0 preserves, 1 regenerates, fractional blends."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadVideo",
|
||||
"pos": [
|
||||
40,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
120
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "file",
|
||||
"name": "file",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "file"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "SolidMask",
|
||||
"pos": [
|
||||
40,
|
||||
460
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
160
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "value",
|
||||
"name": "value",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "value"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "width",
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "height",
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SolidMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
1.0,
|
||||
160,
|
||||
120
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "VLLMOmniLatentMaskEditing",
|
||||
"pos": [
|
||||
560,
|
||||
260
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
300
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "source_video",
|
||||
"name": "source_video",
|
||||
"type": "VIDEO",
|
||||
"link": 1,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "source_audio",
|
||||
"name": "source_audio",
|
||||
"type": "AUDIO",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "video_mask",
|
||||
"name": "video_mask",
|
||||
"type": "MASK",
|
||||
"link": 2,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "audio_mask",
|
||||
"name": "audio_mask",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "audio_mask"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "latent_edit",
|
||||
"name": "latent_edit",
|
||||
"type": "LATENT_MASK_EDITING",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniLatentMaskEditing"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "VLLMOmniMiniMaxH3Params",
|
||||
"pos": [
|
||||
40,
|
||||
680
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
140
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "audio_flow_shift",
|
||||
"name": "audio_flow_shift",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "audio_flow_shift"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "flow_shift",
|
||||
"name": "flow_shift",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "flow_shift"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video params",
|
||||
"name": "video params",
|
||||
"type": "VIDEO_PARAMS",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniMiniMaxH3Params"
|
||||
},
|
||||
"widgets_values": [
|
||||
3.0,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "VLLMOmniGenerateVideo",
|
||||
"pos": [
|
||||
1080,
|
||||
200
|
||||
],
|
||||
"size": [
|
||||
540,
|
||||
680
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "frame",
|
||||
"name": "frame",
|
||||
"type": "IMAGE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "references",
|
||||
"name": "references",
|
||||
"type": "VIDEO_REFERENCES",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "sampling_params",
|
||||
"name": "sampling_params",
|
||||
"type": "SAMPLING_PARAMS",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "lora",
|
||||
"name": "lora",
|
||||
"type": "REMOTE_LORA",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "model_params",
|
||||
"name": "model_params",
|
||||
"type": "VIDEO_PARAMS",
|
||||
"link": 4,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "fast_h3",
|
||||
"name": "fast_h3",
|
||||
"type": "FASTH3_DEPLOYMENT",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "latent_edit",
|
||||
"name": "latent_edit",
|
||||
"type": "LATENT_MASK_EDITING",
|
||||
"link": 3,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "url",
|
||||
"name": "url",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "url"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "model",
|
||||
"name": "model",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "model"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "prompt",
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "negative_prompt",
|
||||
"name": "negative_prompt",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "negative_prompt"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "width",
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "height",
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "fps",
|
||||
"name": "fps",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "fps"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "duration",
|
||||
"name": "duration",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "duration"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VLLMOmniGenerateVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"http://127.0.0.1:8000/v1",
|
||||
"MiniMaxAI/MiniMax-H3",
|
||||
"Partially restyle the clip and soundtrack",
|
||||
"",
|
||||
160,
|
||||
120,
|
||||
24,
|
||||
4.458
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "SaveVideo",
|
||||
"pos": [
|
||||
1700,
|
||||
300
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
160
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"link": 5,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"localized_name": "filename_prefix",
|
||||
"name": "filename_prefix",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "filename_prefix"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "format",
|
||||
"name": "format",
|
||||
"type": "COMFY_DYNAMICCOMBO_V3",
|
||||
"widget": {
|
||||
"name": "format"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "video",
|
||||
"name": "video",
|
||||
"type": "VIDEO",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"vllm-omni/latent-edit",
|
||||
"mp4"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"VIDEO"
|
||||
],
|
||||
[
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
4,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
6,
|
||||
"LATENT_MASK_EDITING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
6,
|
||||
4,
|
||||
"VIDEO_PARAMS"
|
||||
],
|
||||
[
|
||||
5,
|
||||
6,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"VIDEO"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
+10
-10
@@ -2293,10 +2293,10 @@
|
||||
"title": "Usage Guide",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Use a compatible MiniMax-H3 latent-edit server. Select a source video and set the server URL/model in each Generate Video node.\n\nSelect one Save Preview or Save Video output, then use Execute to selected output nodes. Running the whole workflow submits all four cases.\n\nMask: 0 preserves source latents; 1 regenerates. Audio mask applies to the whole clip, not individual sounds. Preview overlays are silent and do not run inference."
|
||||
"Use a compatible MiniMax-H3 latent-edit server. Select a source video and set the server URL/model in each Generate Video node.\n\nSelect one Save Preview or Save Video output, then use Execute to selected output nodes. Running the whole workflow submits all four cases.\n\nMask: 0 preserves source latents; 1 regenerates. For an arbitrary-shape spatial mask, replace a case's Mask Rectangle chain with Load Image (as Mask). Audio mask applies to the whole clip, not individual sounds. Preview overlays are silent and do not run inference."
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"text": "Use a compatible MiniMax-H3 latent-edit server. Select a source video and set the server URL/model in each Generate Video node.\n\nSelect one Save Preview or Save Video output, then use Execute to selected output nodes. Running the whole workflow submits all four cases.\n\nMask: 0 preserves source latents; 1 regenerates. Audio mask applies to the whole clip, not individual sounds. Preview overlays are silent and do not run inference."
|
||||
"text": "Use a compatible MiniMax-H3 latent-edit server. Select a source video and set the server URL/model in each Generate Video node.\n\nSelect one Save Preview or Save Video output, then use Execute to selected output nodes. Running the whole workflow submits all four cases.\n\nMask: 0 preserves source latents; 1 regenerates. For an arbitrary-shape spatial mask, replace a case's Mask Rectangle chain with Load Image (as Mask). Audio mask applies to the whole clip, not individual sounds. Preview overlays are silent and do not run inference."
|
||||
},
|
||||
"color": "#432",
|
||||
"bgcolor": "#000"
|
||||
@@ -2823,7 +2823,7 @@
|
||||
"Node name for S&R": "VLLMOmniGenerateVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"http://127.0.0.1:8001/v1",
|
||||
"http://127.0.0.1:8000/v1",
|
||||
"MiniMaxAI/MiniMax-H3",
|
||||
"integrated_multimodal_description: The masked area is a flat, seamless continuation of the surrounding wall, matching its color, texture, and lighting. The wall surface remains consistent throughout the video.\n\noverall_soundscape: \n\nnon_diegetic_music: ",
|
||||
"",
|
||||
@@ -2833,7 +2833,7 @@
|
||||
5
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"url": "http://127.0.0.1:8001/v1",
|
||||
"url": "http://127.0.0.1:8000/v1",
|
||||
"model": "MiniMaxAI/MiniMax-H3",
|
||||
"prompt": "integrated_multimodal_description: The masked area is a flat, seamless continuation of the surrounding wall, matching its color, texture, and lighting. The wall surface remains consistent throughout the video.\n\noverall_soundscape: \n\nnon_diegetic_music: ",
|
||||
"negative_prompt": "",
|
||||
@@ -3677,7 +3677,7 @@
|
||||
"Node name for S&R": "VLLMOmniGenerateVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"http://127.0.0.1:8001/v1",
|
||||
"http://127.0.0.1:8000/v1",
|
||||
"MiniMaxAI/MiniMax-H3",
|
||||
"integrated_multimodal_description: A small orange cat rests curled up in the masked area, gently swaying its tail from side to side. Its appearance matches the surrounding anime style, lighting, and shadows.\n\noverall_soundscape:\n\nnon_diegetic_music: ",
|
||||
"",
|
||||
@@ -3687,7 +3687,7 @@
|
||||
5
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"url": "http://127.0.0.1:8001/v1",
|
||||
"url": "http://127.0.0.1:8000/v1",
|
||||
"model": "MiniMaxAI/MiniMax-H3",
|
||||
"prompt": "integrated_multimodal_description: A small orange cat rests curled up in the masked area, gently swaying its tail from side to side. Its appearance matches the surrounding anime style, lighting, and shadows.\n\noverall_soundscape:\n\nnon_diegetic_music: ",
|
||||
"negative_prompt": "",
|
||||
@@ -3916,7 +3916,7 @@
|
||||
"Node name for S&R": "VLLMOmniGenerateVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"http://127.0.0.1:8001/v1",
|
||||
"http://127.0.0.1:8000/v1",
|
||||
"MiniMaxAI/MiniMax-H3",
|
||||
"integrated_multimodal_description: The sky turns completely overcast, with the sun hidden and no direct sunlight, as torrential rain pours over the school entrance in dense visible streaks and splashes on the wet pavement. Distant lightning flashes in the sky behind the school buildings.\n\noverall_soundscape: Loud, steady rainfall, splashing water, and gusts of wind.\n\nnon_diegetic_music: None.",
|
||||
"",
|
||||
@@ -3926,7 +3926,7 @@
|
||||
5
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"url": "http://127.0.0.1:8001/v1",
|
||||
"url": "http://127.0.0.1:8000/v1",
|
||||
"model": "MiniMaxAI/MiniMax-H3",
|
||||
"prompt": "integrated_multimodal_description: The sky turns completely overcast, with the sun hidden and no direct sunlight, as torrential rain pours over the school entrance in dense visible streaks and splashes on the wet pavement. Distant lightning flashes in the sky behind the school buildings.\n\noverall_soundscape: Loud, steady rainfall, splashing water, and gusts of wind.\n\nnon_diegetic_music: None.",
|
||||
"negative_prompt": "",
|
||||
@@ -4363,7 +4363,7 @@
|
||||
"Node name for S&R": "VLLMOmniGenerateVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"http://127.0.0.1:8001/v1",
|
||||
"http://127.0.0.1:8000/v1",
|
||||
"MiniMaxAI/MiniMax-H3",
|
||||
"integrated_multimodal_description: Cut to the same school on a summer night beneath a deep blue sky dotted with stars. A firework rises from behind the school buildings, leaving a glowing trail; the camera slowly tilts upward to follow it into the sky, ending as it bursts into a large bloom of colorful sparks.\n\noverall_soundscape: Soft summer insect sounds, followed by the firework rising with a whistle and bursting with a distant boom and crackling sparks.\n\nnon_diegetic_music: N/A",
|
||||
"",
|
||||
@@ -4373,7 +4373,7 @@
|
||||
10
|
||||
],
|
||||
"widgets_values_named": {
|
||||
"url": "http://127.0.0.1:8001/v1",
|
||||
"url": "http://127.0.0.1:8000/v1",
|
||||
"model": "MiniMaxAI/MiniMax-H3",
|
||||
"prompt": "integrated_multimodal_description: Cut to the same school on a summer night beneath a deep blue sky dotted with stars. A firework rises from behind the school buildings, leaving a glowing trail; the camera slowly tilts upward to follow it into the sky, ending as it bursts into a large bloom of colorful sparks.\n\noverall_soundscape: Soft summer insect sounds, followed by the firework rising with a whistle and bursting with a distant boom and crackling sparks.\n\nnon_diegetic_music: N/A",
|
||||
"negative_prompt": "",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
"""Benchmark and exactness check for the Wan VAE decoder fast path.
|
||||
|
||||
Loads a diffusers Wan VAE (by default the Cosmos3 one), decodes seeded latents
|
||||
@@ -30,10 +30,13 @@ return the full video on rank 0 only, so equality and PSNR are computed there.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import statistics
|
||||
import time
|
||||
from collections.abc import Iterable
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
@@ -82,6 +85,10 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--profile", action="store_true", help="Print a torch.profiler CUDA kernel table")
|
||||
parser.add_argument("--profile-rows", type=int, default=25)
|
||||
parser.add_argument("--save-output", default=None, help="Save the `off` decode output to this .pt path")
|
||||
parser.add_argument(
|
||||
"--first-frame-ablation", action="store_true", help="Compare channels_last with/without first-frame Conv2d"
|
||||
)
|
||||
parser.add_argument("--latents", help="For ablation: saved VAE-input tensor (.pt), after latent mean/std scaling")
|
||||
parser.add_argument(
|
||||
"--vae-patch-parallel-size",
|
||||
type=int,
|
||||
@@ -254,8 +261,99 @@ def run_level(
|
||||
return output.detach(), stats
|
||||
|
||||
|
||||
@torch.inference_mode()
|
||||
def run_first_frame_ablation(args: argparse.Namespace, dtype: torch.dtype, device: torch.device) -> None:
|
||||
"""Same-model ABBA comparison; disable only the first-frame helper in the reference arm."""
|
||||
from vllm_omni.diffusion.distributed.autoencoders.wan_vae_fastpath import forwards
|
||||
|
||||
if args.iters < 2 or args.warmup < 0:
|
||||
raise ValueError("Ablation requires --iters >= 2 and --warmup >= 0")
|
||||
vae = load_vae(args, dtype, device, parallel=False)
|
||||
report = install_wan_vae_fastpath(vae, level="channels_last")
|
||||
if not report.installed:
|
||||
raise RuntimeError(f"channels_last installation failed: {report.reason}")
|
||||
latents = make_latents(vae, args, dtype, device)
|
||||
if args.latents:
|
||||
saved = torch.load(args.latents, map_location="cpu", weights_only=True)
|
||||
saved = saved[:, :, : latents.shape[2]]
|
||||
if saved.shape != latents.shape:
|
||||
raise ValueError(f"Saved latent shape {saved.shape} does not match {latents.shape}")
|
||||
latents = saved.to(device=device, dtype=dtype).contiguous()
|
||||
helpers = {"baseline": lambda conv, x, bias: None, "candidate": forwards._first_frame_conv2d}
|
||||
optimized_calls = 0
|
||||
|
||||
def counted(conv, x, bias):
|
||||
nonlocal optimized_calls
|
||||
result = helpers["candidate"](conv, x, bias)
|
||||
optimized_calls += result is not None
|
||||
return result
|
||||
|
||||
with patch.object(forwards, "_first_frame_conv2d", counted):
|
||||
decode(vae, latents)
|
||||
sync()
|
||||
if optimized_calls == 0:
|
||||
raise RuntimeError("The selected workload did not exercise first-frame Conv2d")
|
||||
measurements = []
|
||||
outputs: dict[str, list[torch.Tensor]] = {arm: [] for arm in helpers}
|
||||
for _ in range(args.warmup):
|
||||
for helper in helpers.values():
|
||||
with patch.object(forwards, "_first_frame_conv2d", helper):
|
||||
decode(vae, latents)
|
||||
sync()
|
||||
for iteration in range(args.iters):
|
||||
# Alternating AB and BA pairs give ABBA order without dropping odd runs.
|
||||
arms = ("baseline", "candidate") if iteration % 2 == 0 else ("candidate", "baseline")
|
||||
for arm in arms:
|
||||
with patch.object(forwards, "_first_frame_conv2d", helpers[arm]):
|
||||
sync()
|
||||
torch.accelerator.reset_peak_memory_stats()
|
||||
start = time.perf_counter()
|
||||
output = decode(vae, latents)
|
||||
sync()
|
||||
elapsed = (time.perf_counter() - start) * 1000
|
||||
peak = torch.accelerator.max_memory_allocated()
|
||||
measurements.append({"arm": arm, "wall_ms": elapsed, "peak_allocated_bytes": peak})
|
||||
if len(outputs[arm]) < 2:
|
||||
outputs[arm].append(output.cpu())
|
||||
del output
|
||||
baseline, candidate = outputs["baseline"][0], outputs["candidate"][0]
|
||||
difference = candidate.float() - baseline.float()
|
||||
stats = {}
|
||||
for arm in helpers:
|
||||
times = [row["wall_ms"] for row in measurements if row["arm"] == arm]
|
||||
stats[arm] = {
|
||||
"median_ms": statistics.median(times),
|
||||
"mean_ms": statistics.mean(times),
|
||||
"std_ms": statistics.pstdev(times),
|
||||
"min_ms": min(times),
|
||||
"max_ms": max(times),
|
||||
}
|
||||
result = {
|
||||
"scope": "VAE decode; same-head channels_last first-frame Conv2d ablation",
|
||||
"args": vars(args),
|
||||
"torch": torch.__version__,
|
||||
"cuda": torch.version.cuda,
|
||||
"baseline": stats["baseline"],
|
||||
"candidate": stats["candidate"],
|
||||
"latency_reduction": 1 - stats["candidate"]["median_ms"] / stats["baseline"]["median_ms"],
|
||||
"optimized_calls_per_decode": optimized_calls,
|
||||
"outputs_finite": bool(torch.isfinite(baseline).all() and torch.isfinite(candidate).all()),
|
||||
"self_repeat_equal": {arm: torch.equal(*values) for arm, values in outputs.items()},
|
||||
"candidate_vs_baseline": {
|
||||
"torch_equal": torch.equal(candidate, baseline),
|
||||
"max_abs": difference.abs().max().item(),
|
||||
"relative_l2": (difference.norm() / baseline.float().norm().clamp_min(1e-30)).item(),
|
||||
"psnr_db": psnr(candidate, baseline),
|
||||
},
|
||||
"measurements": measurements,
|
||||
}
|
||||
print("FIRST_FRAME_ABLATION " + json.dumps(result))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
if args.latents and not args.first_frame_ablation:
|
||||
raise SystemExit("--latents requires --first-frame-ablation")
|
||||
if not torch.cuda.is_available():
|
||||
raise SystemExit("CUDA device required")
|
||||
rank, world_size = init_distributed()
|
||||
@@ -267,6 +365,12 @@ def main() -> None:
|
||||
)
|
||||
device = torch.device("cuda", torch.accelerator.current_device_index()) if world_size > 1 else torch.device("cuda")
|
||||
dtype = DTYPES[args.dtype]
|
||||
if args.first_frame_ablation:
|
||||
if world_size != 1 or parallel:
|
||||
raise SystemExit("--first-frame-ablation requires one GPU")
|
||||
with torch.backends.cudnn.flags(benchmark=False, allow_tf32=False):
|
||||
run_first_frame_ablation(args, dtype, device)
|
||||
return
|
||||
levels: Iterable[str] = [level.strip() for level in args.fast_path.split(",") if level.strip()]
|
||||
for level in levels:
|
||||
if level not in VAE_FAST_PATH_LEVELS:
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""Benchmark MammothModa2's Q/K RMSNorm + adjacent-pair real RoPE.
|
||||
|
||||
Example:
|
||||
|
||||
CUDA_VISIBLE_DEVICES=0 python \
|
||||
benchmarks/diffusion/benchmark_mammoth_moda2_qk_norm_rope.py \
|
||||
--tokens 512 4173 8346 --warmup 50 --iters 200
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.metadata
|
||||
import json
|
||||
import os
|
||||
import statistics
|
||||
from collections.abc import Callable
|
||||
|
||||
import torch
|
||||
|
||||
from vllm_omni.diffusion.layers.fused_qk_norm_rope import (
|
||||
_fused_cuda_supported,
|
||||
fused_qk_norm_rope,
|
||||
)
|
||||
from vllm_omni.diffusion.layers.norm import RMSNorm
|
||||
from vllm_omni.diffusion.models.mammoth_moda2.rope_real import (
|
||||
apply_real_rotary_emb,
|
||||
)
|
||||
|
||||
_HEAD_DIM = 120
|
||||
_Q_HEADS = 21
|
||||
_KV_HEADS = 7
|
||||
_EPS = 1e-5
|
||||
|
||||
|
||||
def _parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--tokens", nargs="+", type=int, default=[512, 4173, 8346])
|
||||
parser.add_argument("--warmup", type=int, default=50)
|
||||
parser.add_argument("--iters", type=int, default=200)
|
||||
parser.add_argument("--rounds", type=int, default=3)
|
||||
parser.add_argument("--attention-sequences", nargs="*", type=int, default=[])
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def _version(distribution: str) -> str | None:
|
||||
try:
|
||||
return importlib.metadata.version(distribution)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
return None
|
||||
|
||||
|
||||
def _measure(
|
||||
fn: Callable[[], object],
|
||||
warmup: int,
|
||||
iters: int,
|
||||
) -> dict[str, float]:
|
||||
for _ in range(warmup):
|
||||
fn()
|
||||
torch.accelerator.synchronize()
|
||||
|
||||
torch.accelerator.reset_peak_memory_stats()
|
||||
baseline_bytes = torch.accelerator.memory_allocated()
|
||||
samples = []
|
||||
for _ in range(iters):
|
||||
start = torch.cuda.Event(enable_timing=True)
|
||||
end = torch.cuda.Event(enable_timing=True)
|
||||
start.record()
|
||||
fn()
|
||||
end.record()
|
||||
end.synchronize()
|
||||
samples.append(float(start.elapsed_time(end)))
|
||||
|
||||
ordered = sorted(samples)
|
||||
return {
|
||||
"median_ms": statistics.median(ordered),
|
||||
"mean_ms": statistics.mean(ordered),
|
||||
"stddev_ms": statistics.pstdev(ordered),
|
||||
"min_ms": ordered[0],
|
||||
"max_ms": ordered[-1],
|
||||
"p90_ms": ordered[min(len(ordered) - 1, int(len(ordered) * 0.9))],
|
||||
"extra_peak_mib": (torch.accelerator.max_memory_allocated() - baseline_bytes) / 2**20,
|
||||
}
|
||||
|
||||
|
||||
@torch.inference_mode()
|
||||
def _run_shape(tokens: int, warmup: int, iters: int, *, fused_first: bool = False) -> dict[str, object]:
|
||||
dtype = torch.bfloat16
|
||||
device = torch.device("cuda")
|
||||
torch.manual_seed(42)
|
||||
query = torch.randn(tokens, _Q_HEADS, _HEAD_DIM, device=device, dtype=dtype)
|
||||
key = torch.randn(tokens, _KV_HEADS, _HEAD_DIM, device=device, dtype=dtype)
|
||||
norm_q = RMSNorm(_HEAD_DIM, eps=_EPS).to(device=device, dtype=dtype)
|
||||
norm_k = RMSNorm(_HEAD_DIM, eps=_EPS).to(device=device, dtype=dtype)
|
||||
norm_q.weight.normal_(mean=1.0, std=0.2)
|
||||
norm_k.weight.normal_(mean=1.0, std=0.2)
|
||||
angles = torch.rand(tokens, _HEAD_DIM // 2, device=device) * (2 * torch.pi)
|
||||
cos = angles.cos().repeat_interleave(2, dim=-1).to(dtype)
|
||||
sin = angles.sin().repeat_interleave(2, dim=-1).to(dtype)
|
||||
|
||||
def native() -> tuple[torch.Tensor, torch.Tensor]:
|
||||
q_out = apply_real_rotary_emb(norm_q(query).unsqueeze(0), cos.unsqueeze(0), sin.unsqueeze(0)).squeeze(0)
|
||||
k_out = apply_real_rotary_emb(norm_k(key).unsqueeze(0), cos.unsqueeze(0), sin.unsqueeze(0)).squeeze(0)
|
||||
return q_out, k_out
|
||||
|
||||
def fused() -> tuple[torch.Tensor, torch.Tensor]:
|
||||
rope_table = torch.cat((cos[..., 0::2], sin[..., 0::2]), dim=-1)
|
||||
return fused_qk_norm_rope(
|
||||
query,
|
||||
key,
|
||||
norm_q.weight,
|
||||
norm_k.weight,
|
||||
rope_table,
|
||||
_EPS,
|
||||
head_dim=_HEAD_DIM,
|
||||
rotary_dim=_HEAD_DIM,
|
||||
interleaved=True,
|
||||
)
|
||||
|
||||
expected_q, expected_k = native()
|
||||
actual_q, actual_k = fused()
|
||||
errors = {
|
||||
"q_max_abs": (actual_q.float() - expected_q.float()).abs().max().item(),
|
||||
"q_mean_abs": (actual_q.float() - expected_q.float()).abs().mean().item(),
|
||||
"k_max_abs": (actual_k.float() - expected_k.float()).abs().max().item(),
|
||||
"k_mean_abs": (actual_k.float() - expected_k.float()).abs().mean().item(),
|
||||
}
|
||||
torch.testing.assert_close(actual_q, expected_q, atol=0.0625, rtol=0.02)
|
||||
torch.testing.assert_close(actual_k, expected_k, atol=0.0625, rtol=0.02)
|
||||
|
||||
functions = {"native": native, "fused": fused}
|
||||
order = ("fused", "native") if fused_first else ("native", "fused")
|
||||
measured = {name: _measure(functions[name], warmup, iters) for name in order}
|
||||
native_stats, fused_stats = measured["native"], measured["fused"]
|
||||
return {
|
||||
"tokens": tokens,
|
||||
"native": native_stats,
|
||||
"fused_including_rope_pack": fused_stats,
|
||||
"median_speedup": native_stats["median_ms"] / fused_stats["median_ms"],
|
||||
"errors": errors,
|
||||
}
|
||||
|
||||
|
||||
@torch.inference_mode()
|
||||
def _run_attention(seq: int, warmup: int, iters: int, *, fused_first: bool = False) -> dict[str, object]:
|
||||
from vllm_omni.diffusion.models.mammoth_moda2.mammothmoda2_dit_model import TransformerBlock
|
||||
|
||||
torch.manual_seed(42)
|
||||
block = (
|
||||
TransformerBlock(
|
||||
2520,
|
||||
_Q_HEADS,
|
||||
_KV_HEADS,
|
||||
multiple_of=256,
|
||||
ffn_dim_multiplier=1.0,
|
||||
norm_eps=_EPS,
|
||||
rope_repeats_pairs=True,
|
||||
)
|
||||
.to(device="cuda", dtype=torch.bfloat16)
|
||||
.eval()
|
||||
)
|
||||
block.attn.norm_q.weight.normal_(1.0, 0.2)
|
||||
block.attn.norm_k.weight.normal_(1.0, 0.2)
|
||||
hidden = torch.randn(2, seq, 2520, device="cuda", dtype=torch.bfloat16)
|
||||
mask = torch.ones(2, seq, device="cuda", dtype=torch.bool)
|
||||
mask[0, seq - min(300, seq // 2) :] = False
|
||||
mask[1, seq - min(17, seq // 2) :] = False
|
||||
angles = torch.rand(2, seq, _HEAD_DIM // 2, device="cuda") * (2 * torch.pi)
|
||||
# The production producer supplies FP32 tables; each example has its own positions.
|
||||
rotary = angles.cos().repeat_interleave(2, dim=-1), angles.sin().repeat_interleave(2, dim=-1)
|
||||
env = "VLLM_OMNI_FUSED_QK_NORM_ROPE_MIN_TOKENS"
|
||||
previous = os.environ.get(env)
|
||||
|
||||
def run():
|
||||
return block.attn(hidden, hidden, attention_mask=mask, image_rotary_emb=rotary)
|
||||
|
||||
try:
|
||||
outputs = {}
|
||||
timings = {}
|
||||
order = ("fused", "native") if fused_first else ("native", "fused")
|
||||
for name in order:
|
||||
os.environ[env] = "0" if name == "fused" else str(2 * seq + 1)
|
||||
outputs[name] = run()
|
||||
timings[name] = _measure(run, warmup, iters)
|
||||
diff = (outputs["native"].float() - outputs["fused"].float()).abs()[mask]
|
||||
max_diff, mean_diff = diff.max().item(), diff.mean().item()
|
||||
assert max_diff < 2e-2 and mean_diff < 1e-3, (max_diff, mean_diff)
|
||||
assert torch.count_nonzero(outputs["fused"][~mask]) == 0
|
||||
return {
|
||||
"sequence": seq,
|
||||
"batch": 2,
|
||||
"backend": block.attn.omni_attn.attn_backend.__name__,
|
||||
"native": timings["native"],
|
||||
"fused": timings["fused"],
|
||||
"median_speedup": timings["native"]["median_ms"] / timings["fused"]["median_ms"],
|
||||
"max_abs_diff": max_diff,
|
||||
"mean_abs_diff": mean_diff,
|
||||
}
|
||||
finally:
|
||||
if previous is None:
|
||||
os.environ.pop(env, None)
|
||||
else:
|
||||
os.environ[env] = previous
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = _parse_args()
|
||||
if (
|
||||
args.rounds < 1
|
||||
or args.iters < 1
|
||||
or args.warmup < 0
|
||||
or any(value < 1 for value in [*args.tokens, *args.attention_sequences])
|
||||
):
|
||||
raise ValueError("rounds, iters and sequence lengths must be positive; warmup must be nonnegative")
|
||||
if not torch.cuda.is_available():
|
||||
raise RuntimeError("This benchmark requires CUDA")
|
||||
|
||||
probe_q = torch.empty(1, _Q_HEADS, _HEAD_DIM, device="cuda", dtype=torch.bfloat16)
|
||||
probe_k = torch.empty(1, _KV_HEADS, _HEAD_DIM, device="cuda", dtype=torch.bfloat16)
|
||||
if not _fused_cuda_supported(probe_q, probe_k, _HEAD_DIM, _HEAD_DIM, interleaved=True):
|
||||
raise RuntimeError("The fused CUDA QK norm/RoPE path is unavailable")
|
||||
|
||||
result = {
|
||||
"environment": {
|
||||
"gpu": torch.cuda.get_device_name(),
|
||||
"torch": torch.__version__,
|
||||
"cuda": torch.version.cuda,
|
||||
"triton": _version("triton"),
|
||||
"vllm": _version("vllm"),
|
||||
"vllm_omni": _version("vllm-omni"),
|
||||
},
|
||||
"config": {
|
||||
"dtype": "bfloat16",
|
||||
"q_heads": _Q_HEADS,
|
||||
"kv_heads": _KV_HEADS,
|
||||
"head_dim": _HEAD_DIM,
|
||||
"warmup": args.warmup,
|
||||
"iters": args.iters,
|
||||
"rounds": args.rounds,
|
||||
"inference_mode": True,
|
||||
"alternating_order": True,
|
||||
"norm_weights": "normal(mean=1.0, std=0.2)",
|
||||
},
|
||||
"results": [
|
||||
{"round": round_id, **_run_shape(tokens, args.warmup, args.iters, fused_first=bool(round_id % 2))}
|
||||
for round_id in range(args.rounds)
|
||||
for tokens in args.tokens
|
||||
],
|
||||
"attention_results": [
|
||||
{"round": round_id, **_run_attention(seq, args.warmup, args.iters, fused_first=bool(round_id % 2))}
|
||||
for round_id in range(args.rounds)
|
||||
for seq in args.attention_sequences
|
||||
],
|
||||
}
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,209 @@
|
||||
# MammothModa2-Preview Serving Performance Dashboard
|
||||
|
||||
This document describes how to deploy and benchmark **bytedance-research/MammothModa2-Preview** (T2I) using vLLM-Omni. It includes service startup configuration, benchmark methodology, dataset settings, and performance results.
|
||||
|
||||
MammothModa2 is a two-stage AR→DiT text-to-image pipeline: stage 0 is an autoregressive LLM that encodes the prompt into a visual-token grid, and stage 1 is a generation-LLM DiT that renders the final image (`final_output_type: image`). Unlike single-stage diffusion pipelines, `/v1/images/generations` requests traverse both stages, and the DiT stage ran with `max_num_seqs: 1` in the then-default deploy config, so concurrent requests serialized on the DiT stage while the AR stage batched (request-level DiT batching landed later in #7476).
|
||||
|
||||
> **Legacy-topology baseline**: all numbers in this dashboard were measured on the legacy topology, where the MammothModa2 DiT stage runs as an LLM-generation stage (`stage_type: llm`) built from `VllmConfig`, served through the LLM-typed image-stage compatibility path. #7134 migrates this pipeline to the shared diffusion runtime (DiT registered as a standard `DIFFUSION` stage built from `OmniDiffusionConfig`). These results should be treated as the pre-migration baseline and are expected to be rerun after #7134 lands; they are not directly comparable with post-migration numbers.
|
||||
|
||||
---
|
||||
|
||||
# 1. Overview
|
||||
|
||||
This document covers:
|
||||
|
||||
* Service launch configuration
|
||||
* Benchmark scripts and usage
|
||||
* Dataset and workload settings
|
||||
* Performance measurement results
|
||||
* Reproducibility guidelines
|
||||
|
||||
---
|
||||
|
||||
# 2. Test Environment
|
||||
|
||||
| Component | Specification |
|
||||
|------------|----------------|
|
||||
| GPU | NVIDIA A800-SXM4-80GB (1x) |
|
||||
| CPU | 18 cores |
|
||||
| Model weights | `bytedance-research/MammothModa2-Preview` |
|
||||
| Deploy config | `vllm_omni/deploy/mammoth_moda2.yaml` (bundled) |
|
||||
| Stage 0 (AR) | `gpu_memory_utilization: 0.5`, `enforce_eager: true`, `max_num_seqs: 100` |
|
||||
| Stage 1 (DiT) | `gpu_memory_utilization: 0.3`, `enforce_eager: true`, `max_num_seqs: 1` |
|
||||
| Sampling params (per request) | `text_guidance_scale: 9.0`, `cfg_range: [0.0, 1.0]`, `num_inference_steps: 50` sent via `--extra-body` / `--num-inference-steps` |
|
||||
|
||||
---
|
||||
|
||||
# 3. Service Launch Configuration
|
||||
|
||||
## 3.1 Basic Serving Command
|
||||
|
||||
```bash
|
||||
vllm-omni serve bytedance-research/MammothModa2-Preview --omni \
|
||||
--port 8091 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
When serving from a local weights directory (no `model_index.json`), pass the bundled deploy config explicitly so the `mammoth_moda2` pipeline is selected:
|
||||
|
||||
```bash
|
||||
vllm-omni serve /path/to/MammothModa2-Preview --omni \
|
||||
--port 8091 \
|
||||
--trust-remote-code \
|
||||
--deploy-config vllm_omni/deploy/mammoth_moda2.yaml
|
||||
```
|
||||
|
||||
## 3.2 Key Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `--deploy-config` | Path to the deploy YAML; defaults to the bundled `mammoth_moda2.yaml` for the HF checkpoint |
|
||||
| `size` (request) | `WxH`, e.g. `1024x1024`; routed to the AR grid and the DiT `target_w/target_h` |
|
||||
| `num_inference_steps` (request) | DiT sampling steps (default 50) |
|
||||
| `guidance_scale` (request) | Text guidance scale (default 9.0) |
|
||||
|
||||
Note: the benchmark script's `--warmup-num-inference-steps` shortcut is not usable for this pipeline — a reduced step count truncates the AR stage's visual-token output and crashes the DiT stage. Use `--warmup-requests 0` and warm up with a real request instead.
|
||||
|
||||
---
|
||||
|
||||
# 4. Benchmark Script
|
||||
|
||||
## 4.1 Benchmark Entry
|
||||
|
||||
```bash
|
||||
python benchmarks/diffusion/diffusion_benchmark_serving.py \
|
||||
--base-url http://localhost:8091 \
|
||||
--model bytedance-research/MammothModa2-Preview \
|
||||
--endpoint /v1/images/generations \
|
||||
--task t2i \
|
||||
--dataset random \
|
||||
--num-prompts 8 \
|
||||
--width <W> --height <H> \
|
||||
--num-inference-steps <S> \
|
||||
--extra-body '{"text_guidance_scale": 9.0, "cfg_range": [0.0, 1.0]}' \
|
||||
--max-concurrency <C> \
|
||||
--warmup-requests 0 \
|
||||
--seed 142 \
|
||||
--output-file <OUT>.json
|
||||
```
|
||||
|
||||
## 4.2 Dataset
|
||||
|
||||
`random` synthetic T2I prompts, fixed seed 142, 8 prompts per configuration (4 for 2048x2048 due to per-request latency).
|
||||
|
||||
---
|
||||
|
||||
# 5. Performance Results
|
||||
|
||||
All requests succeeded (0 failures across the sweep). Latency is end-to-end per request (AR + DiT + VAE decode + base64 response).
|
||||
|
||||
> Note: these are legacy-topology baseline results (see the callout in the overview); rerun after the #7134 diffusion-runtime migration before comparing across topologies.
|
||||
|
||||
## 5.1 Latency
|
||||
|
||||
| Resolution | Steps | Concurrency | Mean (s) | P50 (s) | P95 (s) | P99 (s) |
|
||||
|------------|-------|-------------|----------|---------|---------|---------|
|
||||
| 512x512 | 20 | 1 | 21.8 | 21.7 | 22.1 | 22.2 |
|
||||
| 512x512 | 20 | 2 | 24.8 | 25.2 | 25.6 | 25.8 |
|
||||
| 512x512 | 20 | 4 | 28.5 | 29.2 | 31.1 | 31.8 |
|
||||
| 1024x1024 | 50 | 1 | 96.3 | 95.8 | 99.8 | 100.8 |
|
||||
| 1024x1024 | 50 | 2 | 121.5 | 125.4 | 127.9 | 128.5 |
|
||||
| 1024x1024 | 50 | 4 | 150.9 | 157.6 | 178.6 | 186.3 |
|
||||
| 2048x2048 | 50 | 1 | 271.9 | 271.6 | 273.6 | 273.8 |
|
||||
|
||||
## 5.2 Throughput
|
||||
|
||||
| Resolution | Steps | Concurrency | Throughput (img/s) |
|
||||
|------------|-------|-------------|--------------------|
|
||||
| 512x512 | 20 | 1 | 0.0459 |
|
||||
| 512x512 | 20 | 2 | 0.0798 |
|
||||
| 512x512 | 20 | 4 | 0.1329 |
|
||||
| 1024x1024 | 50 | 1 | 0.0104 |
|
||||
| 1024x1024 | 50 | 2 | 0.0162 |
|
||||
| 1024x1024 | 50 | 4 | 0.0238 |
|
||||
| 2048x2048 | 50 | 1 | 0.0037 |
|
||||
|
||||
## 5.3 Stage Breakdown (concurrency 1, from server logs)
|
||||
|
||||
Per-stage wall time from the server's `[OmniTiming]` records across the §5.1/§5.4/§5.5 sweeps (n = 18/9/5 per row; at concurrency 1 stage wall time equals `stage_gen_time_ms` in the engine stats table):
|
||||
|
||||
| Resolution | Steps | AR stage (s) | DiT stage (s) | E2E (s) | AR share | AR output tokens | AR ms/token |
|
||||
|------------|-------|--------------|---------------|---------|----------|------------------|-------------|
|
||||
| 512x512 | 20 | 20.3 | 1.7 | 22.0 | 92% | 1,057 | 19.2 |
|
||||
| 1024x1024 | 50 | 78.9 | 17.2 | 96.1 | 82% | 4,161 | 19.0 |
|
||||
| 2048x2048 | 50 | 156.3 | 114.8 | 271.2 | 58% | 8,154 | 19.2 |
|
||||
|
||||
Stage 0 is the AR prompt encoder (TTFT 82 ms at 1024x1024); stage 1 is the DiT renderer. Three observations:
|
||||
|
||||
1. **AR per-token cost is constant (~19 ms/token) across resolutions**, so AR time is fully predictable: the AR stage decodes a fixed visual grid whose length scales with resolution (1,057 → 4,161 → 8,154 tokens), giving `AR ≈ tokens × 19 ms`. This is also why §5.4's step sweep is sub-linear — the AR token count is step-invariant.
|
||||
2. **DiT time scales super-linearly with pixels**: a 16x pixel increase (512 → 2048) multiplies DiT time by 67 (1.7 → 114.8 s).
|
||||
3. **The dominant stage flips with resolution**: AR is 92% of E2E at 512x512 (DiT optimization is nearly pointless there) but 58% at 2048x2048, where both stages matter.
|
||||
|
||||
At concurrency > 1, per-stage wall times additionally include intra-stage queueing (each stage serializes at `max_num_seqs: 1`), so the c2/c4 rows in §5.1 cannot be decomposed additively from this table.
|
||||
|
||||
## 5.4 Inference-Step Sensitivity (1024x1024 / 512x512, concurrency 1)
|
||||
|
||||
Steps 25 at 1024x1024 (half the default 50) and steps 25 at 512x512, same protocol as §5.1:
|
||||
|
||||
| Resolution | Steps | Concurrency | Mean (s) | P50 (s) | P95 (s) | P99 (s) |
|
||||
|------------|-------|-------------|----------|---------|---------|---------|
|
||||
| 1024x1024 | 25 | 1 | 85.2 | 85.3 | 87.8 | 88.7 |
|
||||
| 1024x1024 | 25 | 4 | 113.7 | 115.8 | 126.1 | 129.4 |
|
||||
| 512x512 | 25 | 1 | 21.9 | 21.8 | 22.2 | 22.3 |
|
||||
|
||||
Halving DiT steps does not halve end-to-end latency (96.3 → 85.2 s at 1024): the AR stage emits a fixed count of visual tokens (4,161 at 1024x1024) regardless of step count, so its ~79 s contribution (§5.3) is step-invariant; only the DiT sampling time scales with steps. At 512x512, steps 20 → 25 adds ~0.1 s per step (21.8 → 21.9 s).
|
||||
|
||||
## 5.5 Prompt-Length Sensitivity (long vs short prompts)
|
||||
|
||||
A ~430-word long prompt (repeated via `--random-request-config '[{"weight":1,"prompt":"..."}]'`) against the default short synthetic prompt, same seed and settings:
|
||||
|
||||
| Resolution | Steps | Concurrency | Prompt | Mean (s) | P99 (s) | Throughput (img/s) |
|
||||
|------------|-------|-------------|--------|----------|---------|---------------------|
|
||||
| 1024x1024 | 50 | 1 | short | 96.3 | 100.8 | 0.0104 |
|
||||
| 1024x1024 | 50 | 1 | long | 93.6 | 94.4 | 0.0107 |
|
||||
| 1024x1024 | 50 | 4 | short | 150.9 | 186.3 | 0.0238 |
|
||||
| 1024x1024 | 50 | 4 | long | 141.7 | 174.2 | 0.0255 |
|
||||
| 512x512 | 20 | 1 | short | 21.8 | 22.2 | 0.0459 |
|
||||
| 512x512 | 20 | 1 | long | 22.2 | 22.5 | 0.0450 |
|
||||
|
||||
Prompt length has no material effect on latency (the long-prompt rows are within run-to-run variance and even slightly faster with tighter P99 tails). This confirms the AR stage's cost is dominated by decoding the fixed 4,161-token visual grid, not by prompt encoding: the KV prefill of a few hundred extra text tokens is negligible against 4,161 decode steps.
|
||||
|
||||
## 5.6 Peak GPU Memory (per-stage process, nvidia-smi polling at 1 Hz)
|
||||
|
||||
Sampled during the §5.4/§5.5 sweeps (stage processes identified by PID; both stages share the single A800):
|
||||
|
||||
| Configuration (worst case) | stage0_ar (MB) | stage1_dit (MB) |
|
||||
|----------------------------|----------------|-----------------|
|
||||
| 1024x1024, 50 steps, c4 | 39,290 | 11,104 |
|
||||
| all other configs | 39,194 | 11,102–11,104 |
|
||||
|
||||
Memory is essentially flat across the sweep: the AR stage grows by only ~96 MB at concurrency 4 and the DiT stage by 2 MB, because both stages pre-allocate from `gpu_memory_utilization` budgets (0.5 / 0.3) at startup. Combined steady-state usage ≈ 50.4 GiB of the 80 GiB device.
|
||||
|
||||
## 5.7 DiT-Stage Component Attribution (pipeline profiler)
|
||||
|
||||
With `enable_diffusion_pipeline_profiler: true` in the stage-1 deploy config (`MammothModa2DiTPipeline` targets: `gen_transformer.forward`, `gen_vae.decode`, `gen_image_condition_refiner.forward`), three warm 1024x1024 / 50-step requests (profiler adds a `synchronize` per call, so timings are indicative, not throughput-grade):
|
||||
|
||||
| Component | Per request (s) | Share of DiT pipeline |
|
||||
|-----------|-----------------|-----------------------|
|
||||
| `gen_transformer.forward` (100 calls: 50 steps x 2 passes) | 14.01–14.07 | 98.6% |
|
||||
| `gen_vae.decode` (1 call) | 0.148 | 1.0% |
|
||||
| `gen_image_condition_refiner.forward` (1 call) | 0.005 | 0.03% |
|
||||
| whole `MammothModa2DiTPipeline.forward` | 14.18–14.27 | — |
|
||||
|
||||
The DiT latency is ~99% transformer sampling; VAE decode costs only ~0.15 s per image at 1024x1024 and is not a bottleneck. Per-step transformer time is ~140 ms (two forward passes per step: ~2.8 s nominal x CFG), consistent with §5.3's 17.2 s image-TTFT figure once base64 encoding and stage handoff are included.
|
||||
|
||||
## 5.8 Endpoint Fix Verification
|
||||
|
||||
Before the serving fix, `/v1/images/generations` returned HTTP 503 ("No diffusion stage found in multi-stage pipeline") because the pipeline's image-output stage is a generation-LLM DiT rather than a `diffusion`-typed stage. After the fix all requests return HTTP 200 with a valid base64 image (verified across every sweep configuration above).
|
||||
|
||||
---
|
||||
|
||||
# 6. Reproducibility
|
||||
|
||||
1. Start the server as in §3.1 and wait for `Application startup complete`.
|
||||
2. Warm up with one real request (any size) — do not use reduced-step warmups.
|
||||
3. Run the sweep commands from §4.1 for each row in §5.
|
||||
4. Server-side per-stage timings are printed in the engine stats table (`stage_gen_time_ms`, `output_unit_count`) at `--log-stats` level info.
|
||||
5. New rows use the same seed (`--seed 142`), `--num-prompts 8`, and `--warmup-requests 0` protocol as §4.1; when saving results with `--output-file`, name them `<res>_s<steps>_c<conc>[_long].json` to keep runs comparable (steps-25, long-prompt, peak-memory and profiler runs).
|
||||
6. To reproduce §5.6 peak-memory numbers, poll `nvidia-smi --query-gpu=memory.used --format=csv -l 1` per stage PID while a sweep is in flight (e.g. `nvidia-smi pmon` / `--query-compute-apps`).
|
||||
7. To reproduce §5.7, copy the bundled deploy config, set `enable_diffusion_pipeline_profiler: true` on the stage-1 entry, restart, and grep `[DiffusionPipelineProfiler]` lines from the server log; timings include a `torch.cuda.synchronize` per call.
|
||||
+10
-7
@@ -1,10 +1,7 @@
|
||||
# Target vLLM version: v0.30.0 (tag @ ced6857afa0e).
|
||||
# Use the matching release image and its coherent CUDA/PyTorch/FlashInfer
|
||||
# runtime. Do not reinstall a commit wheel or repair dependencies on top of
|
||||
# an older base image at a release boundary. For a future unreleased target,
|
||||
# restore an explicit wheel pin and validate its complete runtime stack.
|
||||
# Use the official release image with its matching CUDA/PyTorch stack.
|
||||
ARG VLLM_VERSION=0.31.0
|
||||
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
|
||||
ARG VLLM_BASE_TAG=v0.30.0
|
||||
ARG VLLM_BASE_TAG=v${VLLM_VERSION}
|
||||
|
||||
# We use registry-based layer caching for the dependencies,
|
||||
# where the tag is determined based on the hash of the contents
|
||||
@@ -12,6 +9,7 @@ ARG VLLM_BASE_TAG=v0.30.0
|
||||
# everything except the actual project dir, since that should not
|
||||
# change much across branches.
|
||||
FROM ${VLLM_BASE_IMAGE}:${VLLM_BASE_TAG} AS deps
|
||||
ARG VLLM_VERSION
|
||||
# Keep APP_DIR outside /workspace so k8s Buildkite workspace mounts cannot
|
||||
# shadow the editable install target (#5364). Docker WORKDIR creates /opt if
|
||||
# needed.
|
||||
@@ -36,12 +34,17 @@ RUN apt-get update && \
|
||||
# for CI
|
||||
COPY pyproject.toml setup.py ./
|
||||
COPY requirements/ requirements/
|
||||
ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0
|
||||
# Scope the override to Omni; dependency source builds need their own versions.
|
||||
ENV VLLM_OMNI_VERSION_OVERRIDE=0.0.0
|
||||
# Editable install: deps + project metadata are cached in this layer, while
|
||||
# the package itself is linked to APP_DIR (populated later by COPY --link).
|
||||
# Do NOT use a non-editable install here — that copies an empty stub into
|
||||
# site-packages and breaks ``import vllm_omni.entrypoints``.
|
||||
RUN uv pip install --system -e ".[dev]"
|
||||
# Fail the image build if dependency resolution replaced the selected vLLM.
|
||||
# The CPU builder has no NVIDIA driver; CUDA imports are checked at the start
|
||||
# of each GPU test job by .buildkite/cuda/scripts/check_vllm_runtime.py.
|
||||
RUN python3 -c "import vllm; assert vllm.__version__ == '${VLLM_VERSION}', vllm.__version__"
|
||||
|
||||
# The final stage just copies the working dir on top of the
|
||||
# dependencies image. This uses --link so that we can skip
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# NOTE: this is largely taken from vLLM
|
||||
|
||||
# Exclude .git for CI builds — version is set via SETUPTOOLS_SCM_PRETEND_VERSION
|
||||
# Exclude .git for CI builds — version is set via VLLM_OMNI_VERSION_OVERRIDE
|
||||
.git
|
||||
.venv
|
||||
/build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BASE_IMAGE=vllm/vllm-openai:v0.30.0
|
||||
ARG BASE_IMAGE=vllm/vllm-openai:v0.31.0
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
ARG COMMON_WORKDIR=/app
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
||||
ARG VLLM_ASCEND_VERSION=v0.30.0
|
||||
ARG VLLM_ASCEND_VERSION=v0.31.0
|
||||
ARG VLLM_ASCEND_DEVICE_SUFFIX=
|
||||
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_VERSION}${VLLM_ASCEND_DEVICE_SUFFIX}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
||||
ARG VLLM_ASCEND_VERSION=v0.30.0
|
||||
ARG VLLM_ASCEND_VERSION=v0.31.0
|
||||
ARG VLLM_ASCEND_DEVICE_SUFFIX=
|
||||
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_VERSION}${VLLM_ASCEND_DEVICE_SUFFIX}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Keep the ROCm runtime aligned with the vLLM release targeted by
|
||||
# docker/Dockerfile.ci. A stale base image can build successfully while failing
|
||||
# at runtime when vLLM-Omni imports APIs added by the newer vLLM release.
|
||||
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.30.0
|
||||
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.31.0
|
||||
|
||||
# Keep the dependency-heavy portion independent from the source tree. The
|
||||
# registry cache is keyed on these inputs, while the final source copy can be
|
||||
@@ -12,11 +12,11 @@ FROM ${BASE_IMAGE} AS deps
|
||||
# BASE_IMAGE pins the default prebuilt runtime when USE_NIGHTLY_BUILD=0.
|
||||
# VLLM_VERSION_OR_COMMIT_HASH is consumed only by the opt-in source rebuild when
|
||||
# USE_NIGHTLY_BUILD=1. Keep both aligned with docker/Dockerfile.ci's
|
||||
# VLLM_BASE_TAG. This is enforced by tests/buildkite/test_rocm_dockerfile.py. An
|
||||
# release version. This is enforced by tests/buildkite/test_rocm_dockerfile.py. An
|
||||
# intentional custom source pin must update that regression in the same change,
|
||||
# and should return to the stable release as soon as one is available.
|
||||
ARG USE_NIGHTLY_BUILD=0
|
||||
ARG VLLM_VERSION_OR_COMMIT_HASH=v0.30.0
|
||||
ARG VLLM_VERSION_OR_COMMIT_HASH=v0.31.0
|
||||
ARG ARG_PYTORCH_ROCM_ARCH
|
||||
ENV PYTORCH_ROCM_ARCH=${ARG_PYTORCH_ROCM_ARCH:-${PYTORCH_ROCM_ARCH}}
|
||||
|
||||
@@ -75,7 +75,8 @@ WORKDIR ${COMMON_WORKDIR}/vllm-omni
|
||||
RUN mkdir -p vllm_omni && touch vllm_omni/__init__.py
|
||||
COPY pyproject.toml setup.py ./
|
||||
COPY requirements/ requirements/
|
||||
ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0
|
||||
# Scope the override to Omni; dependency source builds need their own versions.
|
||||
ENV VLLM_OMNI_VERSION_OVERRIDE=0.0.0
|
||||
|
||||
RUN uv pip install --python "$(python3 -c 'import sys; print(sys.executable)')" \
|
||||
--no-cache-dir -e ".[dev]" --no-build-isolation && \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Target vLLM version: v0.30.0. Keep VLLM_VERSION aligned with the target vLLM
|
||||
# Target vLLM version: v0.31.0. Keep VLLM_VERSION aligned with the target vLLM
|
||||
# release on every rebase.
|
||||
#
|
||||
# This file layers vLLM-Omni on top of the published upstream XPU image, which
|
||||
@@ -18,7 +18,7 @@
|
||||
#
|
||||
# .buildkite/intel/scripts/run-xpu-test.sh automates that pull-then-build fallback.
|
||||
ARG VLLM_BASE_IMAGE=vllm/vllm-openai-xpu
|
||||
ARG VLLM_VERSION=v0.30.0
|
||||
ARG VLLM_VERSION=v0.31.0
|
||||
ARG VLLM_BASE=${VLLM_BASE_IMAGE}:${VLLM_VERSION}
|
||||
|
||||
FROM ${VLLM_BASE} AS vllm-omni
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This section lists the most common options for running vLLM-Omni.
|
||||
|
||||
For options within a vLLM Engine, please refer to the [vLLM 0.30 configuration guide](https://docs.vllm.ai/en/v0.30.0/configuration/index.html).
|
||||
For options within a vLLM Engine, please refer to the [vLLM 0.31 configuration guide](https://docs.vllm.ai/en/v0.31.0/configuration/index.html).
|
||||
|
||||
Each model defines fixed topology in a registered `PipelineConfig` and runtime
|
||||
overrides in a deploy YAML.
|
||||
|
||||
@@ -130,7 +130,7 @@ settings.
|
||||
|
||||
| Name | Type and default | Applies to and read time | Precedence and invalid values | Lifecycle |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `VLLM_OMNI_FUSED_QK_NORM_ROPE_MIN_TOKENS` | Non-negative integer; unset or empty means each consumer's own default (Boogu-Image: `2048`) | Token gate of the shared `fused_qk_norm_rope` op: a rotary table is packed for the fused kernel only when it spans at least this many positions (`B*S`); read at forward time | Environment-only. `0` = always fuse; a very large value disables the fused path. Any other value raises `ValueError` on the first forward. The crossover is host-dependent; re-benchmark before overriding. | Experimental performance control |
|
||||
| `VLLM_OMNI_FUSED_QK_NORM_ROPE_MIN_TOKENS` | Non-negative integer; unset or empty means each consumer's own default (Boogu-Image: `2048`; MammothModa2: `0`) | Token gate of the shared `fused_qk_norm_rope` op: a rotary table is packed for the fused kernel only when it spans at least this many positions (`B*S`); read at forward time | Environment-only. `0` = always fuse; a very large value disables the fused path. Any other value raises `ValueError` on the first forward. The crossover is host-dependent; re-benchmark before overriding. | Experimental performance control |
|
||||
| `VLLM_OMNI_SKIP_NVFP4_NAN_CLAMP` | Boolean truthy spellings: `1`, `true`, `yes`, `on`; default false | ModelOpt NVFP4 compatibility patch; read when `vllm_omni.patch` imports | Environment-only escape hatch. Any other value means false. Set only to diagnose the upstream NaN-scale issue. | Diagnostic and temporary |
|
||||
| `VLLM_OMNI_USE_QUACK_FP8` | Boolean truthy spellings: `1`, `true`, `yes`, `on`; unset means hardware auto-detection | FP8 scaled matrix multiplication; evaluated when quack capability is selected | A set value overrides auto-detection. Any non-truthy value forces quack off. If quack cannot load, vLLM-Omni warns and falls back to FlashInfer. | Experimental performance control |
|
||||
|
||||
@@ -186,7 +186,7 @@ their keys only.
|
||||
## Inherited vLLM variables
|
||||
|
||||
vLLM-Omni also reads variables through its aligned vLLM dependency. Refer to
|
||||
the [vLLM 0.30 environment-variable reference](https://docs.vllm.ai/en/v0.30.0/configuration/env_vars.html)
|
||||
the [vLLM 0.31 environment-variable reference](https://docs.vllm.ai/en/v0.31.0/configuration/env_vars.html)
|
||||
for their definitions. This includes vLLM launch, cache, logging, plugin, ROCm,
|
||||
XPU, ModelScope, and FlashInfer workspace settings.
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Common `StagePipelineConfig` fields include:
|
||||
| `final_output` / `final_output_type` | Whether the stage produces a user-visible output and its modality. |
|
||||
| `owns_tokenizer` | Whether this stage owns the pipeline tokenizer. |
|
||||
| `model_arch`, `hf_config_name` | Stage-specific model architecture and nested HF config selector. |
|
||||
| `engine_output_type` | Runtime output representation such as `text`, `latent`, or `audio`. |
|
||||
| `engine_output_type` | Canonical runtime output type: `text`, `image`, `audio`, `latent`, or `token_ids`. |
|
||||
| `custom_process_input_func` | Processor applied to this stage's incoming payload. |
|
||||
| `custom_process_next_stage_input_func` | Processor used for full-payload handoff to the next stage. |
|
||||
| `async_chunk_process_next_stage_input_func` | Processor used for async chunk handoff. |
|
||||
@@ -45,6 +45,50 @@ To add or change topology, define and register a new pipeline variant. Use
|
||||
deploy YAML only for runtime placement, resource sizing, connectors, and other
|
||||
deployment overrides.
|
||||
|
||||
### Engine output types
|
||||
|
||||
`engine_output_type` is parsed when the LLM stage's output processor is built
|
||||
during service initialization. Invalid values fail startup before requests
|
||||
are accepted; engine replicas may already have been initialized at that point
|
||||
and are cleaned up on failure.
|
||||
|
||||
Use the exact lowercase names listed above. Combinations such as `text+audio`
|
||||
or `text,audio` remain supported, with every component canonical. Omitted or
|
||||
`None` values retain the default behavior (`TEXT` in the output processor).
|
||||
Empty strings, whitespace, case variants, and aliases are rejected.
|
||||
|
||||
For existing custom pipelines or direct callers, replace old aliases explicitly:
|
||||
|
||||
| Old value | Canonical value |
|
||||
| ------- | ------- |
|
||||
| `speech`, `wav`, `waveform` | `audio` |
|
||||
| `images`, `pixel_values`, `pixels` | `image` |
|
||||
| `latents` | `latent` |
|
||||
| `tokens` | `text` for text output, or `token_ids` for discrete IDs |
|
||||
|
||||
`token_ids` is a separate type, not a `text` alias. It describes discrete
|
||||
vocabulary or codebook indices, distinct from continuous latent representations.
|
||||
Existing models using `latent` retain their historical payload contracts.
|
||||
GLM-Image's AR stage keeps `engine_output_type="token_ids"`; generated IDs still
|
||||
travel through `cumulative_token_ids`, and source-image IDs through
|
||||
`ids.prior_image`.
|
||||
|
||||
Bare primary tensors and generic producer keys (`model_outputs` or `hidden`)
|
||||
use the declared type as their default output key, including `token_ids`.
|
||||
The normalizer does not inspect or convert their dtype. Distinct auxiliary
|
||||
outputs must use explicit field names: `model_outputs` and `hidden` both map
|
||||
to the same default key and must not represent two independent outputs.
|
||||
GLM-Image disables auxiliary hidden output for its default GPU execution.
|
||||
Some runner paths still attach it; that field is then named `token_ids` even
|
||||
though it contains hidden states. The GLM-Image bridge ignores this auxiliary
|
||||
field and reads generated IDs from `cumulative_token_ids` instead.
|
||||
|
||||
Token-ID tensors use the existing dimension-0 accumulation default, subject to
|
||||
registered per-key overrides, and remain available after DELTA emissions.
|
||||
This is separate from the existing accumulation of `cumulative_token_ids`.
|
||||
`final_output_type` and Diffusion's `output_type` are separate settings and are
|
||||
unchanged by these rules.
|
||||
|
||||
## Deploy configuration schema
|
||||
|
||||
The new deploy schema lives under `vllm_omni/deploy/` and is paired with a frozen `PipelineConfig` registered by the model's `pipeline.py`. Each deploy YAML has these top-level fields:
|
||||
@@ -78,7 +122,44 @@ explicit CLI or stage override, or the downstream vLLM engine default.
|
||||
Note: for the diffusion path, an omitted `distributed_executor_backend` selects
|
||||
`uni` on a single GPU (in-process worker, no MessageQueue / `/dev/shm` output
|
||||
segments) and `mp` when `num_gpus > 1`. Set `mp` explicitly to keep a worker
|
||||
subprocess on one GPU. `ray` / `external_launcher` are not fully supported yet.
|
||||
subprocess on one GPU. Set `ray` for Ray-managed single- or multi-node
|
||||
diffusion workers. `external_launcher` is not supported yet.
|
||||
|
||||
The diffusion `ray` backend runs one worker actor per GPU and supports workers
|
||||
spanning Ray nodes. Enable it in the deploy YAML:
|
||||
|
||||
```yaml
|
||||
distributed_executor_backend: ray
|
||||
```
|
||||
|
||||
The executor reuses Ray when already initialized; otherwise it calls
|
||||
`ray.init()`, which handles cluster discovery or local startup and respects
|
||||
`RAY_ADDRESS`. There is no diffusion-specific address setting. Ray must be
|
||||
installed on every node.
|
||||
|
||||
Ray diffusion workers receive the driver's `VLLM_*`, `OMNI_*`, `DIFFUSION_*`,
|
||||
`NCCL_*`, `TORCH_NCCL_*`, `UCX_*`, and Hugging Face environment settings,
|
||||
plus `PYTHONPATH`, `CUDA_LAUNCH_BLOCKING`, and `OMP_NUM_THREADS`.
|
||||
Explicit stage `runtime.env` entries are also forwarded and take precedence.
|
||||
Device visibility, rank/rendezvous settings, worker host addresses, and `RAY_*`
|
||||
variables are excluded so each worker retains its own assignment and identity.
|
||||
|
||||
For multiple nodes, start the cluster before launching Omni. For example,
|
||||
with head IP `10.0.0.1`:
|
||||
|
||||
```bash
|
||||
# On the head node:
|
||||
ray start --head --node-ip-address=10.0.0.1 --port=6379
|
||||
|
||||
# On each additional worker node:
|
||||
ray start --address=10.0.0.1:6379
|
||||
|
||||
# On the host launching Omni (MODEL and my_deploy.yaml are placeholders):
|
||||
RAY_ADDRESS=10.0.0.1:6379 vllm serve MODEL --omni --deploy-config my_deploy.yaml
|
||||
```
|
||||
|
||||
Omni creates actors on the resources of that cluster; it does not start Ray
|
||||
on additional machines.
|
||||
|
||||
### Stage-level runner selection
|
||||
|
||||
@@ -517,8 +598,9 @@ the intended GPU, and warm the complete pipeline before measuring performance.
|
||||
|
||||
## MOSS-TTS Local 1.5 with Model Runner V2
|
||||
|
||||
`OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5` can opt into CUDA MRV2 using
|
||||
the shared runtime introduced for Qwen3-TTS:
|
||||
`OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5` defaults to CUDA MRV2 using
|
||||
the shared runtime introduced for Qwen3-TTS. To select the C64 profile without
|
||||
MPS explicitly:
|
||||
|
||||
```bash
|
||||
vllm serve OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5 --omni \
|
||||
@@ -526,7 +608,7 @@ vllm serve OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5 --omni \
|
||||
```
|
||||
|
||||
This profile inherits the batching, codec graph buckets and 1-frame/15-frame
|
||||
chunk geometry from `moss_tts_local.yaml`, and selects V2 for both the Local
|
||||
chunk geometry from `moss_tts_local_v1.yaml`, and selects V2 for both the Local
|
||||
Talker and codec stages. The Local depth predictor exposes the MRV2 `mtp`
|
||||
capabilities while retaining its V1 `talker_mtp` implementation, sampling
|
||||
defaults and explicit request-seed handling. Codec chunks use the native data
|
||||
@@ -550,8 +632,12 @@ guarantee that 128 maximum-length prompts fit simultaneously. See the
|
||||
[MOSS recipe](gh-file:recipes/OpenMOSS/MOSS-TTS.md#local-15-mrv2-and-slot-attention)
|
||||
for activation, backend comparisons, memory requirements and benchmark commands.
|
||||
|
||||
Omitting `--deploy-config`, or selecting `moss_tts_local.yaml`, retains V1.
|
||||
NPU, XPU, ROCm and MUSA overrides also retain V1. This profile does not enable
|
||||
Omitting `--deploy-config` selects the C128 system profile on CUDA GPUs with
|
||||
at least 140 GiB memory and MPS available. Smaller GPUs or a failed memory
|
||||
query use C64 with utilization-based memory budgets; missing MPS selects
|
||||
C64 without MPS. Selecting `moss_tts_local.yaml` explicitly uses C64 with MPS.
|
||||
Use `moss_tts_local_v1.yaml` for the previous V1 deployment.
|
||||
NPU, XPU, ROCm and MUSA overrides retain V1. These profiles do not enable
|
||||
MRV2 for MOSS Delay, Realtime or Nano. Local 1.5 outputs 48 kHz stereo audio;
|
||||
set `VLLM_OMNI_BENCH_AUDIO_SAMPLE_RATE=48000` and
|
||||
`VLLM_OMNI_BENCH_AUDIO_CHANNELS=2` when benchmarking raw PCM.
|
||||
@@ -559,5 +645,5 @@ set `VLLM_OMNI_BENCH_AUDIO_SAMPLE_RATE=48000` and
|
||||
Event-driven orchestration remains independently selectable with
|
||||
`VLLM_OMNI_EVENT_DRIVEN_ORCH=0` or `1`. Keep the runner and deployment identical
|
||||
when comparing these modes. Model-runner selection does not change the
|
||||
orchestration default or enable experimental reference encoding, chunk ramps,
|
||||
generation-output draining or MPS.
|
||||
orchestration default. MPS, codec dispatch and graph settings belong to the
|
||||
deployment profile; the automatic CUDA default selects MPS when available.
|
||||
@@ -131,3 +131,24 @@ and real-kernel schema/fake checks pass. The full suite must be rerun with the
|
||||
vLLM version required by upstream. NPU/ROCm device numerics and compilation remain
|
||||
unvalidated. Pre-commit passes with the CI hook skips. Upstream PyTorch/CUTLASS
|
||||
warnings remain.
|
||||
|
||||
## TRTLLM dense execution
|
||||
|
||||
`TRTLLM_ATTN` declares `SUPPORTED` / `CUSTOM_OP` for noncausal dense BF16 on
|
||||
B200/GB200 (SM100) and B300/GB300 (SM103), head dimension 128, and equal Q/K/V
|
||||
head counts, without parallel, paged-KV, piecewise, or HSDP boundaries. Pre-construction, SAGE, skip-softmax, packed,
|
||||
and other unverified paths remain `UNMIGRATED`. Resolution and dispatch share
|
||||
metadata validation; workspace mutation is explicit in the custom-op schema.
|
||||
|
||||
CPU contract tests cover fullgraph replay and schema/fake consistency using a
|
||||
substituted dispatcher. Real-kernel validation requires Blackwell with FlashInfer:
|
||||
|
||||
```bash
|
||||
python -m pytest tests/diffusion/attention/test_trtllm_attn.py \
|
||||
-k dense_contract_fullgraph_matches_sdpa -q -rs
|
||||
```
|
||||
|
||||
These hardware tests compare eager/compiled output with FP32 SDPA and check
|
||||
schema/fake agreement. The TRTLLM and contract suite passed on Blackwell before the final architecture
|
||||
restriction was added: 61 passed, no skips (19 dependency deprecation warnings).
|
||||
The SM100/SM103 restriction is additionally covered by CPU contract tests.
|
||||
@@ -281,6 +281,10 @@ NOTE: be careful to test adequately when refactoring classes that take this styl
|
||||
| `expected_dims` | int \| None | Expected tensor rank for validation (optional) |
|
||||
| `split_output` | bool | `False`: shard **input** params; `True`: shard **output** tensors |
|
||||
| `auto_pad` | bool | Auto-pad if sequence not divisible by world_size (default: `False`) |
|
||||
| `clone_shard` | bool | Give multi-rank shards their own contiguous storage (default: `False`); single-rank inputs are unchanged |
|
||||
|
||||
With `clone_shard=True`, memory savings require callers to drop their references to the full tensor.
|
||||
Other references to the same storage also keep the full allocation alive.
|
||||
|
||||
**SequenceParallelOutput Parameters:**
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ vllm_omni/
|
||||
│ │ DUPLEX_REALTIME_CAPABILITIES, duplex_response_format
|
||||
│ ├── events.py re-export shim (DuplexEvent = RealtimeEvent) + the runner's
|
||||
│ │ epoch-filter sets DOMAIN_TERMINAL_EVENTS / MODEL_OUTPUT_EVENTS
|
||||
│ ├── delivery.py DuplexOutputBuffer (bounded output shared with the session handle)
|
||||
│ ├── realtime_events.py RealtimeProjectionState: internal event -> typed events
|
||||
│ ├── messages.py queue envelopes (Open/Close/Resume/Touch/Command/Result/Event), DuplexSessionError
|
||||
│ ├── config.py DuplexSessionConfig, DuplexCapabilities, ResponseCreateOptions
|
||||
@@ -284,7 +285,8 @@ command handle.submit(DuplexCommand)
|
||||
-> engine.submit_command_async -> DuplexSessionCommandMessage on the request queue [one-way]
|
||||
-> DuplexSessionManager.dispatch: unknown_session / input_backpressure checks, then runner mailbox
|
||||
output DuplexOrchestrator._intercept_stage_output -> runner.on_stage_output -> mailbox -> typed events
|
||||
-> output_sink (DuplexSessionEventMessage) -> DuplexOmni._route_engine_message -> handle.events()
|
||||
-> session's bounded output buffer -> handle.events(); closure and errors without a live session
|
||||
use output_sink (DuplexSessionEventMessage) -> DuplexOmni._route_engine_message
|
||||
detach DuplexOmni.detach_session(expected_lease_generation) -> touch(DETACH): engine-owned disconnect
|
||||
grace, refused for a lease newer than the one the caller held; expiry -> SessionExpired
|
||||
resume DuplexOmni.resume_session(expected_lease_generation) -> lease CAS keyed by the control id (a replay
|
||||
@@ -303,7 +305,9 @@ reap DuplexSessionManager.reaper_loop: idle TTL / disconnect grace expiry, c
|
||||
|
||||
### Concurrency and ordering model of the runner
|
||||
|
||||
Everything below runs on the orchestrator asyncio loop; there is no lock.
|
||||
The runner's session state is owned by the orchestrator asyncio loop and
|
||||
needs no lock. The output buffer shared with the caller thread uses a lock
|
||||
for queue updates and the final validity check before delivery.
|
||||
|
||||
- **Single writer per session.** `DuplexEngineSession` is mutated only by its
|
||||
runner. Inputs reach the runner through one mailbox in this order: client
|
||||
@@ -332,13 +336,15 @@ Everything below runs on the orchestrator asyncio loop; there is no lock.
|
||||
on one loop in program order, nothing can be emitted for an old epoch after
|
||||
its terminal event; `runner.emit()` still drops a terminal carrying a stale
|
||||
epoch and stamps `epoch` on every event for clients that filter
|
||||
defensively. This is a statement about order, not about latency: the engine
|
||||
output queue and each `DuplexSessionHandle` outbox are unbounded and FIFO,
|
||||
so a cancellation is delivered behind whatever audio was already emitted for
|
||||
the response it cancels. A client that must stop quickly cancels its own
|
||||
playback on `response.done` / `audio.cancelled` rather than waiting for the
|
||||
stream to drain. Bounding those buffers and letting an accepted invalidation
|
||||
skip undelivered media is left to the follow-up RFC.
|
||||
defensively. In this experimental follow-up, public output goes directly to
|
||||
one bounded buffer shared by the engine and `DuplexSessionHandle`. Accepted
|
||||
cancellation removes queued `AudioDelta` events for that response through
|
||||
the cancelled epoch. Text increments, audio/transcript completion markers,
|
||||
other responses and normal completion retain FIFO order. The consumer also
|
||||
checks an event already taken from the buffer: WebSocket delivery repeats
|
||||
that check under the connection lock, before sequencing and journaling.
|
||||
Already sequenced replay entries are unchanged; already delivered audio
|
||||
still requires the client to stop its own playback.
|
||||
- **Backpressure before the mailbox.** `DuplexSessionManager.dispatch`
|
||||
checks `max_pending_input_bytes_per_session` and reserves a pending turn for
|
||||
`Commit` before the put; a rejected command is answered with
|
||||
@@ -349,6 +355,46 @@ Everything below runs on the orchestrator asyncio loop; there is no lock.
|
||||
`ResponseDone.stage_metrics` and `metadata.vllm_omni.stage_metrics` on the
|
||||
wire keep their meaning.
|
||||
|
||||
### Experimental output limits
|
||||
|
||||
`max_pending_output_bytes_per_session` defaults to 2 MiB and
|
||||
`max_pending_output_events_per_session` to 512. The byte limit counts compact
|
||||
Realtime JSON, including base64 audio, not decoded PCM or Python object
|
||||
overhead. Audio is already base64-encoded by the producer; counting its length
|
||||
avoids serializing or decoding that large field again. Neither default has
|
||||
been tuned from a performance study.
|
||||
|
||||
Errors and response endings have an additional 64 KiB / eight-event reserve
|
||||
and retain FIFO order. One final session-closure notification has an independent
|
||||
slot, so an exhausted reserve cannot prevent closure or break delivery to
|
||||
other sessions. If that reserve also fills, an error or response-ending
|
||||
notification may be omitted; the final session-closure notification still
|
||||
has its independent slot. Oversized close details (over 4 KiB with generated identity)
|
||||
are replaced by a compact reason, preserving terminal type and identity.
|
||||
Late writes after closure are ignored; a locally ended iterator is not reopened
|
||||
by a late terminal, and a failed open closes the abandoned handle. Events other
|
||||
than errors are not forwarded once their session's buffer has been removed.
|
||||
One event held by the consumer is outside the queued budget.
|
||||
|
||||
The overflow policy fails the active response without committing its partial
|
||||
text to history and closes only the affected session. If overflow occurs while
|
||||
a completion batch is being delivered, its not-yet-queued `ResponseDone` is
|
||||
converted to failure with its identity and output preserved; any history entry
|
||||
for that response is removed. An ending already accepted by the buffer is not
|
||||
replaced or followed by a second ending. After overflow, newly
|
||||
emitted ordinary events are suppressed, including text/audio completion,
|
||||
content-part and output-item completion markers. Previously queued non-audio
|
||||
events retain FIFO order; error and failed response-ending notifications may
|
||||
use the reserve before the independent closure notification. This differs from
|
||||
accepted cancellation, which removes only matching audio. See the
|
||||
[public error and closure fields](../serving/realtime_duplex_api.md#output-limits-and-slow-consumers).
|
||||
Response-only recovery is not implemented. These limits do not bound the runner's raw stage
|
||||
output mailbox, replay journal, or client playback queues. The shared buffer
|
||||
requires the current same-process, separate-thread engine arrangement; it is
|
||||
not a cross-process delivery protocol. Direct handle iteration and WebSocket
|
||||
delivery share buffer validity, but end-to-end client behavior still needs
|
||||
independent real-model verification.
|
||||
|
||||
## Orchestrator seams
|
||||
|
||||
`OrchestratorBase` keeps the generic machinery (request handler skeleton,
|
||||
|
||||
@@ -12,11 +12,25 @@ require new end-to-end performance measurements after the mainline integration.
|
||||
Previously reported numbers do not describe this revised codec backend.
|
||||
|
||||
The generic MRv2 profile and the opt-in native V1 duplex H200 profile enable
|
||||
TF32 matmuls within Code2Wav forward/capture only; the previous process policy
|
||||
is restored afterwards. cuDNN's TF32 policy is independent. TF32 changes rounding.
|
||||
ordinary TF32 for CFM DiT GEMMs within Code2Wav forward/capture only
|
||||
(`torch.backends.cuda.matmul.allow_tf32` on dense QKV/MLP, Triton
|
||||
`input_precision="tf32"` on tiled attention). This is not compensated TF32x3.
|
||||
The previous process matmul policy is restored afterwards. cuDNN's TF32 policy
|
||||
is independent. HiFT stays IEEE FP32. TF32 changes rounding.
|
||||
Duplex keeps the mainline V1 session path. Turn results do not establish duplex
|
||||
performance or interruption correctness.
|
||||
|
||||
The shared asynchronous output snapshot and batched Talker preprocessing also
|
||||
apply to V1 when async chunking/scheduling is enabled. Default V1 behavior must
|
||||
therefore be included in end-to-end regression validation.
|
||||
|
||||
CFM graph execution defaults to the fused DiT body on CUDA when TF32 is
|
||||
allowed. NVIDIA SM80+ uses tiled TF32 attention; older CUDA devices use the
|
||||
fused body's SDPA fallback. Unsupported DiT layouts or non-FP32 attention
|
||||
caches retain the original body. Other platforms retain their existing default.
|
||||
Set `cfm_fused_body: false` in connector `extra` to restore the original body.
|
||||
Disabling TF32 with `token2wav_allow_tf32: false` or
|
||||
`MINICPMO_CODE2WAV_TF32=off` also disables the default fusion choice; an explicit
|
||||
`cfm_fused_body: true` overrides that choice and may use TF32 attention.
|
||||
Slot pooling and row-offset merging remain opt-in. Fusion changes floating-point
|
||||
rounding and does not promise identical waveforms.
|
||||
@@ -0,0 +1,61 @@
|
||||
# MiniCPM-o 4.5 input encoder CUDA graphs
|
||||
|
||||
The vision transformer and stateless audio encoder reuse vLLM's encoder graph
|
||||
manager for exact input shapes. Each encoder retains at most four graphs by
|
||||
default, and captures on the second call with the same shape and CUDA stream.
|
||||
Startup profiling can consume slots. Graphs are never evicted: once the cache
|
||||
is full, other shapes run eagerly for the lifetime of the model.
|
||||
|
||||
Use `--hf-overrides` to configure both encoders:
|
||||
|
||||
```json
|
||||
{
|
||||
"encoder_cuda_graph": true,
|
||||
"encoder_cuda_graph_max_graphs": 4,
|
||||
"encoder_cuda_graph_min_capture_calls": 2,
|
||||
"encoder_cuda_graph_min_free_bytes": 1073741824,
|
||||
"encoder_cuda_graph_share_pools": true
|
||||
}
|
||||
```
|
||||
|
||||
`max_graphs` is a nonnegative integer per encoder; zero disables capture.
|
||||
`min_capture_calls` is an integer of at least two. Increasing it reduces capture
|
||||
of short-lived shapes. Admission history is bounded to four times `max_graphs`,
|
||||
so an evicted history entry must accumulate its calls again. Increasing the
|
||||
graph cap can improve coverage but retains more GPU memory. Existing graphs
|
||||
are not replaced, avoiding repeated capture costs when traffic changes.
|
||||
|
||||
By default, graphs in the same encoder on the same device and replay stream
|
||||
share a memory pool and capture stream. Different encoders and replay streams
|
||||
remain isolated. Graph inputs and output buffers are allocated outside capture;
|
||||
only transient capture allocations share the pool. Returned outputs remain
|
||||
cloned. A late capture waits for earlier replay work and output clones before
|
||||
reusing the pool. Set `encoder_cuda_graph_share_pools` to `false` for an A/B
|
||||
comparison with a separate pool and capture stream per graph.
|
||||
|
||||
The graph count is **not a byte limit**. Sharing pools reuses intermediate
|
||||
storage but does not eliminate per-shape static input and output buffers.
|
||||
Before a new capture, the adapter checks device-free memory against
|
||||
`min_free_bytes`, a nonnegative integer (default: 1 GiB; zero disables the check).
|
||||
Below this floor the call runs eagerly, and a later call may try admission
|
||||
again. Existing graphs can still replay. This check is conservative about
|
||||
reusable allocator memory and does not predict capture allocations, reserve
|
||||
memory, or guarantee that capture fits. Size the floor for the deployment;
|
||||
disable encoder graphs when their additional memory cannot be accommodated.
|
||||
|
||||
Each adapter exposes `get_cumulative_stats()`: graph hits, total misses, hit
|
||||
rate, graph count, and separate ineligible, capacity, warmup and memory misses.
|
||||
These include calls rejected before the upstream manager, so a full cache's
|
||||
lost coverage is visible. Vision uses `vpm._encoder_graph`; audio uses
|
||||
`_audio_encoder_graph` on the model. The counters are per adapter, not global.
|
||||
|
||||
Capture failures propagate the original exception and make that adapter
|
||||
unusable. Subsequent calls raise without retrying capture or running CUDA eager
|
||||
work. Restart the worker, optionally with encoder graphs disabled. This is a
|
||||
fatal contract, not automatic recovery: a failed CUDA capture can leave the
|
||||
device context unusable. `capture_failures` records this terminal state.
|
||||
|
||||
`--enforce-eager` always disables these graphs. CPU, grad/autocast, stateful
|
||||
streaming audio and the other model-specific eager fallbacks remain unchanged.
|
||||
The adapter clones graph outputs through upstream postprocessing so retained
|
||||
embeddings survive later replays.
|
||||
@@ -47,6 +47,29 @@ adapter, not injected through unrelated scheduler state.
|
||||
|
||||
**Rule:** Workers and model runners MUST NOT implement cross-stage routing.
|
||||
|
||||
## Token-only output payloads
|
||||
|
||||
An AR stage whose downstream decoder consumes sampled tokens rather than
|
||||
hidden states can set `omni_pooler_payload_include_hidden = False`. Hidden
|
||||
states remain available for logits and sampling; prefill conditioning and
|
||||
other required multimodal outputs still cross the stage boundary.
|
||||
|
||||
A chunk processor that needs IDs on steps without a tensor payload declares
|
||||
`requires_token_updates = True`. The scheduler then enqueues nonempty sampled
|
||||
IDs while preserving final-stage exclusions and the processor's EOF handling.
|
||||
Processors without this declaration retain their tensor-payload trigger.
|
||||
|
||||
When hidden and multimodal payloads are absent, the shared runner skips empty
|
||||
per-request payload construction. Prefix-cache merges and pending model
|
||||
postprocess retain the normal path. This policy is independent of whether
|
||||
output materialization runs inline or in the background.
|
||||
|
||||
Inline construction consumes query offsets, scheduled-token counts and
|
||||
scheduler metadata before the next execution can reuse them. Background
|
||||
construction snapshots those inputs. Both paths reuse the request IDs already
|
||||
copied by upstream bookkeeping; connector collection stays on the main thread
|
||||
because it can run a tensor-parallel collective.
|
||||
|
||||
## Safe-change guide
|
||||
|
||||
Test request lifecycle, abort, cache state, and every affected worker execution
|
||||
|
||||
@@ -36,6 +36,7 @@ validation_paths:
|
||||
- tests/diffusion/test_diffusion_worker.py
|
||||
- tests/diffusion/test_multiproc_engine_concurrency.py
|
||||
- tests/diffusion/test_uniproc_executor.py
|
||||
- tests/diffusion/test_ray_executor.py
|
||||
- tests/diffusion/test_result_pump.py
|
||||
- tests/diffusion/test_async_output_worker.py
|
||||
- tests/diffusion/test_diffusion_ipc.py
|
||||
@@ -96,8 +97,10 @@ The executor then chooses how the worker runs:
|
||||
RPC.
|
||||
- **`mp`** (default when `num_gpus > 1`, or when set explicitly):
|
||||
`MultiprocDiffusionExecutor` starts one `WorkerProc` per device.
|
||||
- **`ray`** (explicit): `RayDiffusionExecutor` starts one GPU actor per worker
|
||||
across one or more Ray nodes.
|
||||
|
||||
Either way, each worker owns its device, distributed state,
|
||||
In all backends, each worker owns its device, distributed state,
|
||||
`DiffusionWorker`, `DiffusionModelRunner`, and pipeline instance.
|
||||
|
||||
```mermaid
|
||||
@@ -108,7 +111,7 @@ flowchart TB
|
||||
stageProc["StageDiffusionProc<br/>stage transport and lifecycle"]
|
||||
engine["DiffusionEngine<br/>admission and control loop"]
|
||||
scheduler["RequestScheduler or StepScheduler<br/>request state and policy"]
|
||||
executor["DiffusionExecutor<br/>uni or mp backend"]
|
||||
executor["DiffusionExecutor<br/>uni, mp, or ray backend"]
|
||||
streams["Per-request output streams"]
|
||||
|
||||
stageProc --> engine
|
||||
@@ -132,6 +135,8 @@ flowchart TB
|
||||
client -.->|"inline: direct call"| engine
|
||||
executor -->|"mp: broadcast / result queues"| workerProc
|
||||
executor -.->|"uni: in-process call"| worker
|
||||
executor -->|"ray: actor RPC"| rayActor["RayDiffusionWorkerWrapper"]
|
||||
rayActor --> worker
|
||||
streams -->|"process-backed"| stageProc
|
||||
stageProc -->|"ZMQ results"| client
|
||||
streams -.->|"inline"| client
|
||||
@@ -154,6 +159,7 @@ flowchart TB
|
||||
| `DiffusionExecutor` | Execution backend contract, worker RPC, health, shutdown | Admission and request-state transitions |
|
||||
| `UniProcDiffusionExecutor` | Single in-process worker for `num_gpus == 1`; no IPC or async output pump | Multi-GPU execution |
|
||||
| `MultiprocDiffusionExecutor` | Worker processes, shared-memory message queues, result dispatch, worker monitoring | In-process single-GPU path |
|
||||
| `RayDiffusionExecutor` | GPU actors, placement groups, remote RPC, actor monitoring | Local worker subprocesses and shared-memory IPC |
|
||||
| `WorkerProc` | One `mp` worker process's IPC loop and reply rules | Scheduling policy; unused by `uni` |
|
||||
| `DiffusionWorker` | Device/distributed setup, LoRA activation, profiling, sleep/wake, runner delegation | Request admission |
|
||||
| `DiffusionModelRunner` | Pipeline loading, request-local model state, cache/compile setup, request or step execution | Queueing and cross-stage routing |
|
||||
@@ -219,12 +225,12 @@ uses fused `execute_model_batch()` and requires the pipeline to declare
|
||||
request-batch support.
|
||||
|
||||
**Distributed layerwise offload with AllGather (DLO DP concurrency)** is a
|
||||
separate multiproc dispatch path. It activates when
|
||||
separate dispatch path supported by `mp` and `ray`. It activates when
|
||||
`data_parallel_size > 1`, `enable_distributed_layerwise_offload` is set, and
|
||||
`dlo_use_allgather` is true. The engine then sets `dp_concurrent = True` and
|
||||
raises `scheduler.max_num_running_reqs` to `dp_size`, overriding the
|
||||
`max_num_seqs` value from `initialize()`. The scheduler still emits a
|
||||
multi-request `DiffusionSchedulerOutput`; the multiproc executor routes that
|
||||
multi-request `DiffusionSchedulerOutput`; these executors route that
|
||||
wave through `execute_request()` instead of fused `execute_model_batch()`.
|
||||
|
||||
Optional admission coalescing can fill a `dp_size` wave: when
|
||||
@@ -233,7 +239,7 @@ the first schedule of a wave (under `dp_concurrent`, the stable window is
|
||||
`min(0.3s, wait/2)`). With the default `request_batch_max_wait_ms == 0`,
|
||||
there is no wait.
|
||||
|
||||
Before dispatch, `MultiprocDiffusionExecutor.execute_request()` rejects waves
|
||||
Before dispatch, both backends reject waves
|
||||
whose requests differ in sampling-parameter compatibility or `extra_args`
|
||||
(AllGather requires every DP rank to follow the same forward schedule). Each
|
||||
worker rank then picks one envelope from the wave:
|
||||
@@ -297,8 +303,9 @@ head-of-line blocking.
|
||||
| --- | --- | --- |
|
||||
| `uni` | Default for `num_gpus == 1`, or set explicitly | One in-process worker |
|
||||
| `mp` | Default for `num_gpus > 1`, or set explicitly | One worker process per device |
|
||||
| `ray` | Set explicitly | One GPU actor per worker across Ray nodes |
|
||||
|
||||
Ray and external-launcher diffusion backends are not implemented. A custom
|
||||
The external-launcher diffusion backend is not implemented. A custom
|
||||
`DiffusionExecutor` subclass or import path is also accepted.
|
||||
|
||||
### Uniproc backend
|
||||
@@ -337,11 +344,27 @@ result path and does not start those pumps.
|
||||
See [Async diffusion output](../../feature/async_diffusion_output.md) for the
|
||||
multiproc request-mode timeline.
|
||||
|
||||
### Ray backend
|
||||
|
||||
`RayDiffusionExecutor` reuses initialized Ray or calls `ray.init()` (respecting
|
||||
`RAY_ADDRESS`). It reuses the current placement group or reserves one GPU
|
||||
bundle per worker with `PACK` placement. Ray owns GPU placement; PyTorch
|
||||
owns distributed collectives through a shared TCP rendezvous. Global ranks
|
||||
are grouped by host, while each actor uses local device 0.
|
||||
|
||||
Request batches, step execution, and DLO DP waves use actor RPCs with
|
||||
rank-aware replies. Returned tensors move to CPU before Ray serialization;
|
||||
this backend does not use the multiproc async output pumps. Driver inference
|
||||
settings and explicit stage `runtime.env` are forwarded, excluding worker
|
||||
identity and device assignment. See [Stage configuration](../../../configuration/stage_configs.md)
|
||||
for cluster setup and environment details.
|
||||
|
||||
## Worker and runner boundary
|
||||
|
||||
On the `mp` path, `WorkerProc` receives messages and calls methods through
|
||||
`WorkerWrapperBase`. On the `uni` path, the executor owns that wrapper
|
||||
directly. In both cases, `DiffusionWorker` handles the parts tied to a device:
|
||||
directly. Ray actors also construct `WorkerWrapperBase`. In all cases,
|
||||
`DiffusionWorker` handles the parts tied to a device:
|
||||
distributed initialization, model-runner construction, LoRA activation,
|
||||
profiling, and memory sleep/wake. It delegates actual model work to
|
||||
`DiffusionModelRunner`.
|
||||
@@ -399,6 +422,10 @@ streams with an error, closes scheduler state, and shuts down the executor.
|
||||
async-output futures as failed, then clears them. Completed async outputs
|
||||
cached for later `wait_output_ready()` calls are dropped with the executor
|
||||
object.
|
||||
- **`ray`**: the executor gives actors up to five seconds to shut down, then
|
||||
kills them and removes placement groups it owns. Actor death is monitored
|
||||
even while idle; actor errors and RPC timeouts fail the group and notify the
|
||||
stage. The stage client allows subprocess cleanup before signal fallback.
|
||||
- **`uni`**: the executor shuts down the in-process worker, drops the final
|
||||
model reference, and empties the accelerator cache so a later engine can reuse
|
||||
the device.
|
||||
@@ -407,7 +434,7 @@ streams with an error, closes scheduler state, and shuts down the executor.
|
||||
|
||||
Do not continue using a worker group after a fatal collective timeout or
|
||||
unexpected worker exit. Distributed state may be incomplete. The
|
||||
multiprocess executor deliberately fails closed so the stage can restart.
|
||||
`mp` and `ray` executors deliberately fail closed so the stage can restart.
|
||||
The uniproc executor fails closed when the accelerator context itself is
|
||||
poisoned.
|
||||
|
||||
@@ -417,10 +444,11 @@ streams with an error, closes scheduler state, and shuts down the executor.
|
||||
`diffusion_model_runner_cls` configuration still wins.
|
||||
- Tests and custom engine integrations may inject a `BaseScheduler` subclass.
|
||||
`SchedulerInterface` remains only as a deprecated compatibility name.
|
||||
- `distributed_executor_backend` may be `"uni"`, `"mp"`, a custom `DiffusionExecutor`
|
||||
- `distributed_executor_backend` may be `"uni"`, `"mp"`, `"ray"`, a custom `DiffusionExecutor`
|
||||
subclass, or an import path. A backend must preserve scheduler output,
|
||||
request identity, health, and cleanup contracts. Use `"mp"` when you need
|
||||
process isolation or multi-GPU; `"uni"` is the single-GPU default.
|
||||
local process isolation or multi-GPU, or `"ray"` for workers across nodes;
|
||||
`"uni"` is the single-GPU default.
|
||||
- Worker extensions go through `WorkerWrapperBase`. They add worker methods;
|
||||
they do not become a second scheduler or request lifecycle.
|
||||
|
||||
@@ -466,7 +494,7 @@ Test the smallest affected slice, then cover its neighboring boundary:
|
||||
| --- | --- |
|
||||
| Admission or state transitions | `test_diffusion_scheduler.py`, including duplicate IDs, compatibility, abort, and missing output |
|
||||
| Engine loop or output delivery | `test_diffusion_engine.py`, `test_diffusion_engine_cleanup.py`, `test_diffusion_engine_rpc_routing.py` |
|
||||
| Executor or IPC | `test_multiproc_engine_concurrency.py`, `test_uniproc_executor.py`, `test_result_pump.py`, `test_diffusion_ipc.py`, `test_async_output_worker.py` |
|
||||
| Executor or IPC | `test_multiproc_engine_concurrency.py`, `test_uniproc_executor.py`, `test_ray_executor.py`, `test_result_pump.py`, `test_diffusion_ipc.py`, `test_async_output_worker.py` |
|
||||
| Worker or runner | `test_diffusion_worker.py`, `test_diffusion_model_runner.py` |
|
||||
| Stage boundary | `test_stage_diffusion_proc.py`, `test_inline_stage_diffusion_client.py` |
|
||||
| Warmup or streaming | `test_diffusion_engine_dummy_run.py`, `test_diffusion_streaming_output.py` |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Installation
|
||||
|
||||
!!! important
|
||||
vLLM-Omni is released against the matching upstream vLLM major/minor version. The vLLM-Omni 0.30.x release line uses vLLM 0.30.x; the source-install instructions pin vLLM 0.30.0.
|
||||
vLLM-Omni is released against the matching upstream vLLM major/minor version. This source checkout targets vLLM 0.31.0. The published vLLM-Omni 0.30.0 wheel instructions use vLLM 0.30.0; choose the installation path that matches your Omni revision.
|
||||
|
||||
vLLM-Omni supports the following hardware platforms:
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# --8<-- [start:requirements]
|
||||
|
||||
- GPU: compute capability 7.0 or higher (e.g., V100, T4, RTX20xx, A100, L4, H100, etc.)
|
||||
- GPU: compute capability 7.5 or higher (e.g., T4, RTX20xx, A100, L4, H100, etc.)
|
||||
|
||||
# --8<-- [end:requirements]
|
||||
# --8<-- [start:set-up-using-python]
|
||||
|
||||
vLLM-Omni depends on the matching major/minor release of vLLM. The vLLM-Omni 0.30.x release line uses vLLM 0.30.x.
|
||||
vLLM-Omni depends on the matching major/minor release of vLLM. This source
|
||||
checkout targets vLLM 0.31.0. The pre-built Omni wheel instructions below install
|
||||
the published 0.30.0 release with its matching vLLM dependency.
|
||||
|
||||
!!! note
|
||||
PyTorch installed via `conda` will statically link `NCCL` library, which can cause issues when vLLM tries to use `NCCL`. See <gh-issue:8420> for more details.
|
||||
@@ -41,13 +43,17 @@ uv pip install 'vllm-omni[demo]==0.30.0'
|
||||
# --8<-- [start:build-wheel-from-source]
|
||||
|
||||
#### Installation of vLLM
|
||||
If you do not need to modify source code of vLLM, you can directly install the stable 0.30.0 release version of the library
|
||||
If you do not need to modify source code of vLLM, install the final 0.31.0
|
||||
release wheel from its commit-pinned index:
|
||||
|
||||
```bash
|
||||
uv pip install vllm==0.30.0 --torch-backend=auto
|
||||
uv pip install vllm==0.31.0 --torch-backend=auto \
|
||||
--extra-index-url https://wheels.vllm.ai/db9527a46873454610df6dbedf79a36d6bf1a7f6
|
||||
```
|
||||
|
||||
The 0.30.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
|
||||
The default 0.31.0 wheel contains CUDA 12.9 binaries. If you need a different
|
||||
CUDA variant or want to reuse an existing PyTorch installation, build vLLM
|
||||
from source instead.
|
||||
|
||||
#### Installation of vLLM-Omni
|
||||
Since vllm-omni is rapidly evolving, it's recommended to install it from source
|
||||
@@ -68,16 +74,16 @@ If you want to check, modify or debug with source code of vLLM, install the libr
|
||||
```bash
|
||||
git clone https://github.com/vllm-project/vllm.git
|
||||
cd vllm
|
||||
git checkout v0.30.0
|
||||
git checkout v0.31.0
|
||||
```
|
||||
Set up environment variables to get pre-built wheels. If there are internet problems, just download the whl file manually. And set `VLLM_PRECOMPILED_WHEEL_LOCATION` as your local absolute path of whl file.
|
||||
```bash
|
||||
#For CUDA 13.0 (the default for v0.30.0; the wheel filename has no `+cu130` suffix)
|
||||
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.30.0/vllm-0.30.0-cp38-abi3-manylinux_2_28_x86_64.whl
|
||||
# Default CUDA 12.9 wheel for v0.31.0 on x86_64
|
||||
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://wheels.vllm.ai/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm-0.31.0-cp38-abi3-manylinux_2_28_x86_64.whl
|
||||
```
|
||||
Install vllm with command below (If you have no existing PyTorch).
|
||||
```bash
|
||||
uv pip install --editable .
|
||||
VLLM_USE_PRECOMPILED=1 uv pip install --editable . --torch-backend=auto
|
||||
```
|
||||
Install vllm with command below (If you already have PyTorch).
|
||||
```bash
|
||||
@@ -128,12 +134,13 @@ docker run --runtime nvidia --gpus 2 \
|
||||
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.cuda -t vllm-omni-cuda .
|
||||
```
|
||||
|
||||
If you want to specify the base vLLM version:
|
||||
The source build defaults to `vllm/vllm-openai:v0.31.0`. To select that
|
||||
upstream release image explicitly:
|
||||
|
||||
```bash
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
-f docker/Dockerfile.cuda \
|
||||
--build-arg BASE_IMAGE=vllm/vllm-openai:v0.30.0 \
|
||||
--build-arg BASE_IMAGE=vllm/vllm-openai:v0.31.0 \
|
||||
-t vllm-omni-cuda .
|
||||
```
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
|
||||
For ROCm, vLLM-Omni currently recommends the setup steps through Docker Images.
|
||||
|
||||
vLLM-Omni depends on the matching major/minor release of vLLM. The vLLM-Omni
|
||||
0.30.x release line uses vLLM 0.30.x.
|
||||
vLLM-Omni depends on the matching major/minor release of vLLM. This source
|
||||
checkout targets vLLM 0.31.0. The pre-built Omni wheel instructions below install
|
||||
the published 0.30.0 release with its matching vLLM dependency.
|
||||
|
||||
The Dockerfile's `BASE_IMAGE` pin applies only to Docker builds. The
|
||||
`vllm-omni` package does not install vLLM as a dependency, so non-Docker source
|
||||
@@ -49,13 +50,14 @@ uv pip install onnxruntime-rocm
|
||||
|
||||
#### Installation of vLLM
|
||||
|
||||
If you do not need to modify source code of vLLM, you can directly install the stable 0.30.0 release version of the library
|
||||
If you do not need to modify source code of vLLM, install the final 0.31.0
|
||||
release wheel for ROCm 7.2.3:
|
||||
|
||||
```bash
|
||||
uv pip install vllm==0.30.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.30.0/rocm723
|
||||
uv pip install vllm==0.31.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.31.0/rocm723
|
||||
```
|
||||
|
||||
The pre-built 0.30.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
|
||||
The pre-built 0.31.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
|
||||
|
||||
#### Installation of vLLM-Omni
|
||||
|
||||
@@ -75,7 +77,7 @@ If you want to check, modify or debug with source code of vLLM, install the libr
|
||||
```bash
|
||||
git clone https://github.com/vllm-project/vllm.git
|
||||
cd vllm
|
||||
git checkout v0.30.0
|
||||
git checkout v0.31.0
|
||||
python3 -m pip install -r requirements/rocm.txt
|
||||
python3 setup.py develop
|
||||
```
|
||||
@@ -87,7 +89,7 @@ python3 setup.py develop
|
||||
#### Build docker image
|
||||
|
||||
The source build defaults to the published upstream base image
|
||||
`vllm/vllm-openai-rocm:v0.30.0`, aligned with the vLLM release used by CI.
|
||||
`vllm/vllm-openai-rocm:v0.31.0`, aligned with this source checkout.
|
||||
Older or custom bases must provide the vLLM APIs checked by the Dockerfile's
|
||||
image-build canary. This upstream base is distinct from the prebuilt
|
||||
`vllm/vllm-omni-rocm` images discussed below; their published-tag availability
|
||||
@@ -102,7 +104,7 @@ To select the upstream ROCm base explicitly, pass `BASE_IMAGE`:
|
||||
```bash
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
-f docker/Dockerfile.rocm \
|
||||
--build-arg BASE_IMAGE=vllm/vllm-openai-rocm:v0.30.0 \
|
||||
--build-arg BASE_IMAGE=vllm/vllm-openai-rocm:v0.31.0 \
|
||||
-t vllm-omni-rocm .
|
||||
```
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<!-- markdownlint-disable MD001 MD025 -->
|
||||
|
||||
# --8<-- [start:requirements]
|
||||
|
||||
- GPU: Validated on Intel® Arc™ B-Series.
|
||||
|
||||
# --8<-- [end:requirements]
|
||||
|
||||
# --8<-- [start:set-up-using-python]
|
||||
|
||||
vLLM-Omni currently recommends using the Docker image setup steps below.
|
||||
@@ -20,12 +23,15 @@ vLLM-Omni currently recommends using the Docker image setup steps below.
|
||||
#### Build docker image
|
||||
|
||||
```bash
|
||||
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.xpu -t vllm-omni-xpu --shm-size=4g .
|
||||
export VLLM_VERSION=v0.31.0
|
||||
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.xpu -t vllm-omni-xpu --shm-size=4g \
|
||||
--build-arg VLLM_VERSION="$VLLM_VERSION" .
|
||||
```
|
||||
|
||||
This layers vLLM-Omni on top of the published `vllm/vllm-openai-xpu:<VLLM_VERSION>`
|
||||
base image (v0.30.0 by default), which Docker pulls automatically. To target a different vLLM release,
|
||||
pass `--build-arg VLLM_VERSION=<tag>`. If that tag has not been published yet, build
|
||||
base image, which Docker pulls automatically. The Dockerfile defaults to
|
||||
v0.31.0. To target a different vLLM release, pass
|
||||
`--build-arg VLLM_VERSION=<tag>`. If that tag has not been published yet, build
|
||||
the base from upstream's own Dockerfile first and point the build at it:
|
||||
|
||||
```bash
|
||||
@@ -40,7 +46,7 @@ DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.xpu -t vllm-omni-xpu --shm-s
|
||||
|
||||
##### Launch with OpenAI API Server
|
||||
|
||||
```
|
||||
```bash
|
||||
docker run -it -d --shm-size 10g \
|
||||
--name {container_name} \
|
||||
--net=host \
|
||||
|
||||
@@ -10,25 +10,25 @@ The recommended way to use vLLM-Omni on NPU is through the vLLM-Ascend pre-built
|
||||
=== "A2"
|
||||
|
||||
```bash
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.30.0
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.31.0
|
||||
```
|
||||
|
||||
=== "A3"
|
||||
|
||||
```bash
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.30.0-a3
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.31.0-a3
|
||||
```
|
||||
|
||||
=== "A5"
|
||||
|
||||
```bash
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.30.0-a5
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.31.0-a5
|
||||
```
|
||||
|
||||
=== "310P (Experimental)"
|
||||
|
||||
```bash
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.30.0-310p
|
||||
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.31.0-310p
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -71,10 +71,14 @@ We are keeping [issue #886](https://github.com/vllm-project/vllm-omni/issues/886
|
||||
|
||||
# --8<-- [start:installation-main]
|
||||
|
||||
You can also install vLLM-Omni from the latest main branch if you want to use the latest features or bug fixes. Use the aligned vLLM-Ascend v0.30.0 image described above, and check [issue #886](https://github.com/vllm-project/vllm-omni/issues/886) for the status of the latest vLLM-Omni commit on NPU.
|
||||
You can also install vLLM-Omni from source if you want to use the latest features
|
||||
or bug fixes. This source checkout targets vLLM 0.31.0 and needs a matching
|
||||
vLLM-Ascend runtime. The v0.30.0 images above are for the published Omni v0.30.0
|
||||
release. Check [issue #886](https://github.com/vllm-project/vllm-omni/issues/886)
|
||||
for aligned versions and NPU validation before selecting a development image.
|
||||
|
||||
```bash
|
||||
# Inside the vLLM-Ascend v0.30.0 container, install vLLM-Omni from main
|
||||
# Inside a container with matching vLLM and vLLM-Ascend, install Omni from source
|
||||
cd /vllm-workspace
|
||||
git clone https://github.com/vllm-project/vllm-omni.git
|
||||
cd vllm-omni
|
||||
|
||||
@@ -19,17 +19,20 @@ uv venv --python 3.12 --seed
|
||||
source .venv/bin/activate
|
||||
|
||||
# On CUDA
|
||||
uv pip install vllm==0.30.0 --torch-backend=auto
|
||||
uv pip install vllm==0.31.0 --torch-backend=auto \
|
||||
--extra-index-url https://wheels.vllm.ai/db9527a46873454610df6dbedf79a36d6bf1a7f6
|
||||
|
||||
# On ROCm
|
||||
uv pip install vllm==0.30.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.30.0/rocm723
|
||||
uv pip install vllm==0.31.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.31.0/rocm723
|
||||
|
||||
git clone https://github.com/vllm-project/vllm-omni.git
|
||||
cd vllm-omni
|
||||
uv pip install -e .
|
||||
```
|
||||
|
||||
For additional installation methods — please see the [installation guide](installation/README.md).
|
||||
These commands install vLLM 0.31.0 for this source checkout. The CUDA index is
|
||||
pinned to the final release commit. For published vLLM-Omni wheels and additional
|
||||
installation methods, see the [installation guide](installation/README.md).
|
||||
|
||||
!!! note
|
||||
It is important to install the same major & minor version of vLLM and vLLM Omni, otherwise things may not work as expected. If the versions are misaligned, you will see a warning when you import vLLM Omni.
|
||||
|
||||
@@ -29,9 +29,11 @@ th {
|
||||
| `QwenImagePipeline` | Qwen-Image | `Qwen/Qwen-Image` | ✅︎ | | | ✅︎ | [Published](https://recipes.vllm.ai/Qwen/Qwen-Image) |
|
||||
| `QwenImagePipeline` | Qwen-Image-2512 | `Qwen/Qwen-Image-2512` | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Qwen/Qwen-Image-2512.md) |
|
||||
| `QwenImageEditPipeline` | Qwen-Image-Edit | `Qwen/Qwen-Image-Edit` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
| `JoyImageEditPipeline` | JoyAI-Image-Edit | `jdopensource/JoyAI-Image-Edit-Diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/JD/JoyAI-Image-Edit.md) |
|
||||
| `QwenImageEditPlusPipeline` | Qwen-Image-Edit-2509 | `Qwen/Qwen-Image-Edit-2509` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
| `QwenImageLayeredPipeline` | Qwen-Image-Layered | `Qwen/Qwen-Image-Layered` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
| `QwenImageEditPlusPipeline` | Qwen-Image-Edit-2511 | `Qwen/Qwen-Image-Edit-2511` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
| `QwenImage21Pipeline` | Qwen-Image-2.1 (T2I + image-conditioned generation, up to 4 condition images) | `Qwen/Qwen-Image-2.1` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Qwen/Qwen-Image-2.1.md) |
|
||||
| `GlmImagePipeline` | GLM-Image | `zai-org/GLM-Image` | ✅︎ | ✅︎ | | | — |
|
||||
| `ZImagePipeline` | Z-Image | `Tongyi-MAI/Z-Image-Turbo` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Tongyi-MAI/Z-Image-Turbo.md) |
|
||||
| `Krea2Pipeline` | Krea 2 (Raw + Turbo) | `krea/Krea-2-Raw`, `krea/Krea-2-Turbo` | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/krea/Krea-2.md) |
|
||||
@@ -54,12 +56,12 @@ th {
|
||||
| `LingBotVideoPipeline` | LingBot-Video dense and MoE T2I, T2V, TI2V | `robbyant/lingbot-video-dense-1.3b`, `robbyant/lingbot-video-moe-30b-a3b` | ✅︎ | | | | — |
|
||||
| `SanaVideoPipeline` | SANA-Video-2B T2V (native 480p/720p; Diffusers adapter validated at both resolutions) | `Efficient-Large-Model/SANA-Video_2B_480p_diffusers`, `Efficient-Large-Model/SANA-Video_2B_720p_diffusers` | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/NVIDIA/SANA-Video-2B.md) |
|
||||
| `SanaImageToVideoPipeline` | SANA-Video-2B I2V (native and Diffusers adapter validated at 480p/720p) | `Efficient-Large-Model/SANA-Video_2B_480p_diffusers`, `Efficient-Large-Model/SANA-Video_2B_720p_diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/NVIDIA/SANA-Video-2B.md) |
|
||||
| `SeedVR2Pipeline` | [SeedVR2 3B](seedvr2.md) video restoration | `ByteDance-Seed/SeedVR2-3B` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/ByteDance/SeedVR2-RTX-5090.md) |
|
||||
| `SeedVR2Pipeline` | [SeedVR2 3B](seedvr2.md) video restoration | `ByteDance-Seed/SeedVR2-3B` | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/ByteDance/SeedVR2-RTX-5090.md) |
|
||||
| `MiniMaxH3Pipeline` | MiniMax H3 T2VA, first/last-keyframe FL2VA, and mixed-reference Ref2VA | `MiniMaxAI/MiniMax-H3` | ✅︎ | ✅︎ | | | [Published](https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3) |
|
||||
| `Magi2Pipeline` | MAGI-2 native Preview-stage T2VA and I2VA (272p/540p) | `sand-ai/MAGI-2-preview` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/SandAI/MAGI-2-preview-L20X.md) |
|
||||
| `DreamZeroPipeline` | DreamZero-DROID | `GEAR-Dreams/DreamZero-DROID` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
| `Pi0Pipeline` | π0 (Pi-Zero) VLA | `lerobot/pi0_base` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/lerobot/Pi0.md) |
|
||||
| `Pi05Pipeline` | π0.5 VLA | `lerobot/pi05_base` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/lerobot/Pi05.md) |
|
||||
| `Pi05Pipeline` | π0.5 VLA | `lerobot/pi05_base` | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/lerobot/Pi05.md) |
|
||||
| `HeliosPipeline`, `HeliosPyramidPipeline` | Helios | `BestWishYsh/Helios-Base`, `BestWishYsh/Helios-Mid`, `BestWishYsh/Helios-Distilled` | ✅︎ | ✅︎ | ✅︎ | | — |
|
||||
| `OvisImagePipeline` | Ovis-Image | `OvisAI/Ovis-Image` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||
| `LongcatImagePipeline` | LongCat-Image | `meituan-longcat/LongCat-Image` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
@@ -70,7 +72,7 @@ th {
|
||||
| `StableDiffusionXLPipeline` | Stable-Diffusion-XL | `stabilityai/stable-diffusion-xl-base-1.0` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
| `StableDiffusion3Pipeline` | Stable-Diffusion-3 | `stabilityai/stable-diffusion-3.5-medium` | ✅︎ | ✅︎ | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/StabilityAI/Stable-Diffusion-3.5.md) |
|
||||
| `CosyVoice3Model` | CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||
| `AuKForConditionalGeneration` | AuK / AuK-Flash (instruction-driven speech generation and editing) | `tencent/AuK`, `tencent/AuK-Flash` (with `Qwen/Qwen2.5-Omni-3B`; assemble with `tools/prepare_auk_checkpoint.py`) | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Tencent/AuK-H100.md) |
|
||||
| `AuKForConditionalGeneration` | AuK / AuK-Flash (instruction-driven speech generation and editing) | `tencent/AuK`, `tencent/AuK-Flash` (with `Qwen/Qwen2.5-Omni-3B`; assemble with `tools/prepare_auk_checkpoint.py`) | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Tencent/AuK-H100.md) |
|
||||
| `OmniVoiceModel` | OmniVoice | `k2-fsa/OmniVoice` | ✅︎ | | | ✅︎ | — |
|
||||
| `VoxCPM2TalkerForConditionalGeneration` | VoxCPM2 | `openbmb/VoxCPM2` | ✅︎ | | | ✅︎ | — |
|
||||
| `BreezeForConditionalGeneration` | Breeze-TTS-2 (voice design, voice clone, voice direction, CFG) | `BreezeBlue/Breeze-TTS-2` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/BreezeBlue/Breeze-TTS-2.md) |
|
||||
@@ -82,8 +84,8 @@ th {
|
||||
| `FluxPipeline` | FLUX.1-schnell | `black-forest-labs/FLUX.1-schnell` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||
| `OmniGen2Pipeline` | OmniGen2 | `OmniGen2/OmniGen2` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||
| `StableAudioPipeline` | Stable-Audio-Open | `stabilityai/stable-audio-open-1.0` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||
| `MiniMaxMusic3ForConditionalGeneration` | MiniMax Music 3 (text-to-music) | `MiniMaxAI/MiniMax-Music3` | ✅︎ | | | | — |
|
||||
| `Yue2ForCausalLM` | YuE2-3B (text-to-music with ABC plan) | `m-a-p/YuE2-3B` | ✅︎ | | | | — |
|
||||
| `MiniMaxMusic3ForConditionalGeneration` | MiniMax Music 3 (text-to-music) | `MiniMaxAI/MiniMax-Music3` | ✅︎ | | | ✅︎ | — |
|
||||
| `Yue2ForCausalLM` | YuE2-3B (text-to-music with ABC plan) | `m-a-p/YuE2-3B` | ✅︎ | | | ✅︎ | [Recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/m-a-p/YuE2-3B.md) / [Audio-to-score](https://github.com/vllm-project/vllm-omni/blob/main/recipes/m-a-p/SheetSage2-H200.md) |
|
||||
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-CustomVoice | `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` | ✅︎ | | | ✅︎ | [Published](https://recipes.vllm.ai/Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice) |
|
||||
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-VoiceDesign | `Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-Base | `Qwen/Qwen3-TTS-12Hz-1.7B-Base` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||
@@ -93,7 +95,7 @@ th {
|
||||
| `MossTTSDelayModel` | MOSS-TTS, MOSS-TTSD, MOSS-SoundEffect, MOSS-VoiceGenerator | `OpenMOSS-Team/MOSS-VoiceGenerator` | ✅︎ | | | | — |
|
||||
| `MossTTSRealtime` | MOSS-TTS-Realtime | `OpenMOSS-Team/MOSS-TTS-Realtime` | ✅︎ | | | | — |
|
||||
| `HiggsAudioV2ForConditionalGeneration` | Higgs-Audio v2 | `bosonai/higgs-audio-v2-generation-3B-base` | ✅︎ | | | | — |
|
||||
| `HiggsMultimodalQwen3ForConditionalGeneration` | Higgs-Audio v3 (TTS) | `bosonai/higgs-audio-v3-tts-4b` | ✅︎ | | | | [Published](https://recipes.vllm.ai/bosonai/higgs-audio-v3-tts-4b) |
|
||||
| `HiggsMultimodalQwen3ForConditionalGeneration` | Higgs-Audio v3 (TTS) | `bosonai/higgs-audio-v3-tts-4b` | ✅︎ | | | ✅︎ | [Published](https://recipes.vllm.ai/bosonai/higgs-audio-v3-tts-4b) |
|
||||
| `IndexTTS2TalkerForConditionalGeneration` | IndexTTS-2 | `IndexTeam/IndexTTS-2` | ✅︎ | | | | — |
|
||||
| `IndexTTS25TalkerForConditionalGeneration` | IndexTTS-2.5 | native `checkpoints/` bundle | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/IndexTeam/IndexTTS-2_5.md) |
|
||||
| `NextStep11Pipeline` | NextStep-1.1 | `stepfun-ai/NextStep-1.1` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||
@@ -106,6 +108,7 @@ th {
|
||||
| `SenseNovaU1Pipeline` | SenseNova-U1-A3B (MoE) | `sensenova/SenseNova-U1-A3B-MoT` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/SenseNova/SenseNova-U1.md#a3b-moe-variant) |
|
||||
| `LancePipeline` | Lance | `bytedance-research/Lance` | ✅︎ | | | | — |
|
||||
| `HunyuanVideo15Pipeline` | HunyuanVideo-1.5-T2V | `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v`, `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v` | ✅︎ | ✅︎ | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Tencent/HunyuanVideo-1.5.md) |
|
||||
| `Kandinsky6TI2VAPipeline` | Kandinsky 6 TI2VA | `kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Kandinsky/Kandinsky6-TI2VA.md) |
|
||||
| `HunyuanVideo15ImageToVideoPipeline` | HunyuanVideo-1.5-I2V | `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v`, `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_i2v` | ✅︎ | ✅︎ | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Tencent/HunyuanVideo-1.5.md) |
|
||||
| `VoxtralTTSForConditionalGeneration` | Voxtral TTS | `mistralai/Voxtral-4B-TTS-2603` | ✅︎ | ✅︎ | | | — |
|
||||
| `CovoAudioForConditionalGeneration` | Covo-Audio-Chat | `tencent/Covo-Audio-Chat` | ✅︎ | | | | — |
|
||||
@@ -115,7 +118,7 @@ th {
|
||||
| `GepardTalkerForConditionalGeneration` | Gepard-1.0 | `nineninesix/gepard-1.0` | ✅︎ | | | ✅︎ | — |
|
||||
| `HiDreamImagePipeline` | HiDream-I1-Full | `HiDream-ai/HiDream-I1-Full` | ✅︎ | ✅︎ | | | — |
|
||||
| `HiDreamO1ImagePipeline` | HiDream-O1-Image | `HiDream-ai/HiDream-O1-Image` | ✅︎ | | | | — |
|
||||
| `AnimaPipeline` | Anima | `circlestone-labs/Anima` | ✅︎ | ✅︎ | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/circlestone-labs/Anima.md) |
|
||||
| `AnimaPipeline` | Anima | `circlestone-labs/Anima` | ✅︎ | ✅︎ | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/circlestone-labs/Anima.md) |
|
||||
|
||||
The `Recipe` column links the corresponding deployment recipe. `Published` is
|
||||
preferred when a corresponding `recipes.vllm.ai` page exists; otherwise the
|
||||
|
||||
+10
-12
@@ -154,18 +154,16 @@ All six routes are model- or configuration-dependent. In particular,
|
||||
pipeline declares a `duplex_plugin` and its deploy configuration sets
|
||||
`session_mode: duplex` (with `session_mode: turn` the same model boots the
|
||||
ordinary turn-based serving stack instead). On a duplex server a stock
|
||||
Realtime client needs no vendor query parameter; `duplex=0` selects the
|
||||
turn-based Realtime handler, which a duplex server does not mount, so that
|
||||
connection is refused with `Realtime API is not available`. Such a server
|
||||
serves the websocket route plus `POST /v1/chat/completions`, `/v1/models`
|
||||
and `/health`, and no other turn-based HTTP route. The chat route is the
|
||||
ordinary chat service running on the duplex engine: a request is a
|
||||
turn-based generation on the same stages, served alongside the live
|
||||
websocket sessions; it opens no duplex session and holds no
|
||||
`duplex_session.max_sessions` slot -- see [Full Duplex](full_duplex_api.md).
|
||||
On a server that is not duplex, an explicit `?duplex=1` is refused rather
|
||||
than answered by the turn-based handler, so a client that requested duplex
|
||||
never silently gets the other protocol.
|
||||
Realtime client needs no vendor query parameter; `duplex=0` opts out of native
|
||||
duplex and selects the turn-based Realtime handler. It runs generation on the
|
||||
same stages, alongside live websocket sessions, and opens no duplex session
|
||||
or consumes a `duplex_session.max_sessions` slot -- see
|
||||
[Full Duplex](full_duplex_api.md).
|
||||
Such a server serves the websocket route plus `POST /v1/chat/completions`,
|
||||
`/v1/models` and `/health`, and no other turn-based HTTP route. On a server
|
||||
that is not duplex, an explicit `?duplex=1` is refused rather than answered by
|
||||
the turn-based handler, so a client that requested duplex never silently gets
|
||||
the other protocol.
|
||||
|
||||
## Related Endpoints
|
||||
|
||||
|
||||
@@ -250,6 +250,40 @@ call; transport failures raise `DuplexConnectionError`; a session that ends
|
||||
while you are awaiting it raises `DuplexSessionClosedError`. All three derive
|
||||
from `DuplexClientError`.
|
||||
|
||||
### Output limits and slow consumers
|
||||
|
||||
Deployment settings under `duplex_session` bound each session's pending
|
||||
output: `max_pending_output_bytes_per_session` defaults to 2 MiB and
|
||||
`max_pending_output_events_per_session` to 512 events. Bytes count compact
|
||||
Realtime JSON, including base64 audio, not decoded audio. The limits apply
|
||||
to both WebSocket delivery and direct Python handles, not to replay journals
|
||||
or client playback queues.
|
||||
|
||||
Exceeding a limit fails the active response without committing its partial
|
||||
text to history, then closes only that session. If overflow occurs while a
|
||||
completion is being queued, its not-yet-queued `response.done` becomes failed
|
||||
with the same identity and output, and its response history entry is removed.
|
||||
An ending already accepted by the buffer is not replaced or followed by a
|
||||
second ending. Open a new session to continue; the closed session cannot be
|
||||
resumed. The notifications use these fields:
|
||||
|
||||
| Event | Field values |
|
||||
| --- | --- |
|
||||
| `error` | `error.type: "rate_limit_error"`, `error.code: "output_backpressure"` |
|
||||
| `response.done` (active response or not-yet-queued ending) | `response.status: "failed"`, `response.status_details: {"type": "failed", "reason": "output_backpressure"}` |
|
||||
| `session.closed` | `reason: "output_backpressure"` |
|
||||
|
||||
Queued audio for the affected response is removed. Previously queued non-audio
|
||||
events retain their order, but newly emitted ordinary events, including
|
||||
audio/text, content-part and output-item completion markers, are suppressed.
|
||||
Clients must therefore handle closure without waiting for every completion
|
||||
marker. Errors and response endings have an extra 64 KiB / eight-event reserve;
|
||||
if that also fills, either notification may be omitted. Closure has its own
|
||||
independent slot. A closure notification exceeding 4 KiB is compacted to
|
||||
`reason: "close_details_exceed_output_limit"` with empty details, preserving
|
||||
the event type and identity. One event held by the consumer is outside the
|
||||
queued budget; audio already sent still requires the client to stop playback.
|
||||
|
||||
### Measure latency
|
||||
|
||||
`EventCollector` accumulates events for assertions and metrics:
|
||||
@@ -315,6 +349,8 @@ single-consumer async iterator that ends after `session.closed` /
|
||||
`session.expired`; rejected commands come back as `ErrorEvent`s on it.
|
||||
Every event renders the wire JSON with `to_realtime()`, so anything written
|
||||
against the WebSocket protocol works unchanged on the typed stream.
|
||||
If opening fails, the abandoned handle is closed. Late engine events do not
|
||||
reopen a locally closed handle.
|
||||
|
||||
`vllm_omni.clients.inline_duplex.InlineDuplexClient` wraps a `DuplexOmni`
|
||||
behind the `DuplexClient` API, so the same application code runs in-process
|
||||
@@ -573,7 +609,7 @@ formats (`pcm16`, `pcm_s16le`, `s16le`, `pcm_f32le`, `g711_ulaw`,
|
||||
| `session.created` | 2 | Session opened. OpenAI name; adds `attachment_generation`, `resume_token` and vLLM-Omni keys inside `session` (`id` is the server-allocated session id, `epoch`, `turn_id`, `playback`, `capabilities`, ...). |
|
||||
| `session.updated` | 2 | Echo of the effective session config after every `session.update` (same extended `session` object). |
|
||||
| `session.heartbeat_ack` | 3 | Reply to `session.heartbeat`. |
|
||||
| `session.closed` | 3 | Last event on the socket, emitted once the engine released the session; `reason` ∈ `client_close`, `disconnect`, `timeout`, `transport_error`, `shutdown`, ... |
|
||||
| `session.closed` | 3 | Last event on the socket, emitted once the engine released the session; `reason` ∈ `client_close`, `disconnect`, `timeout`, `transport_error`, `shutdown`, `output_backpressure`, `close_details_exceed_output_limit`, ... |
|
||||
| `session.resumed` | 3 | Resume accepted; carries the new `attachment_generation` and rotated `resume_token`; journaled events are replayed after it. |
|
||||
| `session.replaced` | 3 | Sent to the superseded socket when another socket resumes the session. |
|
||||
| `session.expired` | 3 | Engine lease reaped (`disconnect_grace_expired`, `idle_ttl_expired`); socket closes after it. |
|
||||
|
||||
@@ -183,6 +183,16 @@ emit these headers.
|
||||
**Raw audio stream** (`stream_format="audio"`). Streams raw audio bytes (PCM or
|
||||
WAV) as they are decoded.
|
||||
|
||||
`pcm` is 16-bit signed little-endian, interleaved, at the model's native sample
|
||||
rate and channel count (24 kHz mono for most models; MOSS-TTS-Local v1.5 emits
|
||||
48 kHz stereo). Raw streams and non-streaming responses state the format in
|
||||
response headers:
|
||||
|
||||
| Header | Description |
|
||||
| --- | --- |
|
||||
| `x-audio-sample-rate` | Sample rate of the returned audio in Hz (after any `sample_rate` resampling). |
|
||||
| `x-audio-channels` | Channel count of the returned audio. |
|
||||
|
||||
Both streaming modes (`stream_format="audio"` and `"sse"`) require
|
||||
`response_format="pcm"` or `"wav"`, and `speed` must be `1.0` (or omitted).
|
||||
|
||||
@@ -193,9 +203,13 @@ Each event has an `event:` line and a JSON `data:` line:
|
||||
- `speech.audio.delta` — a base64 audio chunk:
|
||||
|
||||
```json
|
||||
{ "type": "speech.audio.delta", "audio": "<base64>", "response_format": "pcm" }
|
||||
{ "type": "speech.audio.delta", "audio": "<base64>", "response_format": "pcm",
|
||||
"sample_rate": 24000, "channels": 1 }
|
||||
```
|
||||
|
||||
`sample_rate` and `channels` extend the OpenAI event and describe the
|
||||
decoded chunk.
|
||||
|
||||
- `speech.audio.done` — terminal event, carrying token `usage`:
|
||||
|
||||
```json
|
||||
@@ -340,7 +354,7 @@ Server -> Client:
|
||||
|
||||
| Message | Description |
|
||||
| --------- | ------------- |
|
||||
| `{"type": "audio.start", "utterance_index": 0, "sentence_index": 0, "sentence_text": "...", "format": "pcm", "sample_rate": 24000}` | Audio generation starting for the buffered input |
|
||||
| `{"type": "audio.start", "utterance_index": 0, "sentence_index": 0, "sentence_text": "...", "format": "pcm", "sample_rate": 24000, "channels": 1}` | Audio generation starting for the buffered input. With `stream_audio=true` it is sent with the first audio chunk, and for `pcm` it carries that audio's `sample_rate` and `channels` (omitted when no audio was produced). |
|
||||
| Binary frame | Raw audio bytes (one or more PCM chunks when `stream_audio=true`) |
|
||||
| `{"type": "audio.done", "utterance_index": 0, "sentence_index": 0, "total_bytes": 96000, "error": false}` | Audio complete for the buffered input |
|
||||
| `{"type": "session.done", "utterance_index": 0, "total_sentences": N}` | Flushed utterance complete |
|
||||
|
||||
@@ -237,13 +237,22 @@ At least one mask is required. A nontrivial `video_noise_mask` requires
|
||||
`source_audio` or a `source_video` with an audio stream. Mask values are in
|
||||
`[0, 1]`: `0` preserves the source, `1` regenerates it, and fractional values
|
||||
blend the two behaviors. Exact all-one masks are no-ops and do not require a
|
||||
source. Source uploads without a mask are rejected. Masks may be a JSON scalar
|
||||
or arrays matching the H3 latent/token grid; pixel-resolution masks must be
|
||||
resized or pooled by the client before upload.
|
||||
source. Source uploads without a mask are rejected. Masks may be a JSON scalar,
|
||||
an array matching the H3 latent/token grid, or (video only) a frame-space
|
||||
array that the server resizes to the latent grid.
|
||||
|
||||
For an aligned output of `F` frames at `W x H`, the video latent grid is
|
||||
`[Tv, H/16, W/16]`, where `Tv = 2 + 5 * ((F - 5) / 17)`. The video mask may be
|
||||
a scalar, a flat token vector, `[Tv, H/32, W/32]`, or the full latent grid. For
|
||||
a scalar, a flat token vector, `[Tv, H/32, W/32]`, the full latent grid, a
|
||||
spatial `[h, w]` mask applied to every frame, or a frame-space `[T, h, w]` mask
|
||||
with one slice per 24 fps output frame. A shape that matches the token or full
|
||||
latent grid is always read as that grid. Frame-space masks are area-resized to
|
||||
`[H/16, W/16]`; along time, a mask shorter than `F` is padded with its last
|
||||
slice (as the source video is) and a longer one is truncated, then each latent
|
||||
takes the maximum over the frames the causal video VAE folds into it, so any
|
||||
regenerated frame regenerates its latent. Because the 8 MiB limit below applies
|
||||
to the JSON file, clients should area-downsample frame-space masks by 16
|
||||
spatially before upload. For
|
||||
the model input, timestep, and velocity, a full-grid mask is max-pooled over
|
||||
each 2x2 spatial token and fractional values are rounded upward to 1/256
|
||||
levels. The final x0 restore uses the original, unquantized mask, so full-grid
|
||||
|
||||
@@ -42,6 +42,7 @@ higher-priority strategy is selected.
|
||||
| --- | --- | --- | :---: | :---: | :---: | --- |
|
||||
| Flux2Pipeline | `black-forest-labs/FLUX.2-dev` | `Flux2Transformer2DModel` | yes | yes | — | `transformer_blocks`, `single_transformer_blocks` |
|
||||
| LongCatImagePipeline | `meituan-longcat/LongCat-Image` | `LongCatImageTransformer2DModel` | — | yes | — | `transformer_blocks`, `single_transformer_blocks` |
|
||||
| JoyImageEditPipeline | `jdopensource/JoyAI-Image-Edit-Diffusers` | `JoyImageEditTransformer3DModel` | yes | yes | — | `double_blocks` |
|
||||
| NextStep11Pipeline | `stepfun-ai/NextStep-1.1` | `NextStepModel` | — | yes | — | `layers` |
|
||||
| OvisImagePipeline | `AIDC-AI/Ovis-Image-7B` | `OvisImageTransformer2DModel` | — | yes | — | `transformer` |
|
||||
| QwenImagePipeline | `Qwen/Qwen-Image` | `QwenImageTransformer2DModel` | yes | yes | — | `transformer_blocks` |
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# VAE Parallelism Guide
|
||||
|
||||
|
||||
## Table of Content
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Example Script](#example-script)
|
||||
- [Configuration Parameters](#configuration-parameters)
|
||||
- [Batch Parallel Decode](#batch-parallel-decode)
|
||||
- [Best Practices](#best-practices)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Summary](#summary)
|
||||
@@ -15,23 +15,22 @@
|
||||
|
||||
## Overview
|
||||
|
||||
VAE parallelism distributes VAE (Variational AutoEncoder) decode/encode work across multiple GPUs. This guide covers VAE patch/tile parallelism, which splits latent space into spatial tiles or patches, and Wan spatial-shard decode, which shards decoder feature maps along height or width.
|
||||
VAE parallelism distributes VAE (Variational AutoEncoder) decode/encode work across multiple GPUs. This guide covers patch/tile parallelism, batch parallel decode for image VAEs, and Wan spatial-shard decode. Patch/tile parallelism divides images into spatial regions; batch parallel decode assigns complete images to ranks.
|
||||
|
||||
This is particularly useful for:
|
||||
|
||||
- **High-resolution image generation** where VAE decode can become a memory bottleneck
|
||||
- **Memory-constrained environments** where the VAE decode activation peak exceeds available VRAM
|
||||
- **Multi-GPU setups** where you want to leverage distributed resources for the VAE stage
|
||||
|
||||
See supported models list in [Supported Models](../../diffusion_features.md#supported-models).
|
||||
|
||||
|
||||
VAE patch parallelism uses two strategies based on image size:
|
||||
|
||||
| Strategy | Use Case | How It Works | Overlap Handling | Output Quality |
|
||||
|----------|----------|--------------|------------------|----------------|
|
||||
| **Tiled Decode** | Large images (triggers VAE tiling) | Distributes existing VAE tiling computation across ranks. Each rank decodes a subset of overlapping tiles. | Uses VAE's native `blend_v` and `blend_h` functions to seamlessly merge overlapping regions | Bit-identical (same logic as single-GPU tiling) |
|
||||
| **Patch Decode** | Small images (no VAE tiling) | Splits latent into spatial patches with halos. Each rank decodes one patch with boundary context. | Halo regions provide edge context; core regions are directly stitched without blending | Near-identical (diff < 0.5%, visually imperceptible) |
|
||||
|
||||
| Strategy | Use Case | How It Works | Overlap Handling | Output Quality |
|
||||
| ---------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| **Tiled Decode** | Large images (triggers VAE tiling) | Distributes existing VAE tiling computation across ranks. Each rank decodes a subset of overlapping tiles. | Uses VAE's native `blend_v` and `blend_h` functions to seamlessly merge overlapping regions | Bit-identical (same logic as single-GPU tiling) |
|
||||
| **Patch Decode** | Small images (no VAE tiling) | Splits latent into spatial patches with halos. Each rank decodes one patch with boundary context. | Halo regions provide edge context; core regions are directly stitched without blending | Near-identical (diff < 0.5%, visually imperceptible) |
|
||||
|
||||
VAE Patch Parallelism **reuses the DiT process group** (`dit_group`) and does not initialize a separate ProcessGroup. This means:
|
||||
|
||||
@@ -110,19 +109,80 @@ vllm serve Tongyi-MAI/Z-Image-Turbo --omni --port 8091 \
|
||||
|
||||
In `DiffusionParallelConfig`:
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `vae_patch_parallel_size` | int | 1 | Number of GPUs for VAE patch/tile parallelism. Set to 2 or higher to enable. Should typically match `tensor_parallel_size` as they share the same process group. |
|
||||
| `vae_parallel_mode` | str | `"tile"` | VAE parallel decode strategy: `"tile"` (default tile/patch parallel decode), `"spatial_shard_height"`, or `"spatial_shard_width"` (spatially-sharded decode, Wan only). See [Spatially-Sharded Decode](#spatially-sharded-decode-wan). |
|
||||
| Parameter | Type | Default | Description |
|
||||
| ------------------------- | ------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `vae_patch_parallel_size` | int | 1 | Number of ranks requested for VAE parallelism, including batch mode. Set to 2 or higher to enable. Batch mode caps this degree at the existing worker group size. |
|
||||
| `vae_parallel_mode` | str | `"tile"` | `"tile"` for tile/patch decode, `"batch"` for complete-image batch decode, or `"spatial_shard_height"` / `"spatial_shard_width"` for Wan spatial-shard decode. |
|
||||
|
||||
Additional requirements:
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `vae_use_tiling` | bool | False | Must be set to `True` when using VAE patch parallelism. |
|
||||
| Parameter | Type | Default | Description |
|
||||
| ---------------- | ------ | --------- | ------------------------------------------------------- |
|
||||
| `vae_use_tiling` | bool | False | Must be set to `True` when using VAE patch parallelism. |
|
||||
|
||||
!!! note "Automatic VAE Tiling"
|
||||
When `vae_patch_parallel_size > 1` and the model has a distributed VAE (`DistributedVaeMixin`), the system automatically sets `vae_use_tiling=True` if not already enabled.
|
||||
When `vae_patch_parallel_size > 1` and the model has a distributed VAE (`DistributedVaeMixin`), tile and spatial-shard modes automatically enable `vae_use_tiling`. Batch mode preserves the requested tiling setting.
|
||||
|
||||
---
|
||||
|
||||
## Batch Parallel Decode
|
||||
|
||||
Set `vae_parallel_mode="batch"` to distribute complete images across ranks during
|
||||
VAE decode. This mode supports pipelines that load `DistributedAutoencoderKL`
|
||||
or `DistributedAutoencoderKLFlux2`, including FLUX.2-dev and FLUX.2 Klein. Klein
|
||||
selects the distributed VAE only for batch mode; its default path is unchanged.
|
||||
Selecting this mode for an
|
||||
unsupported VAE, including Wan, raises an error at model initialization.
|
||||
|
||||
For a pipeline already using two tensor-parallel workers, select batch decode
|
||||
with these Python settings:
|
||||
|
||||
```python
|
||||
parallel_config = DiffusionParallelConfig(
|
||||
tensor_parallel_size=2,
|
||||
vae_patch_parallel_size=2,
|
||||
vae_parallel_mode="batch",
|
||||
)
|
||||
sampling_params = OmniDiffusionSamplingParams(num_outputs_per_prompt=2)
|
||||
```
|
||||
|
||||
The corresponding serving flags are `--vae-patch-parallel-size 2
|
||||
--vae-parallel-mode batch`. Keep the existing DiT parallelism configuration;
|
||||
the VAE degree does not allocate extra workers. Send a request for multiple
|
||||
images to use the batch split.
|
||||
|
||||
Each rank decodes a contiguous part of the image batch through its native VAE
|
||||
decoder. Results are reassembled in the original image order and made available
|
||||
on every worker. With two images and four workers, two workers decode one image
|
||||
each. With five images and four workers, the assigned batch sizes are 2, 1, 1,
|
||||
and 1. Workers without images still participate in the required communication.
|
||||
|
||||
**Constraints and behavior:**
|
||||
|
||||
- Batch mode and distributed tile/patch mode are mutually exclusive. Encoding
|
||||
uses the native local path, including native tiling when enabled, rather than
|
||||
distributing spatial tiles across workers.
|
||||
- Batch size one, VAE degree one, or a single worker uses native decode on each
|
||||
worker. Batch mode does not automatically switch to distributed tile/patch
|
||||
decode for a single image, even with `vae_use_tiling=True`. To distribute a
|
||||
single image across workers, select `vae_parallel_mode="tile"` on a pipeline
|
||||
that supports distributed tile/patch decoding.
|
||||
- Data parallelism, pipeline parallelism, and CFG parallelism must each have
|
||||
degree one because this implementation communicates over the worker WORLD
|
||||
group. Tensor and sequence parallelism may supply the shared workers.
|
||||
- Batch mode does not force tiling. Explicit native VAE tiling and slicing
|
||||
settings remain in effect within each rank's assigned batch and on the
|
||||
single-image fallback. Native tiling splits an image locally on one worker;
|
||||
it does not add cross-worker tile parallelism to batch mode.
|
||||
- It does not merge independent serving requests. The VAE must receive a batch
|
||||
containing multiple images, for example through `num_outputs_per_prompt`.
|
||||
|
||||
Splitting the batch can reduce per-rank decoder work and activation memory, but
|
||||
collecting decoded images adds communication and buffers. Measure complete
|
||||
request latency and memory for the intended model, image count, and resolution.
|
||||
This feature has no universal speedup or memory-saving guarantee. Changing the
|
||||
decoder batch shape can also change floating-point rounding; validate generated
|
||||
outputs when selecting a deployment configuration.
|
||||
|
||||
---
|
||||
|
||||
@@ -194,7 +254,8 @@ python3 benchmarks/diffusion/diffusion_benchmark_serving.py \
|
||||
### Common Issue 1: Model Not Support VAE Patch Parallel
|
||||
|
||||
**Symptoms**:
|
||||
```
|
||||
|
||||
```text
|
||||
WARNING: vae_patch_parallel_size=2 is set but VAE patch parallelism is NOT enabled for xxxPipeline; ignoring.
|
||||
```
|
||||
|
||||
@@ -217,7 +278,6 @@ if vae_pp_size > 1 and not is_distributed_vae:
|
||||
|
||||
2. To add support for a new model, implement `DistributedVaeMixin` on its VAE class (contributions are welcome).
|
||||
|
||||
|
||||
### Common Issue 2: `vae_patch_parallel_size` Exceeds DiT Process Group Size
|
||||
|
||||
**Symptoms**: Shows warning message, and vae patch parallel size is resized to DiT process group size
|
||||
@@ -227,6 +287,7 @@ if vae_pp_size > 1 and not is_distributed_vae:
|
||||
**Recommendation**: Always set `vae_patch_parallel_size` to be no greater than your DiT process group size.
|
||||
|
||||
Note that the size of DiT process group size equals to:
|
||||
|
||||
```text
|
||||
dit_parallel_size = data_parallel_size
|
||||
× cfg_parallel_size
|
||||
@@ -235,7 +296,8 @@ dit_parallel_size = data_parallel_size
|
||||
× tensor_parallel_size
|
||||
|
||||
```
|
||||
_sequence_parallel_size = ulysses_degree × ring_degree_
|
||||
|
||||
`sequence_parallel_size = ulysses_degree × ring_degree`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ convolutional network whose reference implementation in diffusers moves every
|
||||
activation through memory several times per layer (normalization, activation,
|
||||
causal padding, feature-cache bookkeeping, shortcut upsampling). vLLM-Omni
|
||||
installs a fast path on every loaded Wan VAE that fuses this data movement into
|
||||
a handful of Triton kernels while leaving the convolutions themselves untouched.
|
||||
a handful of Triton kernels. The opt-in `channels_last` level also specializes
|
||||
first-frame convolutions that have no history.
|
||||
|
||||
## Levels
|
||||
|
||||
@@ -15,9 +16,9 @@ The fast path is controlled by `--vae-fast-path` (engine argument
|
||||
`vae_fast_path`, deploy-config key `vae_fast_path`):
|
||||
|
||||
| Level | Default | Output vs. diffusers | What it does |
|
||||
|---|---|---|---|
|
||||
| --- | --- | --- | --- |
|
||||
| `lossless` | yes | bit-identical | Fused RMSNorm epilogue, fused causal-conv input assembly and cache refresh, fused shortcut upsampling and residual adds (with the neighbouring convolution biases folded in), single-pass nearest 2x upsampling, preallocated output assembly. |
|
||||
| `channels_last` | no | within tolerance (PSNR typically > 60 dB) | Everything in `lossless`, plus decoder convolution weights converted to channels-last memory format so cuDNN picks its channels-last kernels, and a single-pass channels-last RMSNorm+SiLU kernel that also absorbs the bias of the preceding `conv1`. |
|
||||
| `channels_last` | no | within tolerance (PSNR typically > 60 dB) | Everything in `lossless`, plus channels-last convolution weights, a single-pass channels-last RMSNorm+SiLU kernel that also absorbs the preceding `conv1` bias, and Conv2d for supported single-frame causal convolutions without history. |
|
||||
| `off` | no | bit-identical | Reference diffusers decoder. |
|
||||
|
||||
```bash
|
||||
@@ -61,6 +62,12 @@ stages:
|
||||
- The `channels_last` level changes the order in which cuDNN accumulates
|
||||
convolutions, so outputs differ from the reference in the last bits. Use
|
||||
`lossless` when bitwise reproducibility against diffusers matters.
|
||||
- For a supported causal convolution with one input frame and no history,
|
||||
the temporal input is `[0, 0, x]`. The `channels_last` level uses Conv2d with
|
||||
the last temporal weight slice, preserving the convolution formula for
|
||||
finite inputs and the existing cache update. Calls with history retain
|
||||
Conv3d. The weight slice is taken on each call, so weight updates and device
|
||||
moves do not leave a stale packed copy.
|
||||
- For VAEs kept in fp32, cuDNN's channels-last convolution algorithms run in
|
||||
TF32 under PyTorch's default `torch.backends.cudnn.allow_tf32 = True`, which
|
||||
dominates the difference to the reference (about 1e-3). Set
|
||||
@@ -73,10 +80,10 @@ Cosmos3-Nano VAE, 1280x720 x 189 frames, bf16, one GB200 GPU, `bench_wan_vae_dec
|
||||
(best of 2 runs after warmup):
|
||||
|
||||
| `--vae-fast-path` | Decode time | Speedup | Output vs. `off` |
|
||||
|---|-------------|---------|---|
|
||||
| `off` | 6.00 s | 1.00x | reference |
|
||||
| `lossless` | 3.26 s | 1.84x | bit-identical |
|
||||
| `channels_last` | 2.51 s | 2.39x | PSNR 62.6 dB, max abs diff 4.6e-2 |
|
||||
| --- | ------------- | --------- | --- |
|
||||
| `off` | 6.00 s | 1.00x | reference |
|
||||
| `lossless` | 3.26 s | 1.84x | bit-identical |
|
||||
| `channels_last` | 2.51 s | 2.39x | PSNR 62.6 dB, max abs diff 4.6e-2 |
|
||||
|
||||
Peak decode memory was unchanged (about 13 GiB): it is set by the largest
|
||||
activations and the cuDNN workspace, not by the output assembly.
|
||||
@@ -93,6 +100,22 @@ python benchmarks/diffusion/bench_wan_vae_decode.py --model nvidia/Cosmos3-Nano
|
||||
--size 1280x720 --frames 189 --fast-path off,lossless,channels_last --profile
|
||||
```
|
||||
|
||||
For a single-GPU comparison that isolates first-frame Conv2d within
|
||||
`channels_last`, use `--first-frame-ablation`. It alternates the two paths,
|
||||
disables cuDNN TF32 and autotuning, and prints JSON with every timing sample,
|
||||
summary statistics, peak allocated memory and output comparisons. Per-call
|
||||
weight packing is included in the measured decode time.
|
||||
|
||||
```bash
|
||||
python benchmarks/diffusion/bench_wan_vae_decode.py \
|
||||
--model Wan-AI/Wan2.1-T2V-1.3B-Diffusers --size 832x480 --frames 1 \
|
||||
--dtype bf16 --first-frame-ablation --warmup 60 --iters 50 --seed 0
|
||||
```
|
||||
|
||||
The default input is a seeded latent tensor. `--latents /path/to/latents.pt`
|
||||
can instead load a saved VAE input after latent mean/std scaling; the benchmark
|
||||
selects the temporal prefix requested by `--frames` and checks its shape.
|
||||
|
||||
The same script benchmarks multi-GPU VAE decode when launched with `torchrun`;
|
||||
the decode is timed across all ranks and rank 0 reports:
|
||||
|
||||
|
||||
@@ -152,9 +152,10 @@ The following tables show which models support each feature:
|
||||
| **HiDream-O1-Image** | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **HunyuanImage3** | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅* |
|
||||
| **Krea 2** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
||||
| **JoyAI-Image-Edit** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **LongCat-Image** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **LongCat-Image-Edit** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **MammothModa2(T2I)** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **MammothModa2(T2I)** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Nextstep_1(T2I)** | ❓ | ❓ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **OmniGen2** | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Ovis-Image** | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
@@ -163,6 +164,7 @@ The following tables show which models support each feature:
|
||||
| **Qwen-Image-Edit** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
||||
| **Qwen-Image-Edit-2509** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ (decode) | ✅ | ❌ | ❌ |
|
||||
| **Qwen-Image-Layered** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
||||
| **Qwen-Image-2.1** | ❌ | ❌ | ❓ | ✅ | ✅ | ❌ | ❓ | ❓ | ✅ (decode) | ✅ | ✅ |
|
||||
| **SenseNova-U1** | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **SenseNova-U1.5** | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ |
|
||||
| **SenseNova-U1-A3B** | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
@@ -181,7 +183,8 @@ The following tables show which models support each feature:
|
||||
> 5. HunyuanImage3 supports step execution. Multi-request step execution requires `TORCH_SDPA`; see [Diffusion Execution Modes](diffusion/execution_modes.md#step-execution).
|
||||
> 6. BAGEL step execution supports image generation with `bagel.yaml`, `bagel_think.yaml`, and `bagel_single_stage.yaml`; two-stage Thinker execution and explicit single-stage text output remain on their existing complete-request paths. Image requests require `num_inference_steps >= 2`. BAGEL step execution cannot currently be combined with sequence parallelism or a diffusion cache backend; see [Diffusion Execution Modes](diffusion/execution_modes.md#step-execution).
|
||||
> 7. MammothModa2 runs its DiT stage on the diffusion runner (`StageExecutionType.DIFFUSION`); Cache-DiT is enabled through the standard diffusion-stage knobs on the stage entry of the deploy YAML (`cache_backend: cache_dit`, plus optional `cache_config` / `enable_cache_dit_summary`). The runner installs the backend at startup and the pipeline adopts it per request. Only the repeated main-layer stack is cached; requests with `text_guidance_scale = 1.0` bypass the cache hooks.
|
||||
> 8. SenseNova-U1-A3B: `--cfg-parallel-size 2` and TP=2 both complete. CFG-P covers t2i 2-branch (`cond`+`uncond`), img2img 2-branch (`cond`+`img_cond` when `img_cfg_scale=1`), and img2img 3-branch (`cond`+`img_cond`+`uncond` when `img_cfg_scale` is neither 1 nor equal to `cfg_scale`; 3 branches on 2 ranks dispatch as `[[0, 2], [1]]`). Cache-DiT does not: the adapter is declared on `SenseNovaU1Model`, but discovery walks `_dit_modules` (`language_model` / `SenseNovaU1ForCausalLM`) and raises. Layerwise CPU offload is a no-op for the same reason — that class has no `_layerwise_offload_blocks_attrs`, so the 72.2 GiB expert weights stay resident.
|
||||
> 8. Qwen-Image-2.1 (`QwenImage21Pipeline`) handles both text-to-image and image-conditioned generation (up to 4 condition images) and supports LoRA through `QwenImageLoraLoaderMixin`. Ulysses sequence parallelism is wired and ring attention is not supported, but multi-GPU padding e2e and a parity number are not in this PR's H200 results, so SP stays ❓. Cache acceleration (TeaCache / Cache-DiT) is unavailable; its built-in KV cache for the text/condition prefix is a separate feature from those cache backends. Online FP8 is documented in the quantization guide with H200 numbers.
|
||||
> 9. SenseNova-U1-A3B: `--cfg-parallel-size 2` and TP=2 both complete. CFG-P covers t2i 2-branch (`cond`+`uncond`), img2img 2-branch (`cond`+`img_cond` when `img_cfg_scale=1`), and img2img 3-branch (`cond`+`img_cond`+`uncond` when `img_cfg_scale` is neither 1 nor equal to `cfg_scale`; 3 branches on 2 ranks dispatch as `[[0, 2], [1]]`). Cache-DiT does not: the adapter is declared on `SenseNovaU1Model`, but discovery walks `_dit_modules` (`language_model` / `SenseNovaU1ForCausalLM`) and raises. Layerwise CPU offload is a no-op for the same reason — that class has no `_layerwise_offload_blocks_attrs`, so the 72.2 GiB expert weights stay resident.
|
||||
|
||||
### VideoGen
|
||||
|
||||
@@ -196,6 +199,7 @@ The following tables show which models support each feature:
|
||||
| **SANA-Video-2B T2V I2V** | ❌ | ❌ | ✅ (`--usp`, frame-sharded) | ✅ | ✅ (TP=2 only) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Helios** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅* |
|
||||
| **HunyuanVideo-1.5 T2V I2V** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
||||
| **Kandinsky 6 TI2VA** | ✅* (uncalibrated) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ (decode) | ✅ (FP8) | ✅ (1 request) |
|
||||
| **Cosmos3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
||||
| **LongCat-Video-Avatar-1.5** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **MiniMax-H3** | ✅ (FL2VA) | ✅ | ✅ | ❌ | ✅ (DiT/TE) | ❌ | ✅ | ✅ | ✅ (tile) | ✅ (DiT) | ❌ |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/offline_inference/image_to_image>.
|
||||
|
||||
|
||||
This example edits an input image with `Qwen/Qwen-Image-Edit` using the `image_edit.py` CLI.
|
||||
This example edits an input image with `Qwen/Qwen-Image-Edit` and `JoyAI-Image-Edit` using the `image_edit.py` CLI.
|
||||
|
||||
## Local CLI Usage
|
||||
|
||||
@@ -26,6 +26,27 @@ python image_edit.py \
|
||||
--cfg-scale 4.0
|
||||
```
|
||||
|
||||
### JoyAI-Image-Edit
|
||||
|
||||
JoyAI-Image-Edit uses the same `image_edit.py` CLI and supports one input
|
||||
image per request. Requested dimensions are snapped to the nearest supported
|
||||
Joy bucket; for square outputs, use `1024x1024`.
|
||||
|
||||
```bash
|
||||
python image_edit.py \
|
||||
--model jdopensource/JoyAI-Image-Edit-Diffusers \
|
||||
--image input.png \
|
||||
--prompt "Change the background to a clean studio while preserving the subject." \
|
||||
--height 1024 \
|
||||
--width 1024 \
|
||||
--num-inference-steps 50 \
|
||||
--guidance-scale 4.0 \
|
||||
--output output_joyai_edit.png
|
||||
```
|
||||
|
||||
JoyAI defaults to guidance scale `4.0`; pass `--guidance-scale` explicitly when
|
||||
you need a different value.
|
||||
|
||||
### Multiple Image Editing (Qwen-Image-Edit-2509)
|
||||
|
||||
For multiple image inputs, use `Qwen/Qwen-Image-Edit-2509` or `Qwen/Qwen-Image-Edit-2511`:
|
||||
@@ -43,7 +64,7 @@ python image_edit.py \
|
||||
|
||||
Key arguments:
|
||||
|
||||
- `--model`: model name or path. Use `Qwen/Qwen-Image-Edit-2509` or later for multiple image support.
|
||||
- `--model`: model name or path. Use `Qwen/Qwen-Image-Edit-2509` or later for multiple image support. `jdopensource/JoyAI-Image-Edit-Diffusers` supports one input image.
|
||||
- `--image`: path(s) to the source image(s) (PNG/JPG, converted to RGB). Can specify multiple images.
|
||||
- `--prompt` / `--negative-prompt`: text description (string).
|
||||
- `--cfg-scale`: true classifier-free guidance scale (default: 4.0). Classifier-free guidance is enabled by setting cfg_scale > 1 and providing a negative_prompt. Higher guidance scale encourages images closely linked to the text prompt, usually at the expense of lower image quality.
|
||||
|
||||
@@ -130,7 +130,7 @@ python examples/offline_inference/text_to_speech/cosyvoice3/end2end.py \
|
||||
### Notes
|
||||
|
||||
- Stage 0 (`talker`) emits speech tokens; stage 1 (`code2wav`) runs flow matching + HiFiGAN to synthesize waveform.
|
||||
- Deploy config auto-loads from `vllm_omni/deploy/cosyvoice3.yaml` based on HF `model_type`. Pass `--deploy-config <path>` to override.
|
||||
- H200 automatically selects `cosyvoice3_packed_streaming_optimized_standard.yaml` when the CUDA MPS control tool is available; other devices and environments use `cosyvoice3.yaml`. See the [serving defaults and tradeoffs](../online_serving/text_to_speech.md#cosyvoice3). Pass `--deploy-config cosyvoice3.yaml` for the previous profile or `--deploy-config <path>` to override. The example uses the resolved profile's sampling parameters.
|
||||
- `async_chunk: true` is the default; pass `--no-async-chunk` to switch to the legacy synchronous path.
|
||||
|
||||
---
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/image_to_image>.
|
||||
|
||||
|
||||
This example demonstrates how to deploy Qwen-Image-Edit model for online image editing service using vLLM-Omni.
|
||||
This example demonstrates how to deploy image-to-image models for online image editing service using vLLM-Omni.
|
||||
|
||||
Supported models include Qwen-Image-Edit, BAGEL, JoyAI-Image-Edit, and other image-to-image pipelines.
|
||||
|
||||
For **multi-image** input editing, use **Qwen-Image-Edit-2509** (QwenImageEditPlusPipeline) and send multiple images in the user message content.
|
||||
|
||||
For **JoyAI-Image-Edit**, use `jdopensource/JoyAI-Image-Edit-Diffusers` and send exactly one input image.
|
||||
|
||||
## Start Server
|
||||
|
||||
### Basic Start
|
||||
@@ -24,6 +28,12 @@ vllm serve Qwen/Qwen-Image-Edit --omni --port 8092
|
||||
vllm serve Qwen/Qwen-Image-Edit-2509 --omni --port 8092
|
||||
```
|
||||
|
||||
### JoyAI-Image-Edit
|
||||
|
||||
```bash
|
||||
vllm serve jdopensource/JoyAI-Image-Edit-Diffusers --omni --port 8092
|
||||
```
|
||||
|
||||
### Start with Parameters
|
||||
|
||||
|
||||
@@ -76,6 +86,11 @@ curl -s http://localhost:8092/v1/chat/completions \
|
||||
| cut -d',' -f2 | base64 -d > output.png
|
||||
```
|
||||
|
||||
For JoyAI-Image-Edit, set classifier-free guidance with `guidance_scale`
|
||||
in `extra_body` (default: `4.0`). `true_cfg_scale` remains supported as an
|
||||
alias; if both are provided, their values must match. Values at or below `1.0`
|
||||
disable CFG.
|
||||
|
||||
### Method 2: Using OpenAI Python SDK
|
||||
|
||||
```python
|
||||
|
||||
@@ -155,16 +155,22 @@ vllm serve FunAudioLLM/Fun-CosyVoice3-0.5B-2512 --omni --port 8091 --trust-remot
|
||||
./cosyvoice3/run_server.sh
|
||||
```
|
||||
|
||||
Streaming is on by default via `async_chunk: true` in `vllm_omni/deploy/cosyvoice3.yaml`. Pass `--no-async-chunk` (or `NO_ASYNC_CHUNK=1 ./cosyvoice3/run_server.sh`) for the legacy synchronous path.
|
||||
Streaming is on by default. On CUDA Hopper GPUs with at least 140 GiB of device memory (H200), serving automatically selects `cosyvoice3_packed_streaming_optimized_standard.yaml` when `nvidia-cuda-mps-control` is available on `PATH`: AR32 / codec32, standard sampling, packed Flow, cached ISTFT, HiFT decode graphs, and local CUDA MPS. No `--hf-overrides` is needed. Other devices and environments without the MPS control tool use `cosyvoice3.yaml` with RAS sampling and AR8 / codec8.
|
||||
|
||||
Cross-request Stage-1 flow batching is opt-in. Enable it in the server environment when concurrent requests should share a flow-estimator call:
|
||||
The optimized profile is not a universal hardware configuration. Its packed kernels are Hopper-only; the talker's Model Runner V2 is unsupported on NPU/XPU, and CUDA MPS requires a local NVIDIA GPU and its control tool. Larger batches, 25-frame chunks, and graph allocations also require per-device validation on smaller or slower GPUs. Automatic selection retains the generic profile outside the validated device class; an explicit deploy configuration takes precedence.
|
||||
|
||||
The H200 default targets streaming throughput at C32/C64. It changes sampling from RAS to standard (temperature 0.7, top-p 0.8, top-k 20, repetition penalty 1.21); output lengths and quality can differ. HiFT captures a recurring exact shape on its third use, which adds latency to that request, and its graph pool needs memory headroom beyond the engine reservations. This profile is not a claim of optimal first-audio latency at low concurrency. Pass `--deploy-config cosyvoice3.yaml` to retain the previous configuration, or `--deploy-config <path>` to select another profile.
|
||||
|
||||
Pass `--no-async-chunk` (or `NO_ASYNC_CHUNK=1 ./cosyvoice3/run_server.sh`) for the legacy synchronous path; use `--deploy-config cosyvoice3.yaml` alongside it when selecting that path on H200.
|
||||
|
||||
Cross-request Stage-1 flow batching is enabled in the H200 profile. For the generic configuration, enable it in the server environment when concurrent requests should share a flow-estimator call:
|
||||
|
||||
```bash
|
||||
export COSYVOICE3_BATCH_FLOW=1
|
||||
vllm serve FunAudioLLM/Fun-CosyVoice3-0.5B-2512 --omni --port 8091 --trust-remote-code
|
||||
```
|
||||
|
||||
Batching preserves output lengths and streaming cache alignment, but the different GEMM shapes can produce small waveform differences compared with processing each request separately. Leave `COSYVOICE3_BATCH_FLOW` unset (or set it to `0`) when request-independent numerical behavior is required. Set `COSYVOICE3_BATCH_FLOW_DEBUG=1` to log the observed group-size distribution and enable detailed Stage-1 profiler scopes; diagnostics are disabled by default to avoid per-step profiling overhead.
|
||||
Batching preserves output lengths and streaming cache alignment, but the different GEMM shapes can produce small waveform differences compared with processing each request separately. Use `--deploy-config cosyvoice3.yaml` and leave `COSYVOICE3_BATCH_FLOW` unset (or set it to `0`) when request-independent numerical behavior is required. Set `COSYVOICE3_BATCH_FLOW_DEBUG=1` to log the observed group-size distribution and enable detailed Stage-1 profiler scopes; diagnostics are disabled by default to avoid per-step profiling overhead.
|
||||
|
||||
### CLI client
|
||||
|
||||
@@ -194,7 +200,7 @@ The client supports `--api-base`, `--model`, `--text`, `--ref-audio`, `--ref-tex
|
||||
### Notes
|
||||
|
||||
- Stage 0 (`talker`) emits speech tokens; stage 1 (`code2wav`) runs flow matching + HiFiGAN to synthesize waveform.
|
||||
- Deploy config auto-loads from `vllm_omni/deploy/cosyvoice3.yaml` based on HF `model_type`. Pass `--deploy-config <path>` to override.
|
||||
- Deploy config auto-loads the device-specific CosyVoice3 default described above. Pass `--deploy-config <path>` to override.
|
||||
- For offline inference and the end-to-end script, see the [offline CosyVoice3 section](https://github.com/vllm-project/vllm-omni/tree/main/examples/offline_inference/text_to_speech/README.md#cosyvoice3).
|
||||
|
||||
---
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# FP8 Quantization
|
||||
# FP8 Quantization
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -17,7 +17,7 @@ in deep DiT blocks.
|
||||
## Hardware Support
|
||||
|
||||
| Device | Support |
|
||||
|--------|---------|
|
||||
| -------- | --------- |
|
||||
| NVIDIA Blackwell GPU (SM 100+) | ✅ |
|
||||
| NVIDIA Ada/Hopper GPU (SM 89+) | ✅ |
|
||||
| NVIDIA Ampere GPU (SM 80+) | ✅ |
|
||||
@@ -93,8 +93,9 @@ warmup_quack_fp8([(14040, 2048, 6144), (14040, 2048, 2048)])
|
||||
### Diffusion Models
|
||||
|
||||
| Model | HF models | Online | Pre-calibrated | Recommendation | `ignored_layers` | Text-Encoder quantization |
|
||||
|-------|-----------|:-------:|:------:|----------------|------------------|------------------|
|
||||
| ------- | ----------- | :-------: | :------: | ---------------- | ------------------ | ------------------ |
|
||||
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | Yes | Yes | Skip sensitive image-stream MLPs when quality regresses | `img_mlp` | |
|
||||
| Qwen-Image-2.1 | `Qwen/Qwen-Image-2.1` | Yes | Not validated | Skip image-stream MLPs for best fidelity; all-layer FP8 is loadable. On H200 (1024×1024, teapot prompt) DiT FP8-all is ~30.4 GB / 6.06 s vs BF16 ~36.9 GB / 7.34 s (~33 dB vs BF16). Text encoder FP8 covers the language model only — the vision tower and LM head stay BF16 | `img_mlp` | ✅︎ (language model only) |
|
||||
| Wan2.2 | Wan2.2 diffusion pipelines | Not validated | Not validated | Validate against BF16 before documenting as supported | TBD | |
|
||||
| LTX-2 | `Lightricks/LTX-2`, `rootonchair/LTX-2-19b-distilled` | Yes | Not validated | Transformer only; use dynamic phase LoRA for ordinary two-stage | None | |
|
||||
| LTX-2.3 | `diffusers/LTX-2.3-Diffusers`, `diffusers/LTX-2.3-Distilled-Diffusers` | Yes | Not validated | Transformer only; use dynamic phase LoRA for ordinary two-stage | None | |
|
||||
@@ -110,20 +111,20 @@ warmup_quack_fp8([(14040, 2048, 6144), (14040, 2048, 2048)])
|
||||
|
||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
||||
|
||||
| Model | Scope | Format | Status |
|
||||
|-------|-------|--------|--------|
|
||||
| Model | Scope | Format | Status |
|
||||
| ---------- | ---------------------------- | ---------------------------------------- | ----------------------------------- |
|
||||
| Qwen3-Omni | Thinker language-model stage | [ModelOpt](modelopt.md) `quant_algo=FP8` | Tested for thinker memory reduction |
|
||||
| Qwen3-TTS | TTS language-model stage | Checkpoint config | Not validated |
|
||||
| Qwen3-TTS | TTS language-model stage | Checkpoint config | Not validated |
|
||||
|
||||
Audio encoder, vision encoder, talker, and code2wav stay in BF16 unless a
|
||||
model-specific guide says otherwise.
|
||||
|
||||
### Multi-Stage Diffusion Model (BAGEL, GLM-Image)
|
||||
|
||||
| Model | Scope | Status | Notes |
|
||||
|-------|-------|--------|-------|
|
||||
| BAGEL | Stage-specific transformer or DiT module | Not validated | Route FP8 to the intended stage before enabling |
|
||||
| GLM-Image | Stage-specific transformer or DiT module | Not validated | Validate quality against BF16 baseline |
|
||||
| Model | Scope | Status | Notes |
|
||||
| --------- | ---------------------------------------- | ------------- | ----------------------------------------------- |
|
||||
| BAGEL | Stage-specific transformer or DiT module | Not validated | Route FP8 to the intended stage before enabling |
|
||||
| GLM-Image | Stage-specific transformer or DiT module | Not validated | Validate quality against BF16 baseline |
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -162,7 +163,7 @@ For a pipeline that exposes both a transformer and a quantization-aware text
|
||||
encoder, the scope is:
|
||||
|
||||
| Configuration | Transformer | Text encoder | Components without supported quantizable layers |
|
||||
|---------------|-------------|--------------|-------------------------------------------------|
|
||||
| --------------- | ------------- | -------------- | ------------------------------------------------- |
|
||||
| `quantization="fp8"` | FP8 | FP8 | checkpoint precision |
|
||||
| `{"transformer": {"method": "fp8"}}` | FP8 | checkpoint precision | checkpoint precision |
|
||||
| `{"text_encoder": {"method": "fp8"}}` | checkpoint precision | FP8 | checkpoint precision |
|
||||
@@ -211,7 +212,7 @@ does not fit the workload.
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| ----------- | ------ | --------- | ------------- |
|
||||
| `method` | str | - | Quantization method (`"fp8"`) |
|
||||
| `ignored_layers` | list[str] | `[]` | Layer name patterns to keep in BF16 |
|
||||
| `activation_scheme` | str | `"dynamic"` | `"dynamic"` selects online activation scaling, or `"static"` when scales are available |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""
|
||||
Example script for image editing with OmniGen2.
|
||||
@@ -39,6 +39,20 @@ Usage (multiple images):
|
||||
--cfg-scale 4.0 \
|
||||
--guidance-scale 1.0
|
||||
|
||||
Usage (JoyAI-Image-Edit, single image):
|
||||
python image_edit.py \
|
||||
--model jdopensource/JoyAI-Image-Edit-Diffusers \
|
||||
--image input.png \
|
||||
--prompt "Change the background to a clean studio while preserving the subject." \
|
||||
--height 1024 \
|
||||
--width 1024 \
|
||||
--num-inference-steps 50 \
|
||||
--guidance-scale 4.0 \
|
||||
--output output_joyai_edit.png
|
||||
|
||||
Note: JoyAI-Image-Edit snaps requested dimensions to the nearest supported
|
||||
Joy bucket; for square outputs, use 1024x1024.
|
||||
|
||||
Usage (with cache-dit acceleration):
|
||||
python image_edit.py \
|
||||
--image input.png \
|
||||
@@ -209,14 +223,14 @@ def parse_profiler_config(value: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Edit an image with Qwen-Image-Edit.")
|
||||
parser = argparse.ArgumentParser(description="Edit images with vLLM-Omni.")
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
default="Qwen/Qwen-Image-Edit",
|
||||
help=(
|
||||
"Diffusion model name or local path. "
|
||||
"For multiple image inputs, use Qwen/Qwen-Image-Edit-2509 or Qwen/Qwen-Image-Edit-2511"
|
||||
"which supports QwenImageEditPlusPipeline."
|
||||
"For multiple image inputs, use Qwen/Qwen-Image-Edit-2509, "
|
||||
"Qwen/Qwen-Image-Edit-2511, or Qwen/Qwen-Image-2.1."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -270,9 +284,10 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--cfg-scale",
|
||||
type=float,
|
||||
default=4.0,
|
||||
default=None,
|
||||
help=(
|
||||
"True classifier-free guidance scale (default: 4.0). Guidance scale as defined in Classifier-Free "
|
||||
"True classifier-free guidance scale. When omitted, the pipeline uses its model-specific default. "
|
||||
"Guidance scale as defined in Classifier-Free "
|
||||
"Diffusion Guidance. Classifier-free guidance is enabled by setting cfg_scale > 1 and providing "
|
||||
"a negative_prompt. Higher guidance scale encourages images closely linked to the text prompt, "
|
||||
"usually at the expense of lower image quality."
|
||||
@@ -281,9 +296,9 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--guidance-scale",
|
||||
type=float,
|
||||
default=1.0,
|
||||
default=None,
|
||||
help=(
|
||||
"Guidance scale for guidance-distilled models (default: 1.0, disabled). "
|
||||
"Guidance scale for guidance-distilled models. When omitted, the pipeline uses its model-specific default. "
|
||||
"Unlike classifier-free guidance (--cfg-scale), guidance-distilled models take the guidance scale "
|
||||
"directly as an input parameter. Enabled when guidance_scale > 1. Ignored when not using guidance-distilled models."
|
||||
),
|
||||
@@ -368,7 +383,7 @@ def parse_args() -> argparse.Namespace:
|
||||
"--color-format",
|
||||
type=str,
|
||||
default="RGB",
|
||||
help="For Qwen-Image-Layered, set to RGBA.",
|
||||
help="For Qwen-Image-Layered or Qwen-Image-2.1, set to RGBA to preserve transparency.",
|
||||
)
|
||||
|
||||
# Cache-DiT specific parameters
|
||||
@@ -523,6 +538,12 @@ def parse_args() -> argparse.Namespace:
|
||||
"silently masking with a possibly-wrong prompt."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--init-timeout",
|
||||
type=int,
|
||||
default=600,
|
||||
help="Overall pipeline initialization timeout in seconds.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -593,6 +614,7 @@ def main():
|
||||
enable_cpu_offload=args.enable_cpu_offload,
|
||||
enable_diffusion_pipeline_profiler=args.enable_diffusion_pipeline_profiler,
|
||||
profiler_config=args.profiler_config,
|
||||
init_timeout=args.init_timeout,
|
||||
)
|
||||
if args.enforce_eager is not None:
|
||||
omni_kwargs["enforce_eager"] = args.enforce_eager
|
||||
@@ -647,6 +669,7 @@ def main():
|
||||
|
||||
diffusion_params = OmniDiffusionSamplingParams(
|
||||
generator=generator,
|
||||
seed=args.seed,
|
||||
true_cfg_scale=args.cfg_scale,
|
||||
guidance_scale=args.guidance_scale,
|
||||
guidance_scale_2=args.guidance_scale_2,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Image-To-Image
|
||||
|
||||
This example edits an input image with `Qwen/Qwen-Image-Edit` using the `image_edit.py` CLI.
|
||||
This example edits an input image with `Qwen/Qwen-Image-Edit` and `JoyAI-Image-Edit` using the `image_edit.py` CLI.
|
||||
|
||||
## Local CLI Usage
|
||||
|
||||
@@ -23,6 +23,27 @@ python image_edit.py \
|
||||
--cfg-scale 4.0
|
||||
```
|
||||
|
||||
### JoyAI-Image-Edit
|
||||
|
||||
JoyAI-Image-Edit uses the same `image_edit.py` CLI and supports one input
|
||||
image per request. Requested dimensions are snapped to the nearest supported Joy
|
||||
bucket; for square outputs, use 1024x1024.
|
||||
|
||||
```bash
|
||||
python image_edit.py \
|
||||
--model jdopensource/JoyAI-Image-Edit-Diffusers \
|
||||
--image input.png \
|
||||
--prompt "Change the background to a clean studio while preserving the subject." \
|
||||
--height 1024 \
|
||||
--width 1024 \
|
||||
--num-inference-steps 50 \
|
||||
--guidance-scale 4.0 \
|
||||
--output output_joyai_edit.png
|
||||
```
|
||||
|
||||
JoyAI defaults to guidance scale `4.0`; pass `--guidance-scale` explicitly when
|
||||
you need a different value.
|
||||
|
||||
### Multiple Image Editing (Qwen-Image-Edit-2509)
|
||||
|
||||
For multiple image inputs, use `Qwen/Qwen-Image-Edit-2509` or `Qwen/Qwen-Image-Edit-2511`:
|
||||
@@ -40,7 +61,7 @@ python image_edit.py \
|
||||
|
||||
Key arguments:
|
||||
|
||||
- `--model`: model name or path. Use `Qwen/Qwen-Image-Edit-2509` or later for multiple image support.
|
||||
- `--model`: model name or path. Use `Qwen/Qwen-Image-Edit-2509` or later for multiple image support. `jdopensource/JoyAI-Image-Edit-Diffusers` supports one input image.
|
||||
- `--image`: path(s) to the source image(s) (PNG/JPG, converted to RGB). Can specify multiple images.
|
||||
- `--prompt` / `--negative-prompt`: text description (string).
|
||||
- `--cfg-scale`: true classifier-free guidance scale (default: 4.0). Classifier-free guidance is enabled by setting cfg_scale > 1 and providing a negative_prompt. Higher guidance scale encourages images closely linked to the text prompt, usually at the expense of lower image quality.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
python "$(dirname "${BASH_SOURCE[0]}")/image_edit.py" \
|
||||
--model Qwen/Qwen-Image-2.1 \
|
||||
--color-format RGBA \
|
||||
--seed 42 \
|
||||
--image qwen_bear.png \
|
||||
--prompt "Let this mascot dance under the moon, surrounded by floating stars and poetic bubbles such as 'Be Kind'" \
|
||||
--output qwen_image_21_edit.png \
|
||||
--num-inference-steps 50 \
|
||||
--cfg-scale 1.0 \
|
||||
"$@"
|
||||
@@ -1,7 +1,7 @@
|
||||
# Image-To-Video
|
||||
|
||||
This shared example generates videos from images with VACE, Wan2.2, LTX-2,
|
||||
HunyuanVideo-1.5, SANA-Video, Cosmos3, MAGI-2, and other compatible pipelines.
|
||||
HunyuanVideo-1.5, SANA-Video, Cosmos3, MAGI-2, Kandinsky 6, and other compatible pipelines.
|
||||
|
||||
- `image_to_video.py`: command-line script for single video generation with advanced options.
|
||||
|
||||
@@ -30,6 +30,7 @@ This folder provides a unified CLI script for image-to-video generation using vL
|
||||
| `sand-ai/MAGI-2-preview` | 512 x 896 | 125 | 100 | Model-fixed | Native four-GPU TP/SP; resident SP4 default; DLO available |
|
||||
| `Efficient-Large-Model/SANA-Video_2B_480p_diffusers` | 480 x 832 | 81 | 50 | 6.0 | Native `SanaImageToVideoPipeline`; Wan VAE |
|
||||
| `Efficient-Large-Model/SANA-Video_2B_720p_diffusers` | 704 x 1280 | 81 | 50 | 6.0 | Native `SanaImageToVideoPipeline`; LTX-2 Video VAE |
|
||||
| `kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers` | 480 x 864 | 125 | 50 | 5.0 | ~75 GiB reserved (H100, CPU offload); gated Hub repo |
|
||||
|
||||
MAGI-2 native Preview setup, four-GPU topology and DLO choices, I2VA commands,
|
||||
and eight-GPU validation status are documented in the
|
||||
|
||||
@@ -14,7 +14,7 @@ from diffusers.utils import numpy_to_pil
|
||||
|
||||
from vllm_omni.diffusion.data import logger
|
||||
from vllm_omni.diffusion.utils.image_output import extract_images_from_outputs
|
||||
from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
|
||||
from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args, ar_grid_max_tokens
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
from vllm_omni.entrypoints.openai.stage_params import clone_sampling_params
|
||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||
@@ -72,6 +72,24 @@ def build_text_to_image_prompt(prompt: str, negative_prompt: str | None) -> dict
|
||||
return result
|
||||
|
||||
|
||||
def build_parallel_knob_kwargs(args: argparse.Namespace) -> dict[str, Any]:
|
||||
"""Forward explicitly-set parallel knobs so they can override a deploy YAML.
|
||||
|
||||
The argparse defaults are ``None``; only explicitly supplied values enter
|
||||
the returned dict, letting a deploy YAML's parallel_config stay in effect
|
||||
when the flags are omitted (and letting explicit values equal to the old
|
||||
argparse defaults, e.g. ``--ulysses-degree 1``, still override the YAML).
|
||||
"""
|
||||
knob_names = (
|
||||
"ulysses_degree",
|
||||
"ring_degree",
|
||||
"ulysses_mode",
|
||||
"cfg_parallel_size",
|
||||
"vae_patch_parallel_size",
|
||||
)
|
||||
return {name: getattr(args, name) for name in knob_names if getattr(args, name) is not None}
|
||||
|
||||
|
||||
def _normalize_images_for_save(images: list[Any]) -> list[Any]:
|
||||
"""Convert NumPy diffusion outputs to PIL images before saving."""
|
||||
normalized = []
|
||||
@@ -83,13 +101,15 @@ def _normalize_images_for_save(images: list[Any]) -> list[Any]:
|
||||
return normalized
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
"""Build the example's CLI parser (kept separate for testability)."""
|
||||
parser = argparse.ArgumentParser(description="Generate an image with supported diffusion models.")
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
default="Qwen/Qwen-Image",
|
||||
help="Diffusion model name or local path. Supported models: "
|
||||
"Qwen/Qwen-Image, Tongyi-MAI/Z-Image-Turbo, Qwen/Qwen-Image-2512, stepfun-ai/NextStep-1.1, "
|
||||
"Qwen/Qwen-Image, Tongyi-MAI/Z-Image-Turbo, Qwen/Qwen-Image-2512, Qwen/Qwen-Image-2.1, "
|
||||
"stepfun-ai/NextStep-1.1, "
|
||||
"black-forest-labs/FLUX.1-dev, black-forest-labs/FLUX.2-klein-9B, "
|
||||
"black-forest-labs/FLUX.2-dev, tencent/HunyuanImage-3.0-Instruct, "
|
||||
"HiDream-ai/HiDream-O1-Image, meituan-longcat/LongCat-Image, OvisAI/Ovis-Image, "
|
||||
@@ -166,28 +186,31 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--ulysses-degree",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Number of GPUs used for ulysses sequence parallelism.",
|
||||
default=None,
|
||||
help="Number of GPUs used for ulysses sequence parallelism. "
|
||||
"Explicitly passing 1 overrides a deploy YAML value.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ulysses-mode",
|
||||
type=str,
|
||||
default="strict",
|
||||
default=None,
|
||||
choices=["strict", "advanced_uaa"],
|
||||
help="Ulysses sequence-parallel mode: 'strict' (divisibility required) or 'advanced_uaa' (UAA).",
|
||||
help="Ulysses sequence-parallel mode: 'strict' (divisibility required) or 'advanced_uaa' (UAA). "
|
||||
"Explicitly passing 'strict' overrides a deploy YAML value.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ring-degree",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Number of GPUs used for ring sequence parallelism.",
|
||||
default=None,
|
||||
help="Number of GPUs used for ring sequence parallelism. Explicitly passing 1 overrides a deploy YAML value.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cfg-parallel-size",
|
||||
type=int,
|
||||
default=1,
|
||||
default=None,
|
||||
choices=[1, 2],
|
||||
help="Number of GPUs used for classifier free guidance parallel size.",
|
||||
help="Number of GPUs used for classifier free guidance parallel size. "
|
||||
"Explicitly passing 1 overrides a deploy YAML value.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--enforce-eager",
|
||||
@@ -296,8 +319,9 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--vae-patch-parallel-size",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Number of ranks used for VAE patch/tile parallelism (decode/encode).",
|
||||
default=None,
|
||||
help="Number of ranks used for VAE patch/tile parallelism (decode/encode). "
|
||||
"Explicitly passing 1 overrides a deploy YAML value.",
|
||||
)
|
||||
# NextStep-1.1 specific arguments
|
||||
parser.add_argument(
|
||||
@@ -413,7 +437,11 @@ def parse_args() -> argparse.Namespace:
|
||||
),
|
||||
)
|
||||
current_omni_platform.pre_register_and_update(parser)
|
||||
return parser.parse_args()
|
||||
return parser
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
return build_parser().parse_args()
|
||||
|
||||
|
||||
def _apply_ar_stage_inputs(
|
||||
@@ -527,14 +555,8 @@ def main():
|
||||
"scm_steps_policy": "dynamic", # SCM steps policy: "dynamic" or "static"
|
||||
}
|
||||
elif cache_backend == "tea_cache":
|
||||
# TeaCache configuration
|
||||
# All parameters marked with [tea_cache only] in DiffusionCacheConfig
|
||||
cache_config = {
|
||||
# TeaCache parameters [tea_cache only]
|
||||
"rel_l1_thresh": 0.2, # Threshold for accumulated relative L1 distance
|
||||
# Note: coefficients will use model-specific defaults based on model_type
|
||||
# (e.g., QwenImagePipeline or FluxPipeline)
|
||||
}
|
||||
# Let TeaCache select the model-specific threshold and coefficients.
|
||||
cache_config = {}
|
||||
|
||||
profiler_enabled = args.profiler_config is not None
|
||||
|
||||
@@ -562,21 +584,11 @@ def main():
|
||||
|
||||
omni_kwargs = {
|
||||
"model": args.model,
|
||||
"enable_layerwise_offload": args.enable_layerwise_offload,
|
||||
"vae_use_slicing": args.vae_use_slicing,
|
||||
"vae_use_tiling": args.vae_use_tiling,
|
||||
"cache_backend": args.cache_backend,
|
||||
"cache_config": cache_config,
|
||||
"enable_cache_dit_summary": args.enable_cache_dit_summary,
|
||||
"ulysses_degree": args.ulysses_degree,
|
||||
"ring_degree": args.ring_degree,
|
||||
"ulysses_mode": args.ulysses_mode,
|
||||
"cfg_parallel_size": args.cfg_parallel_size,
|
||||
"vae_patch_parallel_size": args.vae_patch_parallel_size,
|
||||
"enable_expert_parallel": args.enable_expert_parallel,
|
||||
"enable_cpu_offload": args.enable_cpu_offload,
|
||||
"log_stats": args.log_stats,
|
||||
"enable_diffusion_pipeline_profiler": args.enable_diffusion_pipeline_profiler,
|
||||
"profiler_config": args.profiler_config,
|
||||
"init_timeout": args.init_timeout,
|
||||
"stage_init_timeout": args.stage_init_timeout,
|
||||
@@ -584,6 +596,8 @@ def main():
|
||||
**lora_args,
|
||||
**quant_kwargs,
|
||||
}
|
||||
# Diffusion-only engine args: pass only when explicitly set, so pipelines
|
||||
# without a DIFFUSION stage pass ownership validation.
|
||||
if args.tensor_parallel_size is not None:
|
||||
omni_kwargs["tensor_parallel_size"] = args.tensor_parallel_size
|
||||
if args.enforce_eager is not None:
|
||||
@@ -603,6 +617,18 @@ def main():
|
||||
# gate is an engine-level config (offline analog of the server's --no-guardrails).
|
||||
if args.extra_body and "guardrails" in args.extra_body:
|
||||
omni_kwargs["model_config"] = {"guardrails": bool(args.extra_body["guardrails"])}
|
||||
if args.enable_layerwise_offload:
|
||||
omni_kwargs["enable_layerwise_offload"] = True
|
||||
if args.vae_use_slicing:
|
||||
omni_kwargs["vae_use_slicing"] = True
|
||||
if args.vae_use_tiling:
|
||||
omni_kwargs["vae_use_tiling"] = True
|
||||
if args.enable_cache_dit_summary:
|
||||
omni_kwargs["enable_cache_dit_summary"] = True
|
||||
if args.enable_diffusion_pipeline_profiler:
|
||||
omni_kwargs["enable_diffusion_pipeline_profiler"] = True
|
||||
omni_kwargs.update(build_parallel_knob_kwargs(args))
|
||||
|
||||
omni = Omni(**omni_kwargs)
|
||||
model_class_name = get_model_class_name(omni)
|
||||
declared_extra_body_params = get_extra_body_params(model_class_name)
|
||||
@@ -621,11 +647,17 @@ def main():
|
||||
if ignored_layers:
|
||||
print(f" Ignored layers: {ignored_layers}")
|
||||
tp_display = args.tensor_parallel_size if args.tensor_parallel_size is not None else "deploy/default"
|
||||
|
||||
def _knob_display(value: Any, default: Any) -> Any:
|
||||
return value if value is not None else f"deploy/default ({default})"
|
||||
|
||||
print(
|
||||
f" Parallel configuration: tensor_parallel_size={tp_display}, "
|
||||
f"ulysses_degree={args.ulysses_degree}, ulysses_mode={args.ulysses_mode}, "
|
||||
f"ring_degree={args.ring_degree}, cfg_parallel_size={args.cfg_parallel_size}, "
|
||||
f"vae_patch_parallel_size={args.vae_patch_parallel_size}, "
|
||||
f"ulysses_degree={_knob_display(args.ulysses_degree, 1)}, "
|
||||
f"ulysses_mode={_knob_display(args.ulysses_mode, 'strict')}, "
|
||||
f"ring_degree={_knob_display(args.ring_degree, 1)}, "
|
||||
f"cfg_parallel_size={_knob_display(args.cfg_parallel_size, 1)}, "
|
||||
f"vae_patch_parallel_size={_knob_display(args.vae_patch_parallel_size, 1)}, "
|
||||
f"enable_expert_parallel={args.enable_expert_parallel}."
|
||||
)
|
||||
print(f" CPU offload: {args.enable_cpu_offload}; CPU Layerwise Offload: {args.enable_layerwise_offload}")
|
||||
@@ -736,8 +768,9 @@ def main():
|
||||
if idx == 0 and prompt_info.get("omni_task") == ["t2i"]:
|
||||
ar_width = int(prompt_info.get("ar_width", [0])[0])
|
||||
ar_height = int(prompt_info.get("ar_height", [0])[0])
|
||||
if ar_width > 0 and ar_height > 0:
|
||||
params.max_tokens = ar_height * (ar_width + 1) + 1
|
||||
ar_grid_budget = ar_grid_max_tokens(ar_width, ar_height)
|
||||
if ar_grid_budget is not None:
|
||||
params.max_tokens = ar_grid_budget
|
||||
|
||||
if not diffusion_replaced and len(sampling_params_list) == 1:
|
||||
sampling_params_list = [diffusion_params]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
import argparse
|
||||
import copy
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
@@ -10,13 +11,11 @@ from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
from vllm import SamplingParams
|
||||
from vllm.multimodal.media.audio import load_audio
|
||||
|
||||
from vllm_omni.entrypoints.omni import Omni
|
||||
from vllm_omni.model_executor.models.cosyvoice3.tokenizer import get_qwen_tokenizer
|
||||
from vllm_omni.model_executor.models.cosyvoice3.utils import extract_text_token
|
||||
from vllm_omni.transformers_utils.configs.cosyvoice3 import CosyVoice3Config
|
||||
|
||||
# Upstream zero-shot reference clip
|
||||
ZERO_SHOT_PROMPT_URL = "https://raw.githubusercontent.com/FunAudioLLM/CosyVoice/main/asset/zero_shot_prompt.wav"
|
||||
@@ -59,8 +58,7 @@ def run_e2e():
|
||||
"--deploy-config",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Override the deploy config path. If unset, auto-loads "
|
||||
"vllm_omni/deploy/cosyvoice3.yaml based on the HF model_type.",
|
||||
help="Override the deploy config path. If unset, selects the bundled CosyVoice3 profile for the device.",
|
||||
)
|
||||
parser.add_argument("--text", type=str, default="Hello, this is a test of the CosyVoice system capability.")
|
||||
parser.add_argument(
|
||||
@@ -105,8 +103,6 @@ def run_e2e():
|
||||
profiler_config=args.profiler_config,
|
||||
)
|
||||
|
||||
sampling_cfg = {"top_p": 0.8, "top_k": 25, "eos_token_id": 6561 + 1}
|
||||
|
||||
print("Model initialized. Preparing inputs...")
|
||||
ref_audio_path = args.ref_audio or _default_ref_audio()
|
||||
if not os.path.exists(ref_audio_path):
|
||||
@@ -138,7 +134,7 @@ def run_e2e():
|
||||
|
||||
print(f"Generating for prompt: {args.text}")
|
||||
|
||||
config = CosyVoice3Config()
|
||||
config = omni.engine.stage_vllm_configs[0].model_config.hf_config
|
||||
tokenizer = get_qwen_tokenizer(
|
||||
token_path=args.tokenizer,
|
||||
skip_special_tokens=config.skip_special_tokens,
|
||||
@@ -149,29 +145,10 @@ def run_e2e():
|
||||
min_len = int(base_len * config.min_token_text_ratio)
|
||||
max_len = int(base_len * config.max_token_text_ratio)
|
||||
|
||||
# Build SamplingParams for each stage (GPT, S2Mel, Vocoder)
|
||||
gpt_sampling = SamplingParams(
|
||||
temperature=1.0,
|
||||
top_p=sampling_cfg["top_p"],
|
||||
top_k=sampling_cfg["top_k"],
|
||||
repetition_penalty=2.0,
|
||||
min_tokens=min_len,
|
||||
max_tokens=max_len,
|
||||
stop_token_ids=[sampling_cfg["eos_token_id"]],
|
||||
# allowed_token_ids=list(range(6561+3)),
|
||||
detokenize=False,
|
||||
)
|
||||
# Not used
|
||||
s2mel_sampling = SamplingParams(
|
||||
temperature=1.0,
|
||||
top_p=1.0,
|
||||
top_k=-1,
|
||||
repetition_penalty=2.0,
|
||||
max_tokens=256,
|
||||
detokenize=False,
|
||||
)
|
||||
|
||||
sampling_params_list = [gpt_sampling, s2mel_sampling]
|
||||
# Keep the deploy profile's sampling policy, penalties and required stops.
|
||||
sampling_params_list = copy.deepcopy(omni.default_sampling_params_list)
|
||||
sampling_params_list[0].max_tokens = max(1, min(2048, max_len))
|
||||
sampling_params_list[0].min_tokens = min(max(1, min_len), sampling_params_list[0].max_tokens)
|
||||
|
||||
profiler_enabled = args.profiler_config is not None
|
||||
if profiler_enabled:
|
||||
|
||||
@@ -14,6 +14,7 @@ A unified script for text-to-video generation. Supports multiple models with mod
|
||||
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v` | 720x1280 | 121 | 50 | 6.0 | FP8 + VAE tiling required |
|
||||
| `nvidia/Cosmos3-Nano` | 720x1280 | 189 | 35 | 6.0 | ~46 GiB (peak, 720p) |
|
||||
| `BestWishYsh/Helios-Base` / `Helios-Mid` / `Helios-Distilled` | 384x640 | 99 | 50 | 5.0 / 5.0 / 1.0 | — |
|
||||
| `kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers` | 480x864 | 125 | 50 | 5.0 | ~75 GiB reserved (H100, CPU offload, SDPA) |
|
||||
| `sand-ai/MAGI-2-preview` | 512x896 | 125 | 100 | Model-fixed | Native four-GPU TP/SP; resident SP4 default; DLO available |
|
||||
| `Efficient-Large-Model/SANA-Video_2B_480p_diffusers` | 480x832 | 81 | 50 | 6.0 | BF16 DiT + FP32 Wan VAE wrapper |
|
||||
| `Efficient-Large-Model/SANA-Video_2B_720p_diffusers` | 704x1280 | 81 | 50 | 6.0 | BF16 DiT + LTX-2 Video VAE wrapper |
|
||||
@@ -249,6 +250,24 @@ python text_to_video.py \
|
||||
> conditioning tensors that cannot be passed through the JSON `--extra-body`
|
||||
> flag; they are out of scope for this text-to-video example.
|
||||
|
||||
### Kandinsky 6 (TI2VA)
|
||||
|
||||
The Hub checkpoint is gated; export `HF_TOKEN` if download returns 401.
|
||||
`--enable-cpu-offload` is required on an 80 GB H100. Audio is on by default.
|
||||
|
||||
```bash
|
||||
python text_to_video.py \
|
||||
--model kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers \
|
||||
--prompt "A golden retriever runs along a sunny beach, waves crashing, cinematic footage" \
|
||||
--enable-cpu-offload \
|
||||
--output kandinsky6_t2va.mp4
|
||||
```
|
||||
|
||||
Video only: add `--extra-body '{"sample_audio": false}'`. Image-to-video uses
|
||||
the shared [`image_to_video.py`](../image_to_video/README.md) example with
|
||||
`--image`. Serving numbers and flags are in the
|
||||
[`Kandinsky 6 TI2VA recipe`](../../../recipes/Kandinsky/Kandinsky6-TI2VA.md).
|
||||
|
||||
## Key Arguments
|
||||
|
||||
### Common
|
||||
|
||||
@@ -235,7 +235,7 @@ def parse_extra_body(value: str) -> dict[str, Any]:
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a video from a text prompt. "
|
||||
"Supports Wan2.2, HunyuanVideo-1.5, Helios, LingBot-Video, MAGI-2, "
|
||||
"Supports Wan2.2, HunyuanVideo-1.5, Helios, LingBot-Video, MAGI-2, Kandinsky 6, "
|
||||
"and other text-to-video models."
|
||||
)
|
||||
parser.add_argument(
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
This example demonstrates how to deploy image-to-image models for online image editing service using vLLM-Omni.
|
||||
|
||||
Supported models include Qwen-Image-Edit, BAGEL, and other image-to-image pipelines.
|
||||
Supported models include Qwen-Image-Edit, BAGEL, JoyAI-Image-Edit, and other image-to-image pipelines.
|
||||
|
||||
For **multi-image** input editing, use **Qwen-Image-Edit-2509** (QwenImageEditPlusPipeline) and send multiple images in the user message content.
|
||||
|
||||
For **JoyAI-Image-Edit**, use `jdopensource/JoyAI-Image-Edit-Diffusers` and send exactly one input image.
|
||||
|
||||
## Start Server
|
||||
|
||||
### Basic Start
|
||||
@@ -29,6 +31,12 @@ vllm serve Qwen/Qwen-Image-Edit-2509 --omni --port 8092
|
||||
vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --port 8091
|
||||
```
|
||||
|
||||
### JoyAI-Image-Edit
|
||||
|
||||
```bash
|
||||
vllm serve jdopensource/JoyAI-Image-Edit-Diffusers --omni --port 8092
|
||||
```
|
||||
|
||||
### Start with Parameters
|
||||
|
||||
|
||||
@@ -77,6 +85,11 @@ EOF
|
||||
curl -s http://localhost:8092/v1/chat/completions -H "Content-Type: application/json" -d @request.json | jq -r '.choices[0].message.content[0].image_url.url' | cut -d',' -f2 | base64 -d > output.png
|
||||
```
|
||||
|
||||
For JoyAI-Image-Edit, set classifier-free guidance with `guidance_scale`
|
||||
in `extra_body` (default: `4.0`). `true_cfg_scale` remains supported as an
|
||||
alias; if both are provided, their values must match. Values at or below `1.0`
|
||||
disable CFG.
|
||||
|
||||
### Method 2: Using OpenAI Python SDK
|
||||
|
||||
```python
|
||||
|
||||
@@ -277,61 +277,6 @@ cd examples/online_serving/qwen3_omni
|
||||
python examples/online_serving/openai_chat_completion_client_for_multimodal_generation.py --model Qwen/Qwen3-Omni-30B-A3B-Instruct --query-type use_image --port 8091 --host "localhost"
|
||||
```
|
||||
|
||||
#### Realtime WebSocket client (`openai_realtime_client.py`)
|
||||
|
||||
[`openai_realtime_client.py`](./openai_realtime_client.py) connects to **`ws://<host>:<port>/v1/realtime`**, streams a local WAV as **PCM16 mono @ 16 kHz** in fixed-size chunks, and receives **`response.output_audio.*`**, **`response.output_text.*`**, and transcript events. By default it uses explicit `input_audio_buffer.commit`; pass **`--server-vad`** to let the server detect speech endpoints and commit the turn automatically. The client concatenates audio deltas and writes **`--output-wav`** (model output is typically **24 kHz**). Optional **`--delta-dump-dir`** saves each delta as `delta_000001.wav`, … for debugging.
|
||||
|
||||
Streaming input works well for translation-style use cases; if the Thinker runs while input is still incomplete, consider limiting **`max_tokens`** in your session / server defaults to avoid over-generation.
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
```bash
|
||||
pip install websockets
|
||||
```
|
||||
|
||||
**From this directory** (`examples/online_serving/qwen3_omni`):
|
||||
|
||||
```bash
|
||||
python openai_realtime_client.py \
|
||||
--url ws://localhost:8091/v1/realtime \
|
||||
--model Qwen/Qwen3-Omni-30B-A3B-Instruct \
|
||||
--input-wav /path/to/input_16k_mono.wav \
|
||||
--output-wav realtime_output.wav \
|
||||
--server-vad \
|
||||
--delta-dump-dir ./rt_delta_wavs
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Flag | Default | Description |
|
||||
| ------ | --------- | ------------- |
|
||||
| `--url` | `ws://localhost:8091/v1/realtime` | Full WebSocket URL including path |
|
||||
| `--model` | `Qwen/Qwen3-Omni-30B-A3B-Instruct` | Must match the served model (sent in `session.update`) |
|
||||
| `--input-wav` | *(required)* | Input WAV: mono, 16-bit PCM, **16 kHz** |
|
||||
| `--output-wav` | `realtime_output.wav` | Output path for concatenated reply audio |
|
||||
| `--output-text` | *(optional)* | If set, write final transcription text to this path |
|
||||
| `--chunk-ms` | `200` | Size of each uploaded audio chunk (milliseconds of audio) |
|
||||
| `--send-delay-ms` | `0` | Delay between chunk sends (simulate realtime upload) |
|
||||
| `--delta-dump-dir` | *(optional)* | Directory to write per-`response.output_audio.delta` WAV files |
|
||||
| `--server-vad` | disabled | Enable server-side Silero VAD instead of sending explicit commits |
|
||||
| `--num-requests` | `1` | Number of sequential sessions (see `--concurrency`) |
|
||||
| `--concurrency` | `1` | Max concurrent WebSocket sessions when `--num-requests` > 1 |
|
||||
|
||||
Server VAD uses the pinned Silero v6.2 ONNX artifact from `istupakov/silero-vad-onnx` and never downloads model files while processing a session. Make the artifact available in the Hugging Face cache before startup, or configure a local artifact in the deployment YAML:
|
||||
|
||||
```yaml
|
||||
base_config: /path/to/vllm-omni/vllm_omni/deploy/qwen3_omni_duplex.yaml
|
||||
duplex_session:
|
||||
server_vad_model_path: /models/silero_vad.onnx
|
||||
```
|
||||
|
||||
For the engine-owned plugin, use an overlay based on
|
||||
`vllm_omni/deploy/qwen3_omni_duplex.yaml`, with `duplex_session.server_vad_model_path`
|
||||
pointing to your local artifact. See the [complete duplex setup](../realtime_web/README.md#qwen3-with-server-vad-automatic-turns).
|
||||
The default `qwen3_omni_moe.yaml` remains a turn deployment. Duplex mode serves
|
||||
`/v1/realtime?duplex=1` and `/v1/chat/completions`; the legacy STT handler requires
|
||||
a turn deployment.
|
||||
|
||||
The Python client supports the following command-line arguments:
|
||||
|
||||
- `--query-type` (or `-q`): Query type (default: `use_video`). Options: `text`, `use_audio`, `use_image`, `use_video`
|
||||
@@ -358,6 +303,13 @@ python examples/online_serving/openai_chat_completion_client_for_multimodal_gene
|
||||
bash run_curl_multimodal_generation.sh use_image
|
||||
```
|
||||
|
||||
### Realtime WebSocket
|
||||
|
||||
The [shared Realtime UI](../realtime_web/README.md#qwen3-explicit-turn-realtime)
|
||||
supports explicit turns through `/v1/realtime?duplex=0`. The
|
||||
[Server VAD setup](../realtime_web/README.md#qwen3-with-server-vad-automatic-turns)
|
||||
uses the duplex endpoint.
|
||||
|
||||
### FAQ
|
||||
|
||||
## Modality control
|
||||
@@ -582,26 +534,6 @@ The gradio script supports the following arguments:
|
||||
|
||||
Run these commands from the repository root with the vLLM-Omni environment activated.
|
||||
|
||||
### Without VAD: manually submit each turn
|
||||
|
||||
Start the backend:
|
||||
|
||||
```bash
|
||||
vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091
|
||||
```
|
||||
|
||||
In another terminal, check backend readiness and start the UI:
|
||||
|
||||
```bash
|
||||
curl --fail http://127.0.0.1:8091/health
|
||||
python -m examples.online_serving.qwen3_omni.realtime_web \
|
||||
--backend ws://127.0.0.1:8091 --stt --port 7863
|
||||
```
|
||||
|
||||
Open `http://localhost:7863`, start a session, speak, and press **Send turn**.
|
||||
`--stt` is the default. Each turn uses a separate connection without shared model
|
||||
history; the conversation shown in the browser is a local log.
|
||||
|
||||
### With VAD: automatically submit after silence
|
||||
|
||||
Start the backend with a Server VAD deployment overlay and a compatible Silero
|
||||
@@ -610,7 +542,7 @@ ONNX artifact, following the
|
||||
After its health check succeeds, start the UI:
|
||||
|
||||
```bash
|
||||
python -m examples.online_serving.qwen3_omni.realtime_web \
|
||||
python -m examples.online_serving.realtime_web --profile qwen3-turn \
|
||||
--backend ws://127.0.0.1:8091 --vad --port 7863
|
||||
```
|
||||
|
||||
@@ -621,9 +553,7 @@ VAD mode keeps uploading audio during replies and supports speech interruption.
|
||||
The engine-owned Qwen duplex plugin maintains conversation history and tracks
|
||||
played replies through playback acknowledgements. Each committed utterance starts
|
||||
a new generation request; the model does not use native streaming-input KV decoding.
|
||||
STT mode pauses microphone upload while the reply is generated and played.
|
||||
In VAD mode, click **Camera** to upload sampled frames with your spoken question;
|
||||
see [camera setup and limits](../realtime_web/README.md#qwen-vad-camera-input).
|
||||
STT remains audio-only. To switch back to manual turns, use the default turn
|
||||
deployment, restart the UI with `--stt`, refresh the page, and reconnect. Stop the existing UI before reusing port 7863.
|
||||
See the [shared UI guide](../realtime_web/README.md) for HTTPS access and testing.
|
||||
For explicit turns, use `--manual` with the shared UI as described in the
|
||||
[shared UI guide](../realtime_web/README.md#qwen3-explicit-turn-realtime).
|
||||
@@ -7,14 +7,14 @@ This implements [RFC #7222](https://github.com/vllm-project/vllm-omni/issues/722
|
||||
| Profile | Turn control | Camera | Playback acknowledgement |
|
||||
| --- | --- | --- | --- |
|
||||
| `minicpm-native` | Model-controlled listen/speak, continuous audio input | Frames accompany audio | Yes |
|
||||
| `qwen3-turn --stt` (default) | User presses **Send turn** | No | No |
|
||||
| `qwen3-turn --manual` | Press **Send turn**; mic pauses while the reply plays | No | No |
|
||||
| `qwen3-turn --vad` | Server detects trailing silence; speech interrupts replies | Sampled frames with each spoken turn | Yes |
|
||||
| `aura-ptt` | Hold **Hold to talk**; release commits. Hold also stops local playback | Sticky frames with speech; vision-follow every 2 frames only while unlocked (locked from PTT release until text-final / listen) | Yes |
|
||||
|
||||
Qwen3 VAD uses an engine-owned duplex plugin: microphone upload continues
|
||||
while replies stream, and speech can interrupt generation and playback. The
|
||||
checkpoint still generates committed turns; this is not native streaming-input
|
||||
KV decoding. STT remains explicit-turn and audio-only.
|
||||
KV decoding.
|
||||
|
||||
## MiniCPM: existing command stays valid
|
||||
|
||||
@@ -62,45 +62,36 @@ just-committed speech turn on Stage0/1 (`max_num_seqs=1`). Audio still playing
|
||||
does not hold the next vision turn once text is done.
|
||||
MiniCPM / Qwen profiles do not set `pushToTalk`, so the PTT control stays hidden.
|
||||
|
||||
## Qwen3: explicit-turn STT adapter
|
||||
## Qwen3: explicit-turn Realtime
|
||||
|
||||
Start the backend, then the UI in a second terminal:
|
||||
The shared UI sends OpenAI Realtime events to `/v1/realtime?duplex=0` and keeps
|
||||
conversation history on the same connection:
|
||||
|
||||
Start the turn-based backend:
|
||||
|
||||
```bash
|
||||
vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091
|
||||
|
||||
python -m examples.online_serving.qwen3_omni.realtime_web \
|
||||
--backend ws://127.0.0.1:8091 --stt --port 7863
|
||||
```
|
||||
|
||||
Open `http://localhost:7863`, start a session, speak, and press **Send turn**.
|
||||
Wait for the answer to finish playing before speaking again. The UI opens a
|
||||
fresh STT connection for each turn; the displayed conversation is a local log,
|
||||
and **previous turns are not provided as model history**. The STT endpoint
|
||||
accepts only model selection, so system-prompt controls are hidden in this mode.
|
||||
In another terminal, check readiness and start the UI:
|
||||
|
||||
The adapter follows the shipped `qwen3_omni/openai_realtime_client.py` and
|
||||
`vllm_omni/entrypoints/openai/realtime_connection.py`:
|
||||
```bash
|
||||
curl --fail http://127.0.0.1:8091/health
|
||||
python -m examples.online_serving.realtime_web --profile qwen3-turn \
|
||||
--backend ws://127.0.0.1:8091 --manual --port 7863
|
||||
```
|
||||
|
||||
|
||||
1. Explicit `duplex=0` selects the legacy STT handler on a turn deployment.
|
||||
It does not enable STT on a duplex deployment.
|
||||
2. Send `{type: "session.update", model: ...}` and `commit(final=false)`.
|
||||
3. Stream mono PCM16 at 16 kHz; **Send turn** flushes buffered audio, then sends `commit(final=true)`.
|
||||
4. `response.output_audio.delta.audio` contains PCM; `response.output_audio.done`
|
||||
terminates this adapter, including a response with no audio.
|
||||
5. The local implementation emits **model output** on `transcription.*`, so the
|
||||
UI displays it as assistant text. This differs from the early RFC's suggested
|
||||
user-transcript mapping. The VAD adapter also supports `response.output_text.*`
|
||||
and audio-transcript events without duplicating the answer.
|
||||
Speak, then press **Send turn**. The client commits the audio and requests a
|
||||
response; recording pauses while the reply plays.
|
||||
|
||||
## Qwen3 with Server VAD (automatic turns)
|
||||
|
||||
Run commands from the repository root with the vLLM-Omni environment activated.
|
||||
Prepare the pinned Silero v6.2 ONNX artifact as described in the
|
||||
[Qwen3 Server VAD instructions](../qwen3_omni/README.md#realtime-websocket-client-openai_realtime_clientpy).
|
||||
The backend requires ONNX Runtime and a compatible local artifact; setting
|
||||
`--vad` on the UI alone does not enable VAD on the backend.
|
||||
The plugin uses the pinned Silero v6.2 ONNX artifact from
|
||||
`istupakov/silero-vad-onnx` and does not download it while serving. Make it
|
||||
available in the Hugging Face cache or set `server_vad_model_path` to a local
|
||||
copy in the overlay below. The backend requires ONNX Runtime; setting `--vad`
|
||||
on the UI alone does not enable VAD.
|
||||
|
||||
Create an overlay named `qwen3_vad.yaml`, replacing both paths with absolute paths
|
||||
on the backend host:
|
||||
@@ -124,7 +115,7 @@ In a second terminal, wait until the health check succeeds, then start the UI:
|
||||
|
||||
```bash
|
||||
curl --fail http://127.0.0.1:8091/health
|
||||
python -m examples.online_serving.qwen3_omni.realtime_web \
|
||||
python -m examples.online_serving.realtime_web --profile qwen3-turn \
|
||||
--backend ws://127.0.0.1:8091 --vad --port 7863
|
||||
```
|
||||
|
||||
@@ -148,21 +139,17 @@ The bundled duplex deploy allows four audio inputs and eight images per prompt.
|
||||
Use headphones to avoid speaker audio triggering VAD interruption. Reference
|
||||
voices and tool calls are not supported by this plugin.
|
||||
|
||||
### Switching between VAD and manual turns
|
||||
### Using the Qwen3 web UI
|
||||
|
||||
| Mode | Backend | UI flag | Submit a turn |
|
||||
| --- | --- | --- | --- |
|
||||
| Without VAD | Default Qwen turn deployment | `--stt` (default) | Press **Send turn** |
|
||||
| With VAD | Server VAD configuration and Silero artifact | `--vad` | Pause after speaking |
|
||||
The shared UI supports both explicit turns and Server VAD. Use the manual UI
|
||||
command above with the turn-based deployment, or start the VAD deployment above
|
||||
and launch the UI with `--vad`.
|
||||
|
||||
Stop the existing UI process before starting another on the same port, then
|
||||
refresh the browser and reconnect. To use STT, start a backend with the default
|
||||
turn deployment and restart the UI with `--stt`. The duplex backend exposes
|
||||
Realtime duplex and ChatCompletion; it does not expose the legacy STT handler.
|
||||
To enable VAD on a default backend, first restart that backend with the overlay.
|
||||
Make `--backend` match the actual backend port; the UI and backend use separate
|
||||
ports. `Connection refused` means the target backend is unavailable: check its
|
||||
startup log and `/health` before connecting.
|
||||
refresh the browser and reconnect. To enable VAD on a default backend, restart
|
||||
that backend with the overlay. Make `--backend` match the actual backend port;
|
||||
the UI and backend use separate ports. `Connection refused` means the target
|
||||
backend is unavailable: check its startup log and `/health` before connecting.
|
||||
|
||||
VAD connects with `duplex=1` and sends nested `session.audio.input` configuration
|
||||
with `create_response: true` and `interrupt_response: true`. It sends playback
|
||||
@@ -210,8 +197,7 @@ native camera protocol, where Stage 0 interleaves frames at unit boundaries.
|
||||
|
||||
This is a supported subset, not full OpenAI Realtime API compatibility.
|
||||
`duplex=1`, `overlap_policy`, `playback.ack`, and `session.close` remain vLLM-Omni
|
||||
extensions. The legacy `--stt` mode is unchanged and is not the standard Realtime
|
||||
conversation API. This demo does not add WebRTC, semantic VAD, or tool calling.
|
||||
extensions. This demo does not add WebRTC, semantic VAD, or tool calling.
|
||||
|
||||
## Shared host options
|
||||
|
||||
@@ -219,11 +205,14 @@ The shared entry point can select a profile explicitly:
|
||||
|
||||
```bash
|
||||
python -m examples.online_serving.realtime_web --profile qwen3-turn \
|
||||
--backend ws://127.0.0.1:8091 --stt --port 7863
|
||||
--backend ws://127.0.0.1:8091 --vad --port 7863
|
||||
```
|
||||
|
||||
This Qwen3 UI profile requires the server-VAD deployment above.
|
||||
|
||||
- `--backend` / `--ws-backend`: backend WebSocket origin (not the full `/v1/realtime` URL).
|
||||
- `--model`: override the wrapper's model name.
|
||||
- `--manual` / `--vad`: Qwen turn control; manual turns are the default.
|
||||
- `--public-realtime-url`: optional browser-visible WebSocket URL; otherwise the
|
||||
static host proxies `/v1/realtime` on the same origin.
|
||||
- `--host`, `--port`: UI bind address and port (default port 7862).
|
||||
@@ -245,7 +234,7 @@ pytest -m cpu tests/model_executor/models/qwen3_omni/test_duplex_plugin.py \
|
||||
tests/engine/test_duplex_orchestrator.py
|
||||
```
|
||||
|
||||
These CPU tests cover profile messages, two STT turns, VAD terminal/drain ordering,
|
||||
These CPU tests cover profile message serialization, VAD terminal/drain ordering,
|
||||
backpressure, native capture during playback, server config injection, and shared
|
||||
worklets. Backend regressions cover current audio inclusion in history-based
|
||||
prompts, delayed playback ACK ordering, and no extra generation during silence
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
'use strict';
|
||||
const profiles = root.OmniRealtimeProfiles;
|
||||
profiles['qwen3-turn'] = (config) => {
|
||||
const vad = config.adapter === 'vad';
|
||||
const vad = config.turnMode === 'vad';
|
||||
let interruptOnSpeech = vad;
|
||||
let imageSeq = 0;
|
||||
let imageItems = [];
|
||||
// The engine refuses a ninth image (or 4 MiB of them) for the whole
|
||||
// session, so the camera track retires its own oldest frame instead.
|
||||
const MAX_IMAGE_ITEMS = 8;
|
||||
const hint = 'This Qwen deploy needs session_mode: duplex and Server VAD support; use --stt or enable Server VAD.';
|
||||
const hint = 'This Qwen deployment needs session_mode: duplex with Server VAD enabled.';
|
||||
return {
|
||||
title: 'Qwen3-Omni Voice', eyebrow: vad ? 'Engine-owned duplex call' : 'Turn-based realtime call',
|
||||
title: 'Qwen3-Omni Voice', eyebrow: vad ? 'Engine-owned duplex call' : 'Turn-based Realtime call',
|
||||
policy: vad ? 'Server VAD • interruptible responses' : 'Send turn • one response at a time',
|
||||
description: vad
|
||||
? 'Pause to send your turn; speak again to interrupt. Microphone upload continues during playback. Requires the Qwen duplex plugin and Silero. Optional camera frames accompany each spoken turn.'
|
||||
: 'Speak, then press Send turn. Microphone upload pauses while the model answers. Each turn uses a fresh connection; conversation history is not carried between turns. No barge-in or camera input.',
|
||||
: 'Speak, then press Send turn. Microphone upload pauses while the model answers. Conversation history stays on the Realtime session. No barge-in or camera input.',
|
||||
waiting: 'Waiting for you', camera: vad, cameraMaxDimension: 448, playbackAck: vad, clientCommit: !vad,
|
||||
waitForResponseDone: true, halfDuplex: !vad, deduplicateTranscript: true, closeSession: vad, reconnectEachTurn: !vad,
|
||||
inputSampleRate: vad ? 24000 : 16000,
|
||||
readyEvent: vad ? 'session.updated' : 'session.created', instructions: vad, sendIntervalMs: 200,
|
||||
waitForResponseDone: true, halfDuplex: !vad, deduplicateTranscript: true, closeSession: vad,
|
||||
inputSampleRate: 24000,
|
||||
readyEvent: 'session.updated', instructions: true, sendIntervalMs: 200,
|
||||
presets: { assistant: 'You are a helpful assistant. Answer clearly and concisely.' },
|
||||
url(config, location) {
|
||||
const url = profiles.url(config, location);
|
||||
@@ -33,22 +33,17 @@
|
||||
initialMessages(config, instructions) {
|
||||
imageItems = [];
|
||||
interruptOnSpeech = vad;
|
||||
if (!vad) return [
|
||||
{ type: 'session.update', model: config.model },
|
||||
{ type: 'input_audio_buffer.commit', final: false },
|
||||
];
|
||||
const session = { model: config.model, overlap_policy: 'barge_in_on_speech', audio: { input: {
|
||||
const session = { model: config.model, audio: { input: {
|
||||
format: { type: 'audio/pcm', rate: 24000 },
|
||||
turn_detection: { type: 'server_vad', threshold: 0.5, prefix_padding_ms: 300,
|
||||
silence_duration_ms: 500, create_response: true, interrupt_response: true },
|
||||
turn_detection: vad ? { type: 'server_vad', threshold: 0.5, prefix_padding_ms: 300,
|
||||
silence_duration_ms: 500, create_response: true, interrupt_response: true } : null,
|
||||
} } };
|
||||
if (vad) session.overlap_policy = 'barge_in_on_speech';
|
||||
else session.type = 'realtime';
|
||||
if (instructions) session.instructions = instructions;
|
||||
return [{ type: 'session.update', session }];
|
||||
},
|
||||
append(audio) {
|
||||
return vad ? { type: 'input_audio_buffer.append', audio }
|
||||
: { type: 'input_audio_buffer.append', audio, format: 'pcm16', sample_rate_hz: 16000 };
|
||||
},
|
||||
append: (audio) => ({ type: 'input_audio_buffer.append', audio }),
|
||||
// Qwen is a turn model: a frame is a picture added to the conversation,
|
||||
// not a track interleaved into the audio. That is exactly the OpenAI
|
||||
// Realtime image interface, so the camera sends conversation items
|
||||
@@ -68,7 +63,10 @@
|
||||
} });
|
||||
return messages;
|
||||
},
|
||||
commitMessages: () => vad ? [] : [{ type: 'input_audio_buffer.commit', final: true }],
|
||||
commitMessages: () => vad ? [] : [
|
||||
{ type: 'input_audio_buffer.commit' },
|
||||
{ type: 'response.create' },
|
||||
],
|
||||
ack: (responseId, playedMs) => vad ? { type: 'playback.ack', response_id: responseId,
|
||||
item_id: `item_${responseId}`, played_ms: playedMs, committed_ms: playedMs } : null,
|
||||
mapEvent(event) {
|
||||
@@ -87,30 +85,28 @@
|
||||
return { kind: 'interrupt' };
|
||||
}
|
||||
if (event.type === 'playback.acknowledged') return { kind: 'ack', committedMs: (event.event || event).committed_ms || 0 };
|
||||
if (event.type === 'response.output_text.delta' || event.type === 'transcription.delta') {
|
||||
// On the shipped STT route transcription.* is model-generated text,
|
||||
// not a separate ASR transcript of the user. See realtime_connection.py.
|
||||
if (event.type === 'response.output_text.delta') {
|
||||
return { kind: 'text', role: 'assistant', channel: 'text', text: event.delta || '' };
|
||||
}
|
||||
if (event.type === 'response.output_text.done' || event.type === 'transcription.done') {
|
||||
if (event.type === 'response.output_text.done') {
|
||||
return { kind: 'text-final', role: 'assistant', channel: 'text', text: event.text || '' };
|
||||
}
|
||||
if (vad && (event.type === 'output_audio_buffer.cleared' ||
|
||||
(event.type === 'response.done' && event.response?.status === 'cancelled'))) {
|
||||
return { kind: 'interrupt', responseId: event.response_id || event.response?.id || null };
|
||||
}
|
||||
if (event.type === 'input_audio_buffer.committed') return { kind: 'begin' };
|
||||
if (vad && event.type === 'input_audio_buffer.committed') return { kind: 'begin' };
|
||||
if (!vad && event.type === 'input_audio_buffer.cleared') return { kind: 'ignore' };
|
||||
if (event.type === 'input_audio_buffer.cleared') return { kind: 'backpressure', message: 'Input cleared. Model is answering; please wait.' };
|
||||
if (!vad && event.type === 'response.output_audio.done') return { kind: 'done' };
|
||||
const action = profiles.event(event);
|
||||
if (action.kind === 'error') {
|
||||
if (action.code === 'input_backpressure') return { ...action, kind: 'backpressure', message: 'Input buffer is full; please repeat dropped speech after the response.' };
|
||||
if (action.code === 'server_vad_initialization_failed') action.message = 'Server VAD could not load Silero. Configure the server artifact or restart this UI with --stt.';
|
||||
if (action.code === 'server_vad_initialization_failed') action.message = 'Server VAD could not load Silero. Configure the server artifact and restart the UI.';
|
||||
else if (vad && action.code === 'unsupported') action.message += ` ${hint}`;
|
||||
}
|
||||
return action;
|
||||
},
|
||||
connectionHint: vad ? hint : 'Check the Qwen /v1/realtime STT endpoint and model name.',
|
||||
connectionHint: vad ? hint : 'Check the Qwen turn-based /v1/realtime deployment and model name.',
|
||||
};
|
||||
};
|
||||
})(globalThis);
|
||||
@@ -74,12 +74,12 @@ def build_app(
|
||||
public_realtime_url: str | None = None,
|
||||
ref_audio: str | None = None,
|
||||
profile: str = "minicpm-native",
|
||||
adapter: str = "stt",
|
||||
turn_mode: str = "manual",
|
||||
) -> FastAPI:
|
||||
if profile not in {"minicpm-native", "qwen3-turn", "aura-ptt"}:
|
||||
raise ValueError(f"Unknown profile: {profile}")
|
||||
if adapter not in {"stt", "vad"}:
|
||||
raise ValueError(f"Unknown adapter: {adapter}")
|
||||
if turn_mode not in {"manual", "vad"}:
|
||||
raise ValueError(f"Unknown turn mode: {turn_mode}")
|
||||
if profile == "qwen3-turn" and ref_audio:
|
||||
raise ValueError("Qwen3 profile does not accept --ref-audio")
|
||||
if profile == "aura-ptt" and ref_audio:
|
||||
@@ -116,7 +116,7 @@ def build_app(
|
||||
{
|
||||
"model": model,
|
||||
"profile": profile,
|
||||
"adapter": adapter,
|
||||
"turnMode": turn_mode,
|
||||
"realtimePath": public_realtime_url or "v1/realtime",
|
||||
"refAudio": ref_audio_uri,
|
||||
"appVersion": app_version,
|
||||
@@ -190,9 +190,9 @@ def main(default_profile: str = "minicpm-native") -> None:
|
||||
parser.add_argument("--profile", choices=("minicpm-native", "qwen3-turn", "aura-ptt"), default=default_profile)
|
||||
parser.add_argument("--ws-backend", "--backend", dest="ws_backend")
|
||||
mode = parser.add_mutually_exclusive_group()
|
||||
mode.add_argument("--stt", dest="adapter", action="store_const", const="stt")
|
||||
mode.add_argument("--vad", dest="adapter", action="store_const", const="vad")
|
||||
parser.set_defaults(adapter="stt")
|
||||
mode.add_argument("--manual", dest="turn_mode", action="store_const", const="manual")
|
||||
mode.add_argument("--vad", dest="turn_mode", action="store_const", const="vad")
|
||||
parser.set_defaults(turn_mode="manual")
|
||||
parser.add_argument(
|
||||
"--public-realtime-url",
|
||||
help="Browser-visible ws:// or wss:// Realtime URL; defaults to the same-origin proxy.",
|
||||
@@ -225,7 +225,7 @@ def main(default_profile: str = "minicpm-native") -> None:
|
||||
public_realtime_url=args.public_realtime_url,
|
||||
ref_audio=args.ref_audio,
|
||||
profile=args.profile,
|
||||
adapter=args.adapter,
|
||||
turn_mode=args.turn_mode,
|
||||
),
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
# Launch vLLM-Omni server for CosyVoice3 TTS
|
||||
#
|
||||
# Usage:
|
||||
# ./run_server.sh
|
||||
# CUDA_VISIBLE_DEVICES=0 ./run_server.sh
|
||||
#
|
||||
# Streaming (async-chunk) is on by default via vllm_omni/deploy/cosyvoice3.yaml.
|
||||
# Streaming (async-chunk) is on by default. H200 selects the optimized profile;
|
||||
# other devices use vllm_omni/deploy/cosyvoice3.yaml.
|
||||
# Set NO_ASYNC_CHUNK=1 to use the legacy synchronous path.
|
||||
|
||||
set -e
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
# Issue 5 probe: int8 quantized DiT serve paths on the RTX 3090 (24 GB)
|
||||
|
||||
## Round two (owner directive + amendment): W8A8 int8-dynamic — the capacity route is YES
|
||||
|
||||
The 08:37 amendment narrowed the remaining work to one decisive experiment:
|
||||
a compressed-tensors **W8A8 int8-dynamic** artifact (int8 weights + dynamic
|
||||
per-token symmetric int8 activations, int-quantized format) whose kernel on
|
||||
sm_86 is the cutlass int8 scaled-MM — no Marlin anywhere in the path.
|
||||
|
||||
**Result: the non-Marlin int8 path works end-to-end.** The artifact serves,
|
||||
passes the structure gate on all 16 matrix requests with healthy numbers,
|
||||
and lands at BF16-parity PSNR — including the first usable four-reference
|
||||
quality comparison this card has ever produced:
|
||||
|
||||
| Refs | Wall (mean, s) | Peak VRAM | Opacity / alpha | PSNR vs sealed Diffusers (dB) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | 20.5 | 23,879 MiB (run max) | 0.984–0.986 / 255.0 | 33.72 |
|
||||
| 2 | 25.1 | ” | 0.947–0.966 / 254.9–255.0 | 28.34 |
|
||||
| 3 | 29.4 | ” | 0.965–0.988 / 255.0 | (no sealed row; derived fixture) |
|
||||
| 4 | 34.0 | ” | 0.967–0.976 / 255.0 | **27.50** |
|
||||
|
||||
Comparison rows: BF16 served 1–2 refs at 34.2/36.7 s and OOMs at 4;
|
||||
online-FP8 and int8-wo-Marlin run 29.9–46.4 s but produce collapsed output;
|
||||
**W8A8-dynamic is both the fastest row measured (4-ref 34.0 s ≈ 4.3× the
|
||||
sealed Diffusers nocache baseline's 145.8 s) and the only structurally sound
|
||||
quantized row.** 3–4 references at cached speed on one 3090: delivered.
|
||||
|
||||
Gate calibration note: round one's pass rule (`opaque ≥ 0.99`) was an
|
||||
instrument calibration derived from the broken (0.008–0.20) vs BF16 (1.0)
|
||||
contrast. The W8A8 outputs sit at 0.95–0.99 opaque with alpha mean ≈255 and
|
||||
healthy RGB spread — soft-edge alpha, not the broken signature — so the
|
||||
recorded verdict rests on the raw numbers, visual inspection (four-character
|
||||
apothecary scene and the 1-ref portrait both render faithfully), and PSNR in
|
||||
the 26–29 dB BF16 band. The raw per-request structure metrics are in
|
||||
`evidence/rtx3090-round2/` unedited.
|
||||
|
||||
Kernel-path verification for this round (`w8a8_isolate.py`,
|
||||
`cutlass_ops_probe.py`): the raw ops (`scaled_int8_quant` +
|
||||
`cutlass_scaled_mm`) match a float32 reference at 0.17% rel-L2 across
|
||||
(64,256,128) / (512,4096,4096) / (512,4096,12288), and the full layer path
|
||||
(load → transpose-repack → apply, fused QKV and row-parallel, synthetic
|
||||
weights, loaded bitwise-verified) matches at 0.17% too. (Round-two lesson
|
||||
recorded for the instrument: an intermediate 97%-error scare in
|
||||
`w8a8_isolate.py` was a missing ×act_scale factor in the probe's own
|
||||
reference, not a kernel defect — the out/ref norm ratio equaled E[act_scale]
|
||||
exactly, which is what gave it away.)
|
||||
|
||||
Artifacts (outside Git): `…/artifacts/qwen-image-2.1-dit-w8a8dyn/` (int8
|
||||
weights unpacked + fp32 per-channel scales, 224 tensors, 7.26 GiB, manifest
|
||||
with digests). The round-one int8-wo artifact stays for the Marlin evidence.
|
||||
|
||||
The upstream Marlin report is drafted and ready to file in the #3 lane on
|
||||
the owner's call: `UPSTREAM-REPORT-DRAFT.md` (capability-query overclaim +
|
||||
NaN kernel + synthetic repro + the PR #4 FP8 convergence). The hard-stop
|
||||
branch did not trigger.
|
||||
|
||||
Helper-system follow-through for multi-model-helper-server #27 (input only):
|
||||
the 3090's serving-profile candidacy now extends to 3–4-reference profiles
|
||||
via the W8A8-dynamic artifact — 4-ref at 34.0 s/image, peak 23.9 GiB,
|
||||
BF16-parity PSNR (27.5 dB 4-ref vs the sealed int8-DiT Diffusers baseline),
|
||||
provided serving ships the artifact and the branch's two loader/factory
|
||||
fixes. This supersedes round one's "blocked on Marlin" conclusion.
|
||||
|
||||
## Round one: int8 weight-only via compressed-tensors — capacity yes, Marlin W8A16 collapses
|
||||
|
||||
Probed 2026-10-08 on `cachyos-alexander` (RTX 3090, sm_86, driver 615.71.09,
|
||||
CUDA 13.4), at upstream main `8484d85d1` plus this branch's two loader/factory
|
||||
fixes, vLLM-Omni 0.31.0 recipe (Python 3.13 venv, torch 2.13.0+cu130,
|
||||
transformers 5.14.1, diffusers git `122b1e11`, compressed-tensors 0.17.0).
|
||||
The DiT was quantized **offline** from the sealed snapshot
|
||||
`Qwen/Qwen-Image-2.1@790c9263` to int8 weight-only (W8A16), per-output-channel
|
||||
symmetric, stored as a compressed-tensors pack-quantized checkpoint; encoder
|
||||
and VAE stay BF16 on disk via the artifact's per-component config wrapper.
|
||||
The probe mirrors the sealed Diffusers attempt `2026-10-07T170729Z-q3-4r`
|
||||
fixture-for-fixture (same frozen references-suite-2 images, same rendered
|
||||
prompts, seeds 2301–2304, 20 steps, `true_cfg_scale 1.0`).
|
||||
|
||||
## Verdict (superseded by round two above — kept as the round-one record)
|
||||
|
||||
**Capacity: delivered. Quality: collapsed in the serve kernel — the int8
|
||||
sidestep does not rescue 3–4 references on this card with this stack, and the
|
||||
blocker is now sharply isolated: the 8-bit Marlin W8A16 path itself.**
|
||||
|
||||
- Every step of the artifact chain is verified correct: the quantizer's
|
||||
round-trip reproduces the sealed BF16 weights at ~1.1% rel-L2
|
||||
(`evidence/rtx3090/artifact-verify.txt`), and the serve path loads the
|
||||
packed tensors **bitwise-identically** — including the fused q/k/v stacked
|
||||
shards — with finite scales before and after the Marlin repack.
|
||||
- Serving works mechanically: init ~40 s, all 16 capacity-matrix requests
|
||||
(1/2/3/4 references × seeds 2301–2304) complete at BF16-comparable speed
|
||||
(4-ref 46.4 s; BF16 OOMs at 4 refs, online-FP8 was 44.3 s), peak 23,935 MiB.
|
||||
- But the output is structurally broken on **every** counted run (gate FAIL
|
||||
16/16: opaque 0.0–0.20 vs 1.0 at BF16; PSNR vs the sealed baseline
|
||||
7.1–7.5 dB — the same collapsed band as online FP8). The images are
|
||||
horizontal-stripe noise.
|
||||
- A one-layer offline isolation (`marlin_isolate.py`) pins it: vLLM parallel
|
||||
linears built exactly as the qwen_image_21 transformer builds them, loaded
|
||||
with the artifact's verified tensors, emit **NaN** for all four layer
|
||||
geometries (fused QKV, to_out, img_mlp proj/out), for both bf16 and fp16
|
||||
activations — and do the same for **synthetic random int8 weights**, so the
|
||||
checkpoint and its quantization are exonerated. The 8-bit Marlin W8A16 path
|
||||
on this stack (sm_86, vllm 0.31.0, torch 2.13.0+cu130) is broken even though
|
||||
`query_marlin_supported_quant_types` accepts `uint8b128` on the card, and
|
||||
`choose_mp_linear_kernel` offers no non-Marlin W8A16 fallback on sm_86
|
||||
(Machete is sm90+, ExLlama int4-only).
|
||||
|
||||
The torchao-int8-in-Diffusers prior (multi-model-helper-server #160, blinded
|
||||
review within label noise of BF16) stands unchallenged: int8 quantization is
|
||||
fine; the serve-path kernel is not. The online-FP8 collapse from PR #4 and
|
||||
this int8 collapse now share one root suspect — the Marlin mixed-precision
|
||||
path on this fork/card — with the fp8 case previously isolated to the
|
||||
Marlin-quantized DiT and this run extending it to int8 with a fully verified
|
||||
checkpoint on the far side of the kernel.
|
||||
|
||||
## Two serve-path fixes this issue required (in this PR)
|
||||
|
||||
1. **Quant factory could not build compressed-tensors at all.**
|
||||
`_normalize_method_name` canonicalizes `compressed-tensors` to
|
||||
`compressed_tensors`, which then missed vLLM's hyphenated registry — so
|
||||
both `--quantization compressed-tensors` and checkpoint auto-detection
|
||||
raised "Unknown quantization method". The registry lookup now resolves
|
||||
both spellings, and registry configs whose `__init__` does not take the
|
||||
serialized schema fall back to `from_config` (`vllm_omni/quantization/factory.py`,
|
||||
tests in `tests/quantization/test_quant_factory.py`).
|
||||
2. **The diffusion loader misclassified pre-quantized checkpoints as online
|
||||
quantization under CPU offload.** With `--enable-cpu-offload` it loaded
|
||||
every component on the GPU before offloading (encoder 17 GiB + VAE + the
|
||||
packed DiT skeleton > 24 GiB → init OOM). Serialized formats are now
|
||||
detected (`is_prequantized_config`, component-wrapper aware) and load
|
||||
directly on the host, with each quantized module round-tripped through the
|
||||
accelerator only for the post-load Marlin repack
|
||||
(`vllm_omni/diffusion/model_loader/diffusers_loader.py`). With these two
|
||||
fixes the artifact serves: init healthy in ~40 s, logs
|
||||
`Per-component quantization: {'transformer': 'compressed-tensors'}`.
|
||||
|
||||
## Artifact (outside Git, under the helper state root)
|
||||
|
||||
`~/.local/state/multi-model-helper-server/state/image-service/artifacts/qwen-image-2.1-dit-int8wo/`
|
||||
— quantized `transformer/` (6.76 GiB packed int8 + BF16 boundary layers;
|
||||
224 tensors = 32 blocks × {to_q,to_k,to_v,to_out.0,proj,gate_layer,out};
|
||||
6.98 B params quantized, 0.14 B BF16), `model_index.json`/`scheduler`/
|
||||
`text_encoder`/`vae`/`processor` symlinked to the sealed snapshot, and
|
||||
`MANIFEST.json` with inputs (snapshot path, revision, tree digest), versions,
|
||||
per-file sha256, and quantization stats (mean rel-L2 1.10%, max 1.73%).
|
||||
Built by `quantize_dit_int8wo.py` using compressed-tensors' own
|
||||
`pack_to_int32` (uint8 biased by 128, 4 per int32) and `QuantizationConfig`
|
||||
serialization, so the storage convention matches llm-compressor bit-for-bit.
|
||||
`transformer/config.json` carries the quantization metadata wrapped
|
||||
per-component (`{"transformer": {...}}`) so auto-detection quantizes the DiT
|
||||
only — the encoder and VAE resolve to None and stay BF16
|
||||
(the plain global form would hand the encoder a compressed-tensors config
|
||||
it has no scales for).
|
||||
|
||||
## Run matrix — RTX 3090
|
||||
|
||||
Single run `int8wo`: `vllm serve <artifact> --omni --port 8091
|
||||
--served-model-name Qwen/Qwen-Image-2.1 --enforce-eager
|
||||
--enable-cpu-offload`, readiness via `/health`, VRAM via the 1 Hz
|
||||
`nvidia-smi` series (issue 2's instrument). Two precursor runs OOM'd at init
|
||||
for reasons fixed on this branch (see Verdict item 2); their logs survive
|
||||
under `runs/int8wo-expandable/` on the host (the first attempt's log was
|
||||
overwritten by the successful rerun in the same label).
|
||||
|
||||
| Refs | Fixture | Wall (mean, s) | Peak (MiB) | Gate | PSNR vs Diffusers (dB) |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | edit-expression-ysolde | 31.0 | 23,935 (run max) | FAIL 4/4 | 7.14 |
|
||||
| 2 | edit-compose-pair | 36.6 | ” | FAIL 4/4 | 7.46 |
|
||||
| 3 | edit-compose-three | 41.4 | ” | FAIL 4/4 | no sealed row (derived fixture) |
|
||||
| 4 | edit-compose-four | 46.4 | ” | FAIL 4/4 | 7.11 |
|
||||
|
||||
BF16 comparison rows (issue 2): 1-ref 34.2 s, 2-ref 36.7 s, 4-ref OOM.
|
||||
Online-fp8 (broken output, capacity reference): 29.9 / 34.1 / 38.9 / 44.3 s.
|
||||
So the int8 artifact matches the fp8 capacity/speed profile exactly —
|
||||
including its broken-output signature (PSNR band 7.0–7.5 dB) — while the
|
||||
checkpoint side is proven numerically sound, moving the defect wholly into
|
||||
the kernel.
|
||||
|
||||
## Structure gate and quality read
|
||||
|
||||
- Gate rule (issue 1's lesson): `opaque_fraction ≥ 0.99` and `mean(rgb_std)
|
||||
≥ 10`; BF16 outputs measure 1.0 opaque, 26–29 dB. Every int8-artifact run
|
||||
fails (opaque 0.0–0.20, alpha mean 114–137; rgb_std spans 3–93 — several
|
||||
outputs have healthy RGB spread yet near-total transparency, and one
|
||||
(seed 2302) is near-flat grey like the fp8 signature). Outputs are
|
||||
horizontal-stripe noise on visual inspection.
|
||||
- PSNR vs the sealed 4-reference Diffusers baseline (RGBA-on-RGBA,
|
||||
#1's comparator): 7.14 / 7.46 / 7.11 dB mean for 1/2/4 refs — the
|
||||
collapsed band, not the 26–29 dB BF16-row parity the issue hoped for.
|
||||
|
||||
## Kernel isolation (the issue's "different kernels" prior, answered)
|
||||
|
||||
`marlin_isolate.py` builds vLLM `QKVParallelLinear` / `RowParallelLinear` /
|
||||
`ColumnParallelLinear` exactly as `qwen_image_21_transformer.py` does, loads
|
||||
the artifact tensors through the production weight-loader machinery
|
||||
(bitwise-verified after load, fused q/k/v shards included), runs
|
||||
`process_weights_after_loading` (the Marlin repack) on the GPU, and compares
|
||||
`layer(x)` against `float32 x @ dequant(W)ᵀ` computed from the artifact's own
|
||||
unpacked weights:
|
||||
|
||||
| Layer | bf16 acts | fp16 acts | packed bitwise | scales post-repack |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| attn.to_qkv (fused) | NaN | NaN | equal | finite (6.6e-4…1.2e-2) |
|
||||
| attn.to_out.0 (row) | NaN | NaN | equal | finite |
|
||||
| img_mlp.proj (column) | NaN | NaN | equal | finite |
|
||||
| img_mlp.out (row, wide-in) | NaN | NaN | equal | finite |
|
||||
|
||||
Synthetic random int8 weights reproduce the NaN identically, and the runtime
|
||||
capability query accepts `uint8b128` on this card — so this is a defect of
|
||||
the 8-bit Marlin W8A16 path on this stack (repack or kernel; sm_86,
|
||||
vllm 0.31.0, torch 2.13.0+cu130), not of the checkpoint, the quantizer, the
|
||||
loader, or the fused-QKV stacking. No alternative W8A16 kernel exists on
|
||||
sm_86 in this build to switch to.
|
||||
|
||||
## Reproducibility
|
||||
|
||||
Scripts in this directory: `quantize_dit_int8wo.py` (artifact + manifest),
|
||||
`verify_artifact.py` (round-trip check), `serve.sh` / `probe.py` (adapted
|
||||
issue-2 kit; probe records the structure gate inline per request), `gate.py`
|
||||
(gate + capacity summarizer), `psnr.py`, `marlin_isolate.py` (kernel
|
||||
isolation, `--synthetic` for the stack-only variant). All were pushed
|
||||
verbatim to `~/Projects/vllm-omni-val/issue5/` on the host and run from the
|
||||
validation clone checked out on this branch. Per-run serve/vram logs and all
|
||||
output PNGs live under `~/Projects/vllm-omni-val/issue5/runs/int8wo/`;
|
||||
condensed evidence in `evidence/rtx3090/`.
|
||||
|
||||
## Helper-system follow-through (input to multi-model-helper-server #27 — not implemented here)
|
||||
|
||||
- The 3090's 3–4 reference serving path remains blocked, now with a single
|
||||
named suspect: the 8-bit Marlin W8A16 kernel path on sm_86 (this fork's
|
||||
vllm 0.31.0 / torch 2.13.0 stack). Both quantized routes (online FP8,
|
||||
offline int8 compressed-tensors with a verified checkpoint) collapse at the
|
||||
same place. The unlock order for #27 candidacy is now: upstream Marlin
|
||||
8-bit fix (or a non-Marlin W8A16 kernel on sm_86), then serving — capacity
|
||||
and speed are already proven sufficient (4-ref at 46.4 s, 2.9× the sealed
|
||||
Diffusers nocache baseline).
|
||||
- The two vllm-omni loader/factory fixes on this branch are prerequisites for
|
||||
any compressed-tensors artifact serving under CPU offload and are
|
||||
independent of the kernel defect.
|
||||
|
||||
## Out-of-scope confirmation
|
||||
|
||||
No upstream PRs; no W8A8/int4/NVFP4 artifacts built; no helper-system
|
||||
deployment evidence. Root-causing the Marlin 8-bit defect remains the
|
||||
separate lane (PR #4 carries the fp8 isolation; this branch adds the int8 +
|
||||
synthetic + bitwise-load isolation). The only code changes are the two
|
||||
serve-path fixes required to make the issue's plan steps executable at all.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Upstream report draft: Marlin 8-bit W8A16 yields NaN on sm_86 while the capability query accepts it
|
||||
|
||||
Status: DRAFT — prepared for the #3 upstream lane; file when the owner calls it.
|
||||
Evidence: issue 5 round one (this directory) + PR #4's online-FP8 isolation.
|
||||
Reproduction: `probe/issue-5/marlin_isolate.py --synthetic` (no checkpoint needed).
|
||||
|
||||
## Summary
|
||||
|
||||
On vLLM 0.31.0 (torch 2.13.0+cu130, CUDA 13.4, RTX 3090 / sm_86, driver
|
||||
615.71.09), the 8-bit Marlin W8A16 path selected for compressed-tensors
|
||||
int8 weight-only checkpoints produces NaN outputs for every tested layer
|
||||
geometry, activation dtype, and input distribution — including synthetic
|
||||
random weights — even though `query_marlin_supported_quant_types` accepts
|
||||
`uint8b128` on the device and `choose_mp_linear_kernel` has no other W8A16
|
||||
candidate on sm_86 (Machete is sm90+, ExLlama int4-only).
|
||||
|
||||
## Isolation chain (each step independently verified)
|
||||
|
||||
1. **Checkpoint exonerated.** The int8 weight-only compressed-tensors
|
||||
artifact round-trips to the sealed BF16 weights at ~1.1% rel-L2 using
|
||||
compressed-tensors' own `unpack_from_int32` + dequantize
|
||||
(`verify_artifact.py`, output in `evidence/rtx3090/artifact-verify.txt`).
|
||||
2. **Loading exonerated.** `marlin_isolate.py` loads the artifact's
|
||||
`weight_packed` tensors through the production vLLM weight-loader
|
||||
machinery into `QKVParallelLinear` / `RowParallelLinear` /
|
||||
`ColumnParallelLinear` built exactly as the qwen_image_21 transformer
|
||||
builds them; after loading, the parameters are **bitwise-equal** to the
|
||||
checkpoint (including the fused q/k/v stacked shards), and scales are
|
||||
finite before and after `process_weights_after_loading`.
|
||||
3. **Kernel indicted.** `layer(x)` is NaN for all four layer geometries
|
||||
(fused 12288-out QKV, 4096-out row, 12288-out column, wide-in row), for
|
||||
both bf16 and fp16 activations, at M=512.
|
||||
4. **Checkpoint-independence.** `--synthetic` (random int8 weights + random
|
||||
per-channel scales, quantized and packed in-memory) reproduces the NaN
|
||||
identically — no checkpoint, quantizer, or scale file involved.
|
||||
5. **Same-family serve evidence.** PR #4's online-FP8 path (also Marlin
|
||||
W8A16 on sm_86) produces structurally broken output (≈97% transparent
|
||||
RGBA, PSNR 7 dB vs BF16) across every reference count; transformer-only
|
||||
FP8 isolates the collapse to the Marlin-quantized DiT. Two independent
|
||||
quantizers, one kernel family.
|
||||
|
||||
## Suspicion boundary (what is and is not pinned)
|
||||
|
||||
The NaN arises between `gptq_marlin_repack`/`marlin_permute_scales` and
|
||||
`torch.ops.vllm.marlin_gemm` — the isolate stops at that boundary. Not yet
|
||||
dissected: whether the repacked weight bytes are wrong (repack/layout bug
|
||||
for 8-bit on sm_86) or the kernel itself miscomputes from correct bytes.
|
||||
The 4-bit Marlin path was not tested (out of scope for issue 5).
|
||||
|
||||
## Minimal reproduction (from this repo)
|
||||
|
||||
```sh
|
||||
python probe/issue-5/marlin_isolate.py --synthetic # NaN, all geometries
|
||||
```
|
||||
|
||||
Requires: vLLM 0.31.0 importable with CUDA visible; ~2 GiB GPU memory.
|
||||
Expected on an unaffected stack: rel-L2 ≈ 0.01 per layer.
|
||||
Observed on the affected stack: NaN for every layer, both activation dtypes
|
||||
(`evidence/rtx3090/marlin-isolate-synthetic.json`).
|
||||
|
||||
## Context that may matter upstream
|
||||
|
||||
- torch 2.13.0+cu130 / CUDA 13.4 is a bleeding-edge combination; if CI
|
||||
covers Marlin int8 on sm_80/86 with an older stack, a torch/CUDA 13
|
||||
interaction is a candidate.
|
||||
- The 4-bit Marlin path is heavily exercised by CI; the 8-bit
|
||||
(`uint8b128`) path is comparatively new. If 8-bit Marlin support was
|
||||
validated only on sm_80/sm_90, sm_86 may be untested.
|
||||
- The capability query accepting `uint8b128` on sm_86 while the kernel
|
||||
yields NaN is the dispatch-overclaim angle: either the query or the
|
||||
kernel is wrong for this cell.
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5 round two: raw-op probe of the cutlass W8A8 int8 path on this stack.
|
||||
|
||||
Bypasses every layer/loader and tests exactly what CutlassInt8ScaledMMLinearKernel
|
||||
calls: ops.scaled_int8_quant (dynamic per-token symmetric) then
|
||||
ops.cutlass_scaled_mm, against a float32 reference. A failure here means the
|
||||
cutlass int8 kernels themselves are broken on this stack (CUDA 13.4 / sm_86 /
|
||||
torch 2.13.0+cu130 / vllm 0.31.0), independent of any checkpoint or loader.
|
||||
|
||||
Usage: cutlass_ops_probe.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import torch
|
||||
from vllm import _custom_ops as ops
|
||||
|
||||
|
||||
def rel(a: torch.Tensor, b: torch.Tensor) -> float:
|
||||
return float((a - b).norm() / b.norm())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
torch.manual_seed(2301)
|
||||
device = torch.device("cuda")
|
||||
results = []
|
||||
|
||||
for m, k, n in ((64, 256, 128), (512, 4096, 4096), (512, 4096, 12288)):
|
||||
w = torch.randn(n, k, device=device)
|
||||
w_scale = w.abs().amax(dim=1, keepdim=True).clamp_min(1e-12) / 127.0
|
||||
w_q = torch.round(w / w_scale).clamp(-127, 127).to(torch.int8)
|
||||
|
||||
x = torch.randn(m, k, dtype=torch.bfloat16, device=device)
|
||||
x_q, x_s, _ = ops.scaled_int8_quant(x.contiguous(), None, None, symmetric=True)
|
||||
|
||||
# Reference: dequantized per-token acts @ dequantized per-channel weights.
|
||||
ref = (x_q.float() * x_s.float()) @ (w_q.float() * w_scale.float()).T
|
||||
|
||||
w_q_t = w_q.t()
|
||||
out = ops.cutlass_scaled_mm(
|
||||
x_q, w_q_t, scale_a=x_s, scale_b=w_scale, out_dtype=torch.bfloat16
|
||||
).float()
|
||||
|
||||
# Also check the quantizer op itself against a float32 reference.
|
||||
act_scale_ref = x.float().abs().amax(dim=1, keepdim=True).clamp_min(1e-12) / 127.0
|
||||
x_q_ref = torch.round(x.float() / act_scale_ref).clamp(-127, 127).to(torch.int8)
|
||||
quant_match = bool(torch.equal(x_q, x_q_ref)) and bool(
|
||||
torch.allclose(x_s.float(), act_scale_ref, rtol=1e-3, atol=1e-7)
|
||||
)
|
||||
|
||||
results.append({
|
||||
"mk": [m, k, n],
|
||||
"quant_op_matches_reference": quant_match,
|
||||
"gemm_rel_l2": round(rel(out, ref), 5),
|
||||
"out_finite": bool(torch.isfinite(out).all()),
|
||||
"scale_a_shape": list(x_s.shape),
|
||||
"scale_b_shape": list(w_scale.shape),
|
||||
})
|
||||
|
||||
print(json.dumps(results, indent=1))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,56 @@
|
||||
[
|
||||
{
|
||||
"mk": [
|
||||
64,
|
||||
256,
|
||||
128
|
||||
],
|
||||
"quant_op_matches_reference": false,
|
||||
"gemm_rel_l2": 0.00165,
|
||||
"out_finite": true,
|
||||
"scale_a_shape": [
|
||||
64,
|
||||
1
|
||||
],
|
||||
"scale_b_shape": [
|
||||
128,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"mk": [
|
||||
512,
|
||||
4096,
|
||||
4096
|
||||
],
|
||||
"quant_op_matches_reference": false,
|
||||
"gemm_rel_l2": 0.00166,
|
||||
"out_finite": true,
|
||||
"scale_a_shape": [
|
||||
512,
|
||||
1
|
||||
],
|
||||
"scale_b_shape": [
|
||||
4096,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"mk": [
|
||||
512,
|
||||
4096,
|
||||
12288
|
||||
],
|
||||
"quant_op_matches_reference": false,
|
||||
"gemm_rel_l2": 0.00166,
|
||||
"out_finite": true,
|
||||
"scale_a_shape": [
|
||||
512,
|
||||
1
|
||||
],
|
||||
"scale_b_shape": [
|
||||
12288,
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"layer": "attn.to_qkv (fused q/k/v)",
|
||||
"rel_l2": 0.00171,
|
||||
"out_finite": true
|
||||
},
|
||||
{
|
||||
"layer": "attn.to_out.0 (row)",
|
||||
"rel_l2": 0.00172,
|
||||
"out_finite": true,
|
||||
"loaded_bitwise_ok": true,
|
||||
"loaded_scale_ok": true,
|
||||
"rel_l2_vs_layer_weight": 0.00172
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,118 @@
|
||||
== int8w8a8dyn ==
|
||||
1-ref: n=4 gate=FAIL wall 20.4-20.6s (mean 20.5s)
|
||||
edit-expression-ysolde s2301: 20.5s {"opaque_fraction": 0.9847, "alpha_mean": 255.0, "rgb_std": [45.6, 44.3, 47.2]} BROKEN
|
||||
edit-expression-ysolde s2302: 20.43s {"opaque_fraction": 0.9861, "alpha_mean": 255.0, "rgb_std": [47.0, 45.3, 48.5]} BROKEN
|
||||
edit-expression-ysolde s2303: 20.52s {"opaque_fraction": 0.9844, "alpha_mean": 255.0, "rgb_std": [46.5, 45.2, 48.1]} BROKEN
|
||||
edit-expression-ysolde s2304: 20.56s {"opaque_fraction": 0.9854, "alpha_mean": 255.0, "rgb_std": [46.9, 44.8, 47.7]} BROKEN
|
||||
2-ref: n=4 gate=FAIL wall 25.0-25.2s (mean 25.1s)
|
||||
edit-compose-pair s2301: 25.02s {"opaque_fraction": 0.9638, "alpha_mean": 255.0, "rgb_std": [69.4, 70.2, 71.8]} BROKEN
|
||||
edit-compose-pair s2302: 25.11s {"opaque_fraction": 0.9659, "alpha_mean": 255.0, "rgb_std": [61.9, 63.6, 65.7]} BROKEN
|
||||
edit-compose-pair s2303: 25.16s {"opaque_fraction": 0.9572, "alpha_mean": 255.0, "rgb_std": [61.6, 63.2, 64.9]} BROKEN
|
||||
edit-compose-pair s2304: 25.17s {"opaque_fraction": 0.9471, "alpha_mean": 254.9, "rgb_std": [57.5, 55.8, 55.6]} BROKEN
|
||||
3-ref: n=4 gate=FAIL wall 29.4-29.5s (mean 29.4s)
|
||||
edit-compose-three s2301: 29.37s {"opaque_fraction": 0.9785, "alpha_mean": 255.0, "rgb_std": [42.9, 32.4, 23.1]} BROKEN
|
||||
edit-compose-three s2302: 29.44s {"opaque_fraction": 0.9882, "alpha_mean": 255.0, "rgb_std": [37.9, 30.5, 21.6]} BROKEN
|
||||
edit-compose-three s2303: 29.47s {"opaque_fraction": 0.9757, "alpha_mean": 255.0, "rgb_std": [48.5, 40.2, 30.1]} BROKEN
|
||||
edit-compose-three s2304: 29.49s {"opaque_fraction": 0.9645, "alpha_mean": 255.0, "rgb_std": [48.5, 35.7, 25.1]} BROKEN
|
||||
4-ref: n=4 gate=FAIL wall 34.0-34.1s (mean 34.0s)
|
||||
edit-compose-four s2301: 34.08s {"opaque_fraction": 0.976, "alpha_mean": 255.0, "rgb_std": [49.0, 37.3, 29.3]} BROKEN
|
||||
edit-compose-four s2302: 34.0s {"opaque_fraction": 0.9713, "alpha_mean": 255.0, "rgb_std": [47.2, 39.5, 29.8]} BROKEN
|
||||
edit-compose-four s2303: 34.02s {"opaque_fraction": 0.9706, "alpha_mean": 255.0, "rgb_std": [56.4, 44.8, 35.4]} BROKEN
|
||||
edit-compose-four s2304: 34.04s {"opaque_fraction": 0.9674, "alpha_mean": 255.0, "rgb_std": [50.9, 38.3, 29.5]} BROKEN
|
||||
nvidia-smi peak: 23879 MiB; server peak: None MB
|
||||
STRUCTURE GATE: FAIL
|
||||
|
||||
== psnr edit-expression-ysolde ==
|
||||
{
|
||||
"int8w8a8dyn": {
|
||||
"rows": [
|
||||
{
|
||||
"seed": 2301,
|
||||
"psnr_db": 33.45,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2302,
|
||||
"psnr_db": 34.47,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2303,
|
||||
"psnr_db": 34.68,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2304,
|
||||
"psnr_db": 32.26,
|
||||
"modes": "RGBA/RGBA"
|
||||
}
|
||||
],
|
||||
"mean_db": 33.72,
|
||||
"min_db": 32.26,
|
||||
"max_db": 34.68,
|
||||
"n": 4
|
||||
}
|
||||
}
|
||||
== psnr edit-compose-pair ==
|
||||
{
|
||||
"int8w8a8dyn": {
|
||||
"rows": [
|
||||
{
|
||||
"seed": 2301,
|
||||
"psnr_db": 31.94,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2302,
|
||||
"psnr_db": 27.97,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2303,
|
||||
"psnr_db": 23.42,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2304,
|
||||
"psnr_db": 30.05,
|
||||
"modes": "RGBA/RGBA"
|
||||
}
|
||||
],
|
||||
"mean_db": 28.34,
|
||||
"min_db": 23.42,
|
||||
"max_db": 31.94,
|
||||
"n": 4
|
||||
}
|
||||
}
|
||||
== psnr edit-compose-four ==
|
||||
{
|
||||
"int8w8a8dyn": {
|
||||
"rows": [
|
||||
{
|
||||
"seed": 2301,
|
||||
"psnr_db": 28.01,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2302,
|
||||
"psnr_db": 25.7,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2303,
|
||||
"psnr_db": 29.82,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2304,
|
||||
"psnr_db": 26.48,
|
||||
"modes": "RGBA/RGBA"
|
||||
}
|
||||
],
|
||||
"mean_db": 27.5,
|
||||
"min_db": 25.7,
|
||||
"max_db": 29.82,
|
||||
"n": 4
|
||||
}
|
||||
}
|
||||
23879 MiB, 100 %
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"schema": "issue5-w8a8dyn-v1",
|
||||
"created_utc": "2026-10-08T12:52:49+00:00",
|
||||
"description": "Qwen-Image-2.1 DiT w8a8dyn W8A8 int8-dynamic, int-quantized, compressed-tensors",
|
||||
"inputs": {
|
||||
"snapshot": "/home/anodyine/.local/state/multi-model-helper-server/state/image-service/weights/hub/models--Qwen--Qwen-Image-2.1/snapshots/790c92633540aa0cb11d9abf19eb46d861714758",
|
||||
"revision": "790c92633540aa0cb11d9abf19eb46d861714758",
|
||||
"tree_manifest": "/home/anodyine/.local/state/multi-model-helper-server/state/image-service/weights/hub/models--Qwen--Qwen-Image-2.1/trees/790c92633540aa0cb11d9abf19eb46d861714758.json",
|
||||
"tree_sha256": "1f35d0a5a99f2c79bf2a6818b96582828094feb8544a992bc81c1d4100aed436"
|
||||
},
|
||||
"versions": {
|
||||
"python": "3.13.15",
|
||||
"torch": "2.13.0+cu130",
|
||||
"compressed_tensors": "0.17.0"
|
||||
},
|
||||
"quantization": {
|
||||
"method": "compressed-tensors",
|
||||
"format": "pack-quantized",
|
||||
"weights": "int8 symmetric per-output-channel, static, minmax",
|
||||
"config": {
|
||||
"config_groups": {
|
||||
"group_0": {
|
||||
"targets": [
|
||||
"Linear"
|
||||
],
|
||||
"weights": {
|
||||
"num_bits": 8,
|
||||
"type": "int",
|
||||
"symmetric": true,
|
||||
"group_size": null,
|
||||
"strategy": "channel",
|
||||
"block_structure": null,
|
||||
"dynamic": false,
|
||||
"actorder": null,
|
||||
"scale_dtype": null,
|
||||
"zp_dtype": null,
|
||||
"observer": "minmax",
|
||||
"observer_kwargs": {}
|
||||
},
|
||||
"input_activations": {
|
||||
"num_bits": 8,
|
||||
"type": "int",
|
||||
"symmetric": true,
|
||||
"group_size": null,
|
||||
"strategy": "token",
|
||||
"block_structure": null,
|
||||
"dynamic": true,
|
||||
"actorder": null,
|
||||
"scale_dtype": null,
|
||||
"zp_dtype": null,
|
||||
"observer": null,
|
||||
"observer_kwargs": {}
|
||||
},
|
||||
"output_activations": null,
|
||||
"format": null
|
||||
}
|
||||
},
|
||||
"quant_method": "compressed-tensors",
|
||||
"kv_cache_scheme": null,
|
||||
"format": "int-quantized",
|
||||
"quantization_status": "frozen",
|
||||
"global_compression_ratio": null,
|
||||
"ignore": [
|
||||
"img_in",
|
||||
"txt_in",
|
||||
"time_text_embed",
|
||||
"modulation",
|
||||
"norm_out",
|
||||
"proj_out"
|
||||
]
|
||||
},
|
||||
"ignored_layers": [
|
||||
"img_in",
|
||||
"txt_in",
|
||||
"time_text_embed",
|
||||
"modulation",
|
||||
"norm_out",
|
||||
"proj_out"
|
||||
],
|
||||
"note": "ignore list mirrors the vllm-omni model's quant_config=None boundary layers"
|
||||
},
|
||||
"stats": {
|
||||
"quantized_tensors": 224,
|
||||
"quantized_params": 6979321856,
|
||||
"bf16_params": 135802880,
|
||||
"mean_rel_l2": 0.010959165191577216,
|
||||
"max_rel_l2": 0.017284497618675232,
|
||||
"weights_file_bytes": 7256754928
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"path": "transformer/config.json",
|
||||
"bytes": 1786,
|
||||
"sha256": "321512e271d79f729790890a64773464baafe7e7c702fc10442de89f79a7d163"
|
||||
},
|
||||
{
|
||||
"path": "transformer/diffusion_pytorch_model.safetensors",
|
||||
"bytes": 7256754928,
|
||||
"sha256": "ab2814308c55eb95c648aad94d47f59c28386113677767d791fea72a4506ce5c"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,978 @@
|
||||
[
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2301,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 20.5,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "31af175ab74c8a86746d7a69df771fd62552def574cc08b2a673220c69f302da",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9847,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
45.6,
|
||||
44.3,
|
||||
47.2
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 20433.584690093994,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1021.6792345046997,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 20433.70294570923,
|
||||
"image_time_to_first_output_ms": 20433.545112609863,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.12007599434582517,
|
||||
"stage_0_gen_ms": 20433.584690093994
|
||||
},
|
||||
"peak_memory_mb": 21550.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2302,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 20.43,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "9f24d8b8ff78fd10c6f17c036a69b86574023fab13f772d3999497341016d321",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9861,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
47.0,
|
||||
45.3,
|
||||
48.5
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 20371.083736419678,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1018.5541868209839,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 20371.186017990112,
|
||||
"image_time_to_first_output_ms": 20371.060371398926,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.09984300413634628,
|
||||
"stage_0_gen_ms": 20371.083736419678
|
||||
},
|
||||
"peak_memory_mb": 21550.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2303,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 20.52,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "cf9ec7928fa22375c476640a9920b2e05ef439200ada14b3d073bce896d5ea64",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9844,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
46.5,
|
||||
45.2,
|
||||
48.1
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 20455.31988143921,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1022.7659940719604,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 20455.437421798706,
|
||||
"image_time_to_first_output_ms": 20455.294847488403,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.11576600081752986,
|
||||
"stage_0_gen_ms": 20455.31988143921
|
||||
},
|
||||
"peak_memory_mb": 21550.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2304,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 20.56,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "288b63f713ac0664b7e0035e5f8ada494eeeb413052a35d8f3324035cb4b2bc7",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9854,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
46.9,
|
||||
44.8,
|
||||
47.7
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 20500.65541267395,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1025.0327706336975,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 20500.685691833496,
|
||||
"image_time_to_first_output_ms": 20500.566005706787,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.09380799747304991,
|
||||
"stage_0_gen_ms": 20500.65541267395
|
||||
},
|
||||
"peak_memory_mb": 21550.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2301,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 25.02,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "3df72b7d1334454fddc1768c6b6ff172a86cdb3ab8b075c3ff784260260bd1d1",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9638,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
69.4,
|
||||
70.2,
|
||||
71.8
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 24954.949617385864,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1247.7474808692932,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 24954.51283454895,
|
||||
"image_time_to_first_output_ms": 24954.333305358887,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.15277299826266244,
|
||||
"stage_0_gen_ms": 24954.949617385864
|
||||
},
|
||||
"peak_memory_mb": 22342.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2302,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 25.11,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "151ddf53141622414678c27efeafe7b928297a4cfc29038e41607069ca6968f6",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9659,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
61.9,
|
||||
63.6,
|
||||
65.7
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 25045.337438583374,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1252.2668719291687,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 25045.44496536255,
|
||||
"image_time_to_first_output_ms": 25045.308351516724,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.11107399768661708,
|
||||
"stage_0_gen_ms": 25045.337438583374
|
||||
},
|
||||
"peak_memory_mb": 22280.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2303,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 25.16,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "023407fe5dd8530aa1c640c3949b1b8da0893a15f07ae061a7ece4c132a7931b",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9572,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
61.6,
|
||||
63.2,
|
||||
64.9
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 25092.447519302368,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1254.6223759651184,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 25092.57960319519,
|
||||
"image_time_to_first_output_ms": 25092.416524887085,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.13619099627248943,
|
||||
"stage_0_gen_ms": 25092.447519302368
|
||||
},
|
||||
"peak_memory_mb": 22280.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2304,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 25.17,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "7e46495775ee73c2ccc21e252c351311c7e0976a59ef0d9483b14cfcc0fa27f7",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9471,
|
||||
"alpha_mean": 254.9,
|
||||
"rgb_std": [
|
||||
57.5,
|
||||
55.8,
|
||||
55.6
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 25105.11350631714,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1255.255675315857,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 25105.209827423096,
|
||||
"image_time_to_first_output_ms": 25105.08370399475,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.10113100142916664,
|
||||
"stage_0_gen_ms": 25105.11350631714
|
||||
},
|
||||
"peak_memory_mb": 22280.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2301,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 29.37,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "1b3430e99b76ed0483c37fbdbacdb3a890e08af69a5a9c77388cb1665c54fa19",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9785,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
42.9,
|
||||
32.4,
|
||||
23.1
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 29299.254417419434,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1464.9627208709717,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 29299.4122505188,
|
||||
"image_time_to_first_output_ms": 29299.22580718994,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.1593470005900599,
|
||||
"stage_0_gen_ms": 29299.254417419434
|
||||
},
|
||||
"peak_memory_mb": 23432.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2302,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 29.44,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "67ed3ba31c5db970ddda42dc38e5f75e395d83aae1c467625060a5bc1034906d",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9882,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
37.9,
|
||||
30.5,
|
||||
21.6
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 29367.507219314575,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1468.3753609657288,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 29367.653369903564,
|
||||
"image_time_to_first_output_ms": 29367.475748062134,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.15194500156212598,
|
||||
"stage_0_gen_ms": 29367.507219314575
|
||||
},
|
||||
"peak_memory_mb": 23432.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2303,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 29.47,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "ed8493e310da3d892eeba849519fe8697e638c215eb01bccad96608b9040e70b",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9757,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
48.5,
|
||||
40.2,
|
||||
30.1
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 29401.696920394897,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1470.0848460197449,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 29401.797771453857,
|
||||
"image_time_to_first_output_ms": 29401.665925979614,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.10474099690327421,
|
||||
"stage_0_gen_ms": 29401.696920394897
|
||||
},
|
||||
"peak_memory_mb": 23432.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2304,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 29.49,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "eca7238f811f5980b514adde1f0727fc6ce6d33efe16bd620cef184831ed1b44",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9645,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
48.5,
|
||||
35.7,
|
||||
25.1
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 29419.892072677612,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1470.9946036338806,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 29419.984102249146,
|
||||
"image_time_to_first_output_ms": 29419.86107826233,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.09873499948298559,
|
||||
"stage_0_gen_ms": 29419.892072677612
|
||||
},
|
||||
"peak_memory_mb": 23432.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2301,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 34.08,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "ed2978bbee6c6d667a1e113fddb6e5d7c06d9b0c7c0d8eda77ad330b92bdc45f",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.976,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
49.0,
|
||||
37.3,
|
||||
29.3
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 34010.05530357361,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1700.5027651786804,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 34010.1637840271,
|
||||
"image_time_to_first_output_ms": 34010.02025604248,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.11569900379981846,
|
||||
"stage_0_gen_ms": 34010.05530357361
|
||||
},
|
||||
"peak_memory_mb": 23598.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2302,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 34.0,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "f4433739a48bdb32c24f0b53d4feee604adad91327d0b25656cb0b28d4051e1b",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9713,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
47.2,
|
||||
39.5,
|
||||
29.8
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 33924.51333999634,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1696.225666999817,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 33924.62372779846,
|
||||
"image_time_to_first_output_ms": 33924.47805404663,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.11932000052183867,
|
||||
"stage_0_gen_ms": 33924.51333999634
|
||||
},
|
||||
"peak_memory_mb": 23590.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2303,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 34.02,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "9b673d3e82dc29d8781dc9d2e0a40bdd7dcad746332f9566e80e5bd8a055bd30",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9706,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
56.4,
|
||||
44.8,
|
||||
35.4
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 33950.94609260559,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1697.5473046302795,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 33951.04670524597,
|
||||
"image_time_to_first_output_ms": 33950.846672058105,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.17462699906900525,
|
||||
"stage_0_gen_ms": 33950.94609260559
|
||||
},
|
||||
"peak_memory_mb": 23590.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2304,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 34.04,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "22d9558389165abb8e58c02e7f1c4d49d4f312ff178062246c9907d519336ad6",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.9674,
|
||||
"alpha_mean": 255.0,
|
||||
"rgb_std": [
|
||||
50.9,
|
||||
38.3,
|
||||
29.5
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 33973.530292510986,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 1698.6765146255493,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 33973.62184524536,
|
||||
"image_time_to_first_output_ms": 33973.4947681427,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.09957399743143469,
|
||||
"stage_0_gen_ms": 33973.530292510986
|
||||
},
|
||||
"peak_memory_mb": 23590.0
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"schema": "issue5-int8wo-v1",
|
||||
"created_utc": "2026-10-08T11:16:41+00:00",
|
||||
"description": "Qwen-Image-2.1 DiT int8 weight-only (W8A16), compressed-tensors pack-quantized",
|
||||
"inputs": {
|
||||
"snapshot": "/home/anodyine/.local/state/multi-model-helper-server/state/image-service/weights/hub/models--Qwen--Qwen-Image-2.1/snapshots/790c92633540aa0cb11d9abf19eb46d861714758",
|
||||
"revision": "790c92633540aa0cb11d9abf19eb46d861714758",
|
||||
"tree_manifest": "/home/anodyine/.local/state/multi-model-helper-server/state/image-service/weights/hub/models--Qwen--Qwen-Image-2.1/trees/790c92633540aa0cb11d9abf19eb46d861714758.json",
|
||||
"tree_sha256": "1f35d0a5a99f2c79bf2a6818b96582828094feb8544a992bc81c1d4100aed436"
|
||||
},
|
||||
"versions": {
|
||||
"python": "3.13.15",
|
||||
"torch": "2.13.0+cu130",
|
||||
"compressed_tensors": "0.17.0"
|
||||
},
|
||||
"quantization": {
|
||||
"method": "compressed-tensors",
|
||||
"format": "pack-quantized",
|
||||
"weights": "int8 symmetric per-output-channel, static, minmax",
|
||||
"config": {
|
||||
"config_groups": {
|
||||
"group_0": {
|
||||
"targets": [
|
||||
"Linear"
|
||||
],
|
||||
"weights": {
|
||||
"num_bits": 8,
|
||||
"type": "int",
|
||||
"symmetric": true,
|
||||
"group_size": null,
|
||||
"strategy": "channel",
|
||||
"block_structure": null,
|
||||
"dynamic": false,
|
||||
"actorder": null,
|
||||
"scale_dtype": null,
|
||||
"zp_dtype": null,
|
||||
"observer": "minmax",
|
||||
"observer_kwargs": {}
|
||||
},
|
||||
"input_activations": null,
|
||||
"output_activations": null,
|
||||
"format": null
|
||||
}
|
||||
},
|
||||
"quant_method": "compressed-tensors",
|
||||
"kv_cache_scheme": null,
|
||||
"format": "pack-quantized",
|
||||
"quantization_status": "frozen",
|
||||
"global_compression_ratio": null,
|
||||
"ignore": [
|
||||
"img_in",
|
||||
"txt_in",
|
||||
"time_text_embed",
|
||||
"modulation",
|
||||
"norm_out",
|
||||
"proj_out"
|
||||
]
|
||||
},
|
||||
"ignored_layers": [
|
||||
"img_in",
|
||||
"txt_in",
|
||||
"time_text_embed",
|
||||
"modulation",
|
||||
"norm_out",
|
||||
"proj_out"
|
||||
],
|
||||
"note": "ignore list mirrors the vllm-omni model's quant_config=None boundary layers"
|
||||
},
|
||||
"stats": {
|
||||
"quantized_tensors": 224,
|
||||
"quantized_params": 6979321856,
|
||||
"bf16_params": 135802880,
|
||||
"mean_rel_l2": 0.010959165191577216,
|
||||
"max_rel_l2": 0.017284497618675232,
|
||||
"weights_file_bytes": 7256783616
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"path": "transformer/config.json",
|
||||
"bytes": 1405,
|
||||
"sha256": "43b8c94380afb473f252d2707d1398b2be180f0248acee31cad4d6613f4c0a84"
|
||||
},
|
||||
{
|
||||
"path": "transformer/diffusion_pytorch_model.safetensors",
|
||||
"bytes": 7256783616,
|
||||
"sha256": "947d9c0660776b1125ec98762817af55d59d326b8e223120aa1bdb0eced91aa9"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
transformer_blocks.0.attn.to_k.weight: shape (4096, 4096) rel_l2=0.01092 scale (4096, 1)/torch.float32
|
||||
transformer_blocks.0.attn.to_out.0.weight: shape (4096, 4096) rel_l2=0.00978 scale (4096, 1)/torch.float32
|
||||
transformer_blocks.0.attn.to_q.weight: shape (4096, 4096) rel_l2=0.01106 scale (4096, 1)/torch.float32
|
||||
transformer_blocks.0.attn.to_v.weight: shape (4096, 4096) rel_l2=0.00976 scale (4096, 1)/torch.float32
|
||||
checked 4 tensors
|
||||
@@ -0,0 +1,119 @@
|
||||
== int8wo ==
|
||||
1-ref: n=4 gate=FAIL wall 30.5-31.7s (mean 31.0s)
|
||||
edit-expression-ysolde s2301: 31.73s {"opaque_fraction": 0.0294, "alpha_mean": 114.2, "rgb_std": [59.9, 61.7, 63.8]} BROKEN
|
||||
edit-expression-ysolde s2302: 30.53s {"opaque_fraction": 0.0, "alpha_mean": 126.9, "rgb_std": [3.2, 2.9, 3.3]} BROKEN
|
||||
edit-expression-ysolde s2303: 30.88s {"opaque_fraction": 0.0391, "alpha_mean": 125.5, "rgb_std": [63.5, 66.2, 67.0]} BROKEN
|
||||
edit-expression-ysolde s2304: 30.95s {"opaque_fraction": 0.0352, "alpha_mean": 122.4, "rgb_std": [63.6, 66.3, 67.1]} BROKEN
|
||||
2-ref: n=4 gate=FAIL wall 36.5-36.7s (mean 36.6s)
|
||||
edit-compose-pair s2301: 36.46s {"opaque_fraction": 0.0303, "alpha_mean": 115.4, "rgb_std": [59.2, 61.0, 63.3]} BROKEN
|
||||
edit-compose-pair s2302: 36.57s {"opaque_fraction": 0.0, "alpha_mean": 126.9, "rgb_std": [3.1, 2.9, 3.2]} BROKEN
|
||||
edit-compose-pair s2303: 36.61s {"opaque_fraction": 0.03, "alpha_mean": 115.7, "rgb_std": [76.1, 77.9, 69.1]} BROKEN
|
||||
edit-compose-pair s2304: 36.69s {"opaque_fraction": 0.1257, "alpha_mean": 127.1, "rgb_std": [64.3, 64.3, 64.3]} BROKEN
|
||||
3-ref: n=4 gate=FAIL wall 41.3-41.6s (mean 41.4s)
|
||||
edit-compose-three s2301: 41.57s {"opaque_fraction": 0.2029, "alpha_mean": 137.2, "rgb_std": [93.0, 92.7, 91.0]} BROKEN
|
||||
edit-compose-three s2302: 41.32s {"opaque_fraction": 0.0193, "alpha_mean": 131.9, "rgb_std": [40.8, 45.8, 44.3]} BROKEN
|
||||
edit-compose-three s2303: 41.34s {"opaque_fraction": 0.0188, "alpha_mean": 131.6, "rgb_std": [41.0, 45.9, 44.6]} BROKEN
|
||||
edit-compose-three s2304: 41.39s {"opaque_fraction": 0.0189, "alpha_mean": 131.6, "rgb_std": [41.1, 46.0, 44.6]} BROKEN
|
||||
4-ref: n=4 gate=FAIL wall 46.4-46.5s (mean 46.4s)
|
||||
edit-compose-four s2301: 46.4s {"opaque_fraction": 0.0195, "alpha_mean": 131.6, "rgb_std": [41.4, 46.3, 45.0]} BROKEN
|
||||
edit-compose-four s2302: 46.42s {"opaque_fraction": 0.0196, "alpha_mean": 131.9, "rgb_std": [41.1, 46.0, 44.5]} BROKEN
|
||||
edit-compose-four s2303: 46.44s {"opaque_fraction": 0.0195, "alpha_mean": 132.0, "rgb_std": [41.1, 46.1, 44.7]} BROKEN
|
||||
edit-compose-four s2304: 46.48s {"opaque_fraction": 0.0195, "alpha_mean": 131.7, "rgb_std": [41.0, 46.1, 44.5]} BROKEN
|
||||
nvidia-smi peak: 23935 MiB; server peak: None MB
|
||||
STRUCTURE GATE: FAIL
|
||||
== psnr edit-expression-ysolde ==
|
||||
{
|
||||
"int8wo": {
|
||||
"rows": [
|
||||
{
|
||||
"seed": 2301,
|
||||
"psnr_db": 6.95,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2302,
|
||||
"psnr_db": 8.3,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2303,
|
||||
"psnr_db": 6.67,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2304,
|
||||
"psnr_db": 6.65,
|
||||
"modes": "RGBA/RGBA"
|
||||
}
|
||||
],
|
||||
"mean_db": 7.14,
|
||||
"min_db": 6.65,
|
||||
"max_db": 8.3,
|
||||
"n": 4
|
||||
}
|
||||
}
|
||||
== psnr edit-compose-pair ==
|
||||
{
|
||||
"int8wo": {
|
||||
"rows": [
|
||||
{
|
||||
"seed": 2301,
|
||||
"psnr_db": 7.03,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2302,
|
||||
"psnr_db": 8.86,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2303,
|
||||
"psnr_db": 6.77,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2304,
|
||||
"psnr_db": 7.19,
|
||||
"modes": "RGBA/RGBA"
|
||||
}
|
||||
],
|
||||
"mean_db": 7.46,
|
||||
"min_db": 6.77,
|
||||
"max_db": 8.86,
|
||||
"n": 4
|
||||
}
|
||||
}
|
||||
== psnr edit-compose-four ==
|
||||
{
|
||||
"int8wo": {
|
||||
"rows": [
|
||||
{
|
||||
"seed": 2301,
|
||||
"psnr_db": 7.02,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2302,
|
||||
"psnr_db": 7.11,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2303,
|
||||
"psnr_db": 7.1,
|
||||
"modes": "RGBA/RGBA"
|
||||
},
|
||||
{
|
||||
"seed": 2304,
|
||||
"psnr_db": 7.2,
|
||||
"modes": "RGBA/RGBA"
|
||||
}
|
||||
],
|
||||
"mean_db": 7.11,
|
||||
"min_db": 7.02,
|
||||
"max_db": 7.2,
|
||||
"n": 4
|
||||
}
|
||||
}
|
||||
23935 MiB, 100 %
|
||||
(APIServer pid=473436) INFO 10-08 07:36:28 [data.py:1483] Auto-detected quantization 'component' from model config
|
||||
(DiffusionWorker pid=473436) INFO 10-08 07:36:28 [diffusers_loader.py:719] Offline-quantized model with CPU offload, loading weights directly on CPU
|
||||
@@ -0,0 +1,82 @@
|
||||
[
|
||||
{
|
||||
"layer": "attn.to_qkv (fused q/k/v)",
|
||||
"rel_l2": "NaN",
|
||||
"out_finite": false,
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.0006612942670471966,
|
||||
"max": 0.012303149327635765
|
||||
},
|
||||
"out_shape": [
|
||||
512,
|
||||
12288
|
||||
],
|
||||
"ref_shape": [
|
||||
512,
|
||||
12288
|
||||
]
|
||||
},
|
||||
{
|
||||
"layer": "attn.to_out.0 (row)",
|
||||
"rel_l2": "NaN",
|
||||
"out_finite": false,
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.00037870631786063313,
|
||||
"max": 0.011257382109761238
|
||||
},
|
||||
"out_shape": [
|
||||
512,
|
||||
4096
|
||||
],
|
||||
"ref_shape": [
|
||||
512,
|
||||
4096
|
||||
]
|
||||
},
|
||||
{
|
||||
"layer": "img_mlp.proj (column)",
|
||||
"rel_l2": "NaN",
|
||||
"out_finite": false,
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.0005651759565807879,
|
||||
"max": 0.005628691054880619
|
||||
},
|
||||
"out_shape": [
|
||||
512,
|
||||
12288
|
||||
],
|
||||
"ref_shape": [
|
||||
512,
|
||||
12288
|
||||
]
|
||||
},
|
||||
{
|
||||
"layer": "img_mlp.out (row, wide-in)",
|
||||
"rel_l2": "NaN",
|
||||
"out_finite": false,
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.0008073942153714597,
|
||||
"max": 0.014763779938220978
|
||||
},
|
||||
"out_shape": [
|
||||
512,
|
||||
4096
|
||||
],
|
||||
"ref_shape": [
|
||||
512,
|
||||
4096
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,58 @@
|
||||
[
|
||||
{
|
||||
"layer": "attn.to_qkv (fused q/k/v)",
|
||||
"rel_l2_by_act_dtype": {
|
||||
"bfloat16": "NaN",
|
||||
"float16": "NaN"
|
||||
},
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.02410734072327614,
|
||||
"max": 0.043380916118621826
|
||||
}
|
||||
},
|
||||
{
|
||||
"layer": "attn.to_out.0 (row)",
|
||||
"rel_l2_by_act_dtype": {
|
||||
"bfloat16": "NaN",
|
||||
"float16": "NaN"
|
||||
},
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.02417764998972416,
|
||||
"max": 0.042702700942754745
|
||||
}
|
||||
},
|
||||
{
|
||||
"layer": "img_mlp.proj (column)",
|
||||
"rel_l2_by_act_dtype": {
|
||||
"bfloat16": "NaN",
|
||||
"float16": "NaN"
|
||||
},
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.024067752063274384,
|
||||
"max": 0.04336588457226753
|
||||
}
|
||||
},
|
||||
{
|
||||
"layer": "img_mlp.out (row, wide-in)",
|
||||
"rel_l2_by_act_dtype": {
|
||||
"bfloat16": "NaN",
|
||||
"float16": "NaN"
|
||||
},
|
||||
"packed_bitwise_ok": true,
|
||||
"scale_finite_pre_repack": true,
|
||||
"scale_post_repack": {
|
||||
"finite": true,
|
||||
"min": 0.02682814560830593,
|
||||
"max": 0.04223968833684921
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,978 @@
|
||||
[
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2301,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 31.73,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "a62cc903858a5026c08f284bdfea58108068fd6ab6425b54c246d65990e0bec4",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0294,
|
||||
"alpha_mean": 114.2,
|
||||
"rgb_std": [
|
||||
59.9,
|
||||
61.7,
|
||||
63.8
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 31616.730451583862,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1580.8365225791931,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 31616.933584213257,
|
||||
"image_time_to_first_output_ms": 31616.7151927948,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.18030800129054114,
|
||||
"stage_0_gen_ms": 31616.730451583862
|
||||
},
|
||||
"peak_memory_mb": 21594.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2302,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 30.53,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "ae5c51a9a36f8892c15116b90bcd2b85f90a83bae3daea2196f2602b5968e9af",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0,
|
||||
"alpha_mean": 126.9,
|
||||
"rgb_std": [
|
||||
3.2,
|
||||
2.9,
|
||||
3.3
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 30463.57011795044,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1523.178505897522,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 30463.69981765747,
|
||||
"image_time_to_first_output_ms": 30463.540077209473,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.12335599603829905,
|
||||
"stage_0_gen_ms": 30463.57011795044
|
||||
},
|
||||
"peak_memory_mb": 21900.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2303,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 30.88,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "d05825c0d525ff8522d83db2a9fc6337cc06a3960f7eb2a112898c5b71aa34a1",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0391,
|
||||
"alpha_mean": 125.5,
|
||||
"rgb_std": [
|
||||
63.5,
|
||||
66.2,
|
||||
67.0
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 30779.66594696045,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1538.9832973480225,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 30779.80923652649,
|
||||
"image_time_to_first_output_ms": 30779.641151428223,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.14197500422596931,
|
||||
"stage_0_gen_ms": 30779.66594696045
|
||||
},
|
||||
"peak_memory_mb": 21906.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-expression-ysolde",
|
||||
"seed": 2304,
|
||||
"n_references": 1,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "864x1152",
|
||||
"wall_s": 30.95,
|
||||
"http": 200,
|
||||
"bytes": 3983827,
|
||||
"sha256": "10e85cf24451f4fb7bf2d8e07b4039158af08e36467a2a14d2baee1d68cd98fa",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0352,
|
||||
"alpha_mean": 122.4,
|
||||
"rgb_std": [
|
||||
63.6,
|
||||
66.3,
|
||||
67.1
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 30871.540069580078,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 995328,
|
||||
"denoise_step_latency_ms": 1543.577003479004,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 30871.644735336304,
|
||||
"image_time_to_first_output_ms": 30871.514797210693,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.10297299741068855,
|
||||
"stage_0_gen_ms": 30871.540069580078
|
||||
},
|
||||
"peak_memory_mb": 21906.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2301,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 36.46,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "4bfdf57ecb0e1ed1c2c4dcb10ce0545d89040ad08ed754e01adbe2ddd2b407d2",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0303,
|
||||
"alpha_mean": 115.4,
|
||||
"rgb_std": [
|
||||
59.2,
|
||||
61.0,
|
||||
63.3
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 36376.55758857727,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1818.8278794288635,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 36376.71780586243,
|
||||
"image_time_to_first_output_ms": 36376.53112411499,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.15923400496831164,
|
||||
"stage_0_gen_ms": 36376.55758857727
|
||||
},
|
||||
"peak_memory_mb": 22676.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2302,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 36.57,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "e5ad17605624fdb636dc02e272c0dba2d3286dcfa4c27f297a605e785f3177a2",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0,
|
||||
"alpha_mean": 126.9,
|
||||
"rgb_std": [
|
||||
3.1,
|
||||
2.9,
|
||||
3.2
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 36508.753061294556,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1825.4376530647278,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 36508.851528167725,
|
||||
"image_time_to_first_output_ms": 36508.72349739075,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.1011220010695979,
|
||||
"stage_0_gen_ms": 36508.753061294556
|
||||
},
|
||||
"peak_memory_mb": 22556.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2303,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 36.61,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "f58a982cf1f3afd4a81fcc6b349aa3b147fecea0594e33f49cab5673f300b2ac",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.03,
|
||||
"alpha_mean": 115.7,
|
||||
"rgb_std": [
|
||||
76.1,
|
||||
77.9,
|
||||
69.1
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 36523.38361740112,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1826.1691808700562,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 36523.181676864624,
|
||||
"image_time_to_first_output_ms": 36523.05722236633,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.0983119971351698,
|
||||
"stage_0_gen_ms": 36523.38361740112
|
||||
},
|
||||
"peak_memory_mb": 22554.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-pair",
|
||||
"seed": 2304,
|
||||
"n_references": 2,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1248x832",
|
||||
"wall_s": 36.69,
|
||||
"http": 200,
|
||||
"bytes": 4155590,
|
||||
"sha256": "871c8147b27310508fb2c9e72b3b47499d7f8ab4ea705719aa30c465c8a5476a",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.1257,
|
||||
"alpha_mean": 127.1,
|
||||
"rgb_std": [
|
||||
64.3,
|
||||
64.3,
|
||||
64.3
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 36608.7908744812,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1038336,
|
||||
"denoise_step_latency_ms": 1830.43954372406,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 36608.89911651611,
|
||||
"image_time_to_first_output_ms": 36608.76417160034,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.10716100223362446,
|
||||
"stage_0_gen_ms": 36608.7908744812
|
||||
},
|
||||
"peak_memory_mb": 22556.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2301,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 41.57,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "810cb635438bac76de76fa44172687e2f18b212379458580a32bbddd028bd78c",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.2029,
|
||||
"alpha_mean": 137.2,
|
||||
"rgb_std": [
|
||||
93.0,
|
||||
92.7,
|
||||
91.0
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 41476.71937942505,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2073.8359689712524,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 41476.8271446228,
|
||||
"image_time_to_first_output_ms": 41476.691246032715,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.1088700009859167,
|
||||
"stage_0_gen_ms": 41476.71937942505
|
||||
},
|
||||
"peak_memory_mb": 23468.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2302,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 41.32,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "2f085a5089ab0618a2f17df7c29c52fde091247cb4a1ccf0cd8d662171b18fc9",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0193,
|
||||
"alpha_mean": 131.9,
|
||||
"rgb_std": [
|
||||
40.8,
|
||||
45.8,
|
||||
44.3
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 41233.43777656555,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2061.6718888282776,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 41233.57892036438,
|
||||
"image_time_to_first_output_ms": 41233.40702056885,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.14587400073651224,
|
||||
"stage_0_gen_ms": 41233.43777656555
|
||||
},
|
||||
"peak_memory_mb": 23460.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2303,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 41.34,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "81e9b7436d392c8420d1529976a0d88e5de03af1e70823cc8c7b279b3d7bb3fa",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0188,
|
||||
"alpha_mean": 131.6,
|
||||
"rgb_std": [
|
||||
41.0,
|
||||
45.9,
|
||||
44.6
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 41250.11157989502,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2062.505578994751,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 41250.218868255615,
|
||||
"image_time_to_first_output_ms": 41250.08296966553,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.10969900176860392,
|
||||
"stage_0_gen_ms": 41250.11157989502
|
||||
},
|
||||
"peak_memory_mb": 23394.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-three",
|
||||
"seed": 2304,
|
||||
"n_references": 3,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 41.39,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "2221932a1783102d87c01157e231160803cebac2113c86b9416e537e0e738747",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0189,
|
||||
"alpha_mean": 131.6,
|
||||
"rgb_std": [
|
||||
41.1,
|
||||
46.0,
|
||||
44.6
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 41297.605991363525,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2064.8802995681763,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 41297.31011390686,
|
||||
"image_time_to_first_output_ms": 41297.15037345886,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.134524998429697,
|
||||
"stage_0_gen_ms": 41297.605991363525
|
||||
},
|
||||
"peak_memory_mb": 23394.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2301,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 46.4,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "abd057bee84e594437f895cc9f51f90f118adfeb4dbf738686791b41487e93b2",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0195,
|
||||
"alpha_mean": 131.6,
|
||||
"rgb_std": [
|
||||
41.4,
|
||||
46.3,
|
||||
45.0
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 46304.86750602722,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2315.243375301361,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 46304.97694015503,
|
||||
"image_time_to_first_output_ms": 46304.8369884491,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.11391400039428845,
|
||||
"stage_0_gen_ms": 46304.86750602722
|
||||
},
|
||||
"peak_memory_mb": 23534.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2302,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 46.42,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "24a783a9ca3a002745351476e8000be87854d1364ce92ca944c2986a983922f5",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0196,
|
||||
"alpha_mean": 131.9,
|
||||
"rgb_std": [
|
||||
41.1,
|
||||
46.0,
|
||||
44.5
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 46325.687408447266,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2316.2843704223633,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 46324.424266815186,
|
||||
"image_time_to_first_output_ms": 46324.246883392334,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.15303100371966138,
|
||||
"stage_0_gen_ms": 46325.687408447266
|
||||
},
|
||||
"peak_memory_mb": 23524.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2303,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 46.44,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "db507c15651324404b79d6e5446e43fb460797204b302c0c258be37f1e2a7a80",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0195,
|
||||
"alpha_mean": 132.0,
|
||||
"rgb_std": [
|
||||
41.1,
|
||||
46.1,
|
||||
44.7
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 46352.77342796326,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2317.638671398163,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 46352.887868881226,
|
||||
"image_time_to_first_output_ms": 46352.74004936218,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.1095690022339113,
|
||||
"stage_0_gen_ms": 46352.77342796326
|
||||
},
|
||||
"peak_memory_mb": 23524.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture_id": "edit-compose-four",
|
||||
"seed": 2304,
|
||||
"n_references": 4,
|
||||
"steps": 20,
|
||||
"true_cfg_scale": 1.0,
|
||||
"size": "1344x768",
|
||||
"wall_s": 46.48,
|
||||
"http": 200,
|
||||
"bytes": 4130915,
|
||||
"sha256": "5481e029af2cb8ef3140565c615a5abc97ed755ff180b1970badf3a997c60ac4",
|
||||
"structure": {
|
||||
"opaque_fraction": 0.0195,
|
||||
"alpha_mean": 131.7,
|
||||
"rgb_std": [
|
||||
41.0,
|
||||
46.1,
|
||||
44.5
|
||||
]
|
||||
},
|
||||
"usage": null,
|
||||
"metrics": {
|
||||
"stage_metrics": {
|
||||
"0": {
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"finish_reason": null,
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_gen_time_ms": 46385.611057281494,
|
||||
"postprocess_time_ms": 0.0,
|
||||
"audio_frames": 0,
|
||||
"audio_sample_rate": 0,
|
||||
"audio_duration_s": 0.0,
|
||||
"image_pixels": 1032192,
|
||||
"denoise_step_latency_ms": 2319.2805528640747,
|
||||
"output_unit_type": "image",
|
||||
"output_unit_count": 1,
|
||||
"serving_time_to_first_output_ms": 46385.305404663086,
|
||||
"image_time_to_first_output_ms": 46385.130643844604,
|
||||
"time_per_output_unit_ms": 0.0,
|
||||
"inter_output_latency_ms": 0.0,
|
||||
"inter_output_latencies_ms": [],
|
||||
"vllm_ttft_ms": 0.0,
|
||||
"vllm_tpot_ms": 0.0,
|
||||
"vllm_itl_ms": 0.0,
|
||||
"vllm_itls_ms": [],
|
||||
"stage_name": "diffusion"
|
||||
}
|
||||
},
|
||||
"stage_id": 0,
|
||||
"final_output_type": "image",
|
||||
"num_tokens_in": 0,
|
||||
"num_tokens_out": 0,
|
||||
"stage_durations": {
|
||||
"queue_wait_ms": 0.14768500113859773,
|
||||
"stage_0_gen_ms": 46385.611057281494
|
||||
},
|
||||
"peak_memory_mb": 23524.0
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"schema": "issue5-runs-summary-v2",
|
||||
"probed_utc": "2026-10-08",
|
||||
"round_two": {
|
||||
"directive": "issue 5 comment 2026-10-08T07:51 (amended 08:37): one decisive experiment - W8A8 int8-dynamic, non-Marlin",
|
||||
"artifact": "~/.local/state/multi-model-helper-server/state/image-service/artifacts/qwen-image-2.1-dit-w8a8dyn",
|
||||
"kernel": "CutlassInt8ScaledMMLinearKernel (ops.scaled_int8_quant + ops.cutlass_scaled_mm)",
|
||||
"kernel_verification": {
|
||||
"raw_ops_rel_l2": "0.0017 at (64,256,128)/(512,4096,4096)/(512,4096,12288) vs float32 reference (cutlass-ops-probe.json in evidence/rtx3090-round2)",
|
||||
"layer_path_rel_l2": "0.0017 for fused QKV and row-parallel, synthetic weights, loaded bitwise-verified (w8a8-layer-isolate.json)"
|
||||
},
|
||||
"serve_run": {
|
||||
"label": "int8w8a8dyn",
|
||||
"init": "healthy in ~40 s",
|
||||
"wall_s_mean": {"1-ref": 20.5, "2-ref": 25.1, "3-ref": 29.4, "4-ref": 34.0},
|
||||
"nvidia_smi_peak_mib": 23879,
|
||||
"structure": "opaque 0.947-0.988, alpha mean 254.9-255.0, rgb_std 21-72 across all 16 requests; visual checks render faithful scenes",
|
||||
"gate_note": "round-one 0.99-opacity rule was a broken-vs-BF16 calibration; W8A8 outputs are soft-edge alpha, not the broken signature (0.008-0.20 band); verdict rests on raw numbers + visual + PSNR",
|
||||
"psnr_vs_sealed_diffusers_mean_db": {"1-ref": 33.72, "2-ref": 28.34, "4-ref": 27.50},
|
||||
"bf16_reference_pair_db": 27.57
|
||||
},
|
||||
"verdict": "YES - W8A8 int8-dynamic delivers 3-4 reference capacity at the fastest measured speed with BF16-parity quality; hard stop did not trigger; Marlin W8A16 root-cause draft staged for the #3 lane (UPSTREAM-REPORT-DRAFT.md)"
|
||||
},
|
||||
"host": "cachyos-alexander (RTX 3090 24 GB, sm_86, driver 615.71.09, CUDA 13.4)",
|
||||
"stack": {
|
||||
"vllm_omni": "ISSUE-5 branch e778d21f..67e17786 over main 8484d85d1",
|
||||
"vllm": "0.31.0",
|
||||
"torch": "2.13.0+cu130",
|
||||
"compressed_tensors": "0.17.0",
|
||||
"transformers": "5.14.1",
|
||||
"diffusers": "0.41.0.dev0 (git 122b1e11)"
|
||||
},
|
||||
"artifact": {
|
||||
"path": "~/.local/state/multi-model-helper-server/state/image-service/artifacts/qwen-image-2.1-dit-int8wo",
|
||||
"quantized_params": 6979321856,
|
||||
"bf16_boundary_params": 135802880,
|
||||
"weights_file_bytes": 7256783616,
|
||||
"mean_rel_l2_quant_error": 0.010959,
|
||||
"max_rel_l2_quant_error": 0.017284,
|
||||
"roundtrip_verification": "unpack+dequant vs sealed BF16: rel_l2 0.0098-0.0117 per tensor (see artifact-verify.txt)"
|
||||
},
|
||||
"serve_run": {
|
||||
"label": "int8wo",
|
||||
"command": "vllm serve <artifact> --omni --port 8091 --served-model-name Qwen/Qwen-Image-2.1 --enforce-eager --enable-cpu-offload",
|
||||
"init": "healthy in ~40 s; per-component quantization auto-detected from transformer/config.json (transformer -> compressed-tensors, encoder/VAE BF16); CompressedTensorsWNA16 + MarlinLinearKernel selected on sm_86",
|
||||
"failed_precursor_runs": [
|
||||
"int8wo (first): init OOM - loader treated the pre-quantized checkpoint as online quantization and loaded all components on the GPU (encoder 17 GiB + VAE + packed DiT skeleton > 24 GiB); fixed by the offline-detection loader change on this branch",
|
||||
"int8wo-expandable: same OOM with PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (genuine capacity, not fragmentation); also polluted by a leftover 586 MiB DiffusionWorker from an interrupted test run"
|
||||
]
|
||||
},
|
||||
"capacity_matrix": {
|
||||
"fixtures": {"1-ref": "edit-expression-ysolde", "2-ref": "edit-compose-pair", "3-ref": "edit-compose-three", "4-ref": "edit-compose-four"},
|
||||
"seeds": [2301, 2302, 2303, 2304],
|
||||
"requests": 16,
|
||||
"http_200": 16,
|
||||
"wall_s_mean": {"1-ref": 31.0, "2-ref": 36.6, "3-ref": 41.4, "4-ref": 46.4},
|
||||
"nvidia_smi_peak_mib": 23935,
|
||||
"bf16_reference_walls": {"1-ref": 34.2, "2-ref": 36.7, "4-ref": "OOM at BF16 (issue 2)"},
|
||||
"fp8_reference_walls": {"1-ref": 29.9, "2-ref": 34.1, "3-ref": 38.9, "4-ref": 44.3}
|
||||
},
|
||||
"structure_gate": {
|
||||
"rule": "opaque_fraction >= 0.99 AND mean(rgb_std) >= 10",
|
||||
"result": "FAIL 16/16",
|
||||
"signature": "opaque 0.0-0.20 (vs 1.0 at BF16), alpha mean 114-137, rgb_std 3-93; outputs are horizontal-stripe noise, not rendered scenes",
|
||||
"detail": "gate-and-psnr-summary.txt; per-request records in matrix.json"
|
||||
},
|
||||
"psnr_vs_sealed_diffusers_baseline": {
|
||||
"edit-expression-ysolde_1ref_mean_db": 7.14,
|
||||
"edit-compose-pair_2ref_mean_db": 7.46,
|
||||
"edit-compose-four_4ref_mean_db": 7.11,
|
||||
"bf16_pair_reference_mean_db": 27.57,
|
||||
"fp8_broken_reference_mean_db": 7.11
|
||||
},
|
||||
"marlin_isolation": {
|
||||
"method": "marlin_isolate.py: vLLM QKVParallelLinear/RowParallelLinear/ColumnParallelLinear built exactly as the qwen_image_21 transformer does; artifact tensors loaded through the production weight-loaders; Marlin repack on GPU; layer(x) compared against float32 x @ dequant(W)^T",
|
||||
"results_block0_artifact": "NaN output, all four layer kinds, bf16 AND fp16 activations; loaded weight_packed bitwise-equal to artifact (incl. fused q/k/v stacked shards); scales finite pre and post repack (marlin-isolate-block0.json)",
|
||||
"results_synthetic_random_int8": "NaN identically - the checkpoint and its quantization are exonerated (marlin-isolate-synthetic.json)",
|
||||
"conclusion": "the 8-bit Marlin W8A16 path on this stack (sm_86, vllm 0.31.0, torch 2.13.0+cu130) produces NaN for int8 weight-only layers although query_marlin_supported_quant_types accepts uint8b128 on the card; choose_mp_linear_kernel offers no non-Marlin fallback for W8A16 on sm_86 (Machete is sm90+, ExLlama int4-only)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"edit-expression-ysolde": {
|
||||
"mode": "edit",
|
||||
"references": [
|
||||
"ref-portrait-ysolde"
|
||||
],
|
||||
"width": 864,
|
||||
"height": 1152,
|
||||
"prompt": "Change only her expression and the chart: she now laughs openly and lowers the chart; keep her face, hair, clothing, the room, the framing, and the lighting as in the reference. Result: Ysolde laughs at something off-frame in the map room. Emotional tone: sudden delight.\nSubject: Ysolde Varn, an adult woman in her early thirties, with deep brown skin, close-cropped black hair with one silver-beaded braid behind the left ear, and amber eyes; wearing a teal oilskin coat with brass buttons over a cream linen shirt; a thin pale scar through the right eyebrow and a brass compass on a cord at her throat. Laughing openly, the chart lowered to her chest.\nSetting: The map room of a harbor guildhall, charts pinned to timber walls, rain on the window.\nComposition: close-up; head and shoulders, three-quarter view, face in sharp focus; lighting: cool grey window light from the left with a warm lamp glow from below\nStyle: Semi-realistic digital painting: natural proportions, painterly texture, soft realistic lighting.",
|
||||
"negative_prompt": null
|
||||
},
|
||||
"edit-compose-pair": {
|
||||
"mode": "edit",
|
||||
"references": [
|
||||
"ref-portrait-ysolde",
|
||||
"ref-cutout-tamsin"
|
||||
],
|
||||
"width": 1248,
|
||||
"height": 832,
|
||||
"prompt": "Place the woman from the first reference and the figure from the second reference together; keep both identities exactly. Result: Ysolde and Tamsin share one unrolled map at a harbor wall. Emotional tone: wary cooperation.\nSubject: Ysolde Varn, an adult woman in her early thirties, with deep brown skin, close-cropped black hair with one silver-beaded braid behind the left ear, and amber eyes; wearing a teal oilskin coat with brass buttons over a cream linen shirt; a thin pale scar through the right eyebrow and a brass compass on a cord at her throat. Holding one end of an unrolled map, explaining a route.\nSubject: Tamsin Rook, an adult androgynous person in their late twenties, with warm olive skin, straight shoulder-length silver-white hair, and dark brown eyes; wearing a crimson hooded mantle over fitted grey leather armour; three thin black geometric lines tattooed down the left cheek. Holding the other end of the map, listening sceptically.\nSetting: A sea wall above the harbor at midday, fishing boats below.\nComposition: medium shot; both figures side by side, the map between them; lighting: bright midday sun with short shadows\nStyle: Semi-realistic digital painting: natural proportions, painterly texture, soft realistic lighting.",
|
||||
"negative_prompt": null
|
||||
},
|
||||
"edit-compose-four": {
|
||||
"mode": "edit",
|
||||
"references": [
|
||||
"ref-portrait-ysolde",
|
||||
"ref-storm-corvin",
|
||||
"ref-cutout-tamsin",
|
||||
"ref-shop-hesper"
|
||||
],
|
||||
"width": 1344,
|
||||
"height": 768,
|
||||
"prompt": "Bring the four characters from the references together in one scene; keep every identity exactly. Result: All four gather around the apothecary counter. Emotional tone: conspiratorial calm.\nSubject: Ysolde Varn, an adult woman in her early thirties, with deep brown skin, close-cropped black hair with one silver-beaded braid behind the left ear, and amber eyes; wearing a teal oilskin coat with brass buttons over a cream linen shirt; a thin pale scar through the right eyebrow and a brass compass on a cord at her throat. Unfolding a chart on the counter.\nSubject: Corvin Ashdown, an adult man in his mid-fifties, with pale, heavily freckled skin, short receding red hair and a grey-streaked red beard, and pale blue eyes; wearing a dark green harbormaster's greatcoat with a silver chain of office; a black leather patch over the left eye. Leaning on the doorframe.\nSubject: Tamsin Rook, an adult androgynous person in their late twenties, with warm olive skin, straight shoulder-length silver-white hair, and dark brown eyes; wearing a crimson hooded mantle over fitted grey leather armour; three thin black geometric lines tattooed down the left cheek. Sniffing a stoppered bottle.\nSubject: Hesper Quill, an adult woman in her early seventies, with light brown, deeply weathered skin, long white hair in one thick plait, and grey-green eyes; wearing a lavender dress under a stained canvas apothecary's apron; round wire-rimmed spectacles and a silver ring on every finger of the right hand. Behind the counter, weighing herbs on a brass scale.\nSetting: Inside the apothecary: shelves of bottled remedies, drying herbs hanging from the beams, a long counter.\nComposition: wide shot; from behind the counter's end, all four faces visible; lighting: warm lamplight with green light filtering through bottle glass\nStyle: Anime illustration: clean line art, cel shading, flat vivid colour, expressive faces.",
|
||||
"negative_prompt": null
|
||||
},
|
||||
"edit-compose-three": {
|
||||
"mode": "edit",
|
||||
"references": [
|
||||
"ref-portrait-ysolde",
|
||||
"ref-storm-corvin",
|
||||
"ref-cutout-tamsin"
|
||||
],
|
||||
"width": 1344,
|
||||
"height": 768,
|
||||
"prompt": "Bring the three characters from the references together in one scene; keep every identity exactly. Result: All three gather around the apothecary counter. Emotional tone: conspiratorial calm.\nSubject: Ysolde Varn, an adult woman in her early thirties, with deep brown skin, close-cropped black hair with one silver-beaded braid behind the left ear, and amber eyes; wearing a teal oilskin coat with brass buttons over a cream linen shirt; a thin pale scar through the right eyebrow and a brass compass on a cord at her throat. Unfolding a chart on the counter.\nSubject: Corvin Ashdown, an adult man in his mid-fifties, with pale, heavily freckled skin, short receding red hair and a grey-streaked red beard, and pale blue eyes; wearing a dark green harbormaster's greatcoat with a silver chain of office; a black leather patch over the left eye. Leaning on the doorframe.\nSubject: Tamsin Rook, an adult androgynous person in their late twenties, with warm olive skin, straight shoulder-length silver-white hair, and dark brown eyes; wearing a crimson hooded mantle over fitted grey leather armour; three thin black geometric lines tattooed down the left cheek. Sniffing a stoppered bottle.\nSetting: Inside the apothecary: shelves of bottled remedies, drying herbs hanging from the beams, a long counter.\nComposition: wide shot; from behind the counter's end, all four faces visible; lighting: warm lamplight with green light filtering through bottle glass\nStyle: Anime illustration: clean line art, cel shading, flat vivid colour, expressive faces.",
|
||||
"negative_prompt": null,
|
||||
"derived_from": "edit-compose-four minus ref-shop-hesper and its Subject line; four->three wording"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5 structure gate + capacity summarizer.
|
||||
|
||||
Applies the gate rule from issue 1's FP8 misread to every recorded request
|
||||
and folds a run directory into the capacity matrix row: per reference
|
||||
count, wall time, peak VRAM, and the server-reported peak.
|
||||
|
||||
Gate rule (a run only counts when structurally sound):
|
||||
opaque_fraction >= 0.99 AND mean(rgb_std) >= 10
|
||||
|
||||
Broken FP8 output measured opaque 0.008-0.035 with flat grey RGB (std of a
|
||||
few); the BF16 path is 100% opaque with healthy channel spread.
|
||||
|
||||
Usage: gate.py RUN_DIR [RUN_DIR ...] # reads matrix.json / matrix-*.json
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
|
||||
def peak_vram_mb(vram_log: pathlib.Path) -> int | None:
|
||||
if not vram_log.exists():
|
||||
return None
|
||||
peak = 0
|
||||
for line in vram_log.read_text().splitlines():
|
||||
match = re.match(r"\s*(\d+)\s+MiB\s*,", line) or re.match(r"\s*(\d+),", line)
|
||||
if match:
|
||||
peak = max(peak, int(match.group(1)))
|
||||
return peak or None
|
||||
|
||||
|
||||
def server_peak_mb(serve_log: pathlib.Path) -> int | None:
|
||||
if not serve_log.exists():
|
||||
return None
|
||||
peaks = [int(m) for m in re.findall(r"peak.*?(\d[\d,]+)\s*MB", serve_log.read_text())]
|
||||
if not peaks:
|
||||
return None
|
||||
return max(peaks)
|
||||
|
||||
|
||||
def gate(structure: dict) -> bool:
|
||||
return (
|
||||
structure["opaque_fraction"] >= 0.99
|
||||
and sum(structure["rgb_std"]) / len(structure["rgb_std"]) >= 10
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("runs", nargs="+", type=pathlib.Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
for run_dir in args.runs:
|
||||
records: list[dict] = []
|
||||
for candidate in sorted(run_dir.glob("matrix*.json")):
|
||||
records.extend(json.loads(candidate.read_text()))
|
||||
by_refs: dict[int, list[dict]] = {}
|
||||
for rec in records:
|
||||
by_refs.setdefault(rec.get("n_references", 0), []).append(rec)
|
||||
|
||||
print(f"== {run_dir.name} ==")
|
||||
all_pass = bool(records)
|
||||
for n_refs in sorted(by_refs):
|
||||
rows = by_refs[n_refs]
|
||||
walls = [r["wall_s"] for r in rows if "wall_s" in r]
|
||||
gates = [gate(r["structure"]) for r in rows if "structure" in r]
|
||||
row_pass = bool(rows) and all(gates) and len(gates) == len(rows)
|
||||
all_pass = all_pass and row_pass
|
||||
print(
|
||||
f" {n_refs}-ref: n={len(rows)} gate={'PASS' if row_pass else 'FAIL'} "
|
||||
f"wall {min(walls):.1f}-{max(walls):.1f}s"
|
||||
+ (f" (mean {sum(walls) / len(walls):.1f}s)" if walls else "")
|
||||
)
|
||||
for rec in rows:
|
||||
s = rec.get("structure")
|
||||
tag = "" if s is None else (" ok" if gate(s) else " BROKEN")
|
||||
err = f" error={rec['error'][:80]}" if rec.get("error") else ""
|
||||
print(
|
||||
f" {rec.get('fixture_id')} s{rec.get('seed')}: "
|
||||
f"{rec.get('wall_s')}s {json.dumps(s) if s else 'no-image'}{tag}{err}"
|
||||
)
|
||||
peak = peak_vram_mb(run_dir / "vram.log")
|
||||
server_peak = server_peak_mb(run_dir / "serve.log")
|
||||
print(f" nvidia-smi peak: {peak} MiB; server peak: {server_peak} MB")
|
||||
print(f" STRUCTURE GATE: {'PASS' if all_pass else 'FAIL'}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5 diagnostic: isolate the compressed-tensors W8A16 serve path per layer.
|
||||
|
||||
Builds vLLM parallel linear layers exactly as the qwen_image_21 transformer
|
||||
does (fused QKV, row-parallel to_out, img_mlp proj/out), loads the int8
|
||||
artifact's packed tensors through the production weight-loader machinery,
|
||||
runs the Marlin repack (process_weights_after_loading) on the GPU, and
|
||||
compares each layer's forward output against a float32 reference computed
|
||||
from the artifact's own unpacked+dequantized weights.
|
||||
|
||||
Expected rel-L2 when the load->repack->apply chain is correct: ~1-2%
|
||||
(int8 quantization error plus bf16 activation rounding). A large error
|
||||
pins corruption inside the serve path with the checkpoint already
|
||||
verified correct (verify_artifact.py).
|
||||
|
||||
Usage: marlin_isolate.py --artifact ARTIFACT_DIR [--block N]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
import torch
|
||||
from compressed_tensors.compressors.pack_quantized.helpers import unpack_from_int32
|
||||
from safetensors.torch import load_file
|
||||
|
||||
|
||||
def dequantized_weight(art: dict, base: str) -> torch.Tensor:
|
||||
shape = tuple(art[f"{base}_shape"].tolist())
|
||||
q = unpack_from_int32(art[f"{base}_packed"].cpu(), 8, shape)
|
||||
return q.to(torch.float32) * art[f"{base}_scale"].cpu().to(torch.float32)
|
||||
|
||||
|
||||
def load_tensor(layer: torch.nn.Module, param_name: str, tensor: torch.Tensor, shard: str | None) -> None:
|
||||
param = getattr(layer, param_name)
|
||||
if shard is None:
|
||||
param.weight_loader(param, tensor)
|
||||
else:
|
||||
param.weight_loader(param, tensor, shard)
|
||||
|
||||
|
||||
def check(name: str, layer: torch.nn.Module, art: dict, base: str, shards: list[tuple[str, str]], in_features: int, dtype: torch.dtype) -> dict:
|
||||
device = torch.device("cuda")
|
||||
for suffix in ("weight_packed", "weight_scale", "weight_shape"):
|
||||
for ckpt_base, shard in shards:
|
||||
load_tensor(layer, suffix, art[f"{ckpt_base}.{suffix}"].to(device), shard)
|
||||
|
||||
# Bitwise check of the loaded packed weights against the artifact.
|
||||
loaded = layer.weight_packed.detach().cpu()
|
||||
if len(shards) > 1:
|
||||
ref_packed = torch.cat([art[f"{b}.weight_packed"] for b, _ in shards], dim=0)
|
||||
else:
|
||||
ref_packed = art[f"{base}.weight_packed"]
|
||||
packed_ok = bool(torch.equal(loaded, ref_packed))
|
||||
scale_loaded = layer.weight_scale.detach().float()
|
||||
scale_ok = bool(torch.isfinite(scale_loaded).all())
|
||||
|
||||
layer.quant_method.process_weights_after_loading(layer)
|
||||
repacked_scale = getattr(layer, "weight_scale", None)
|
||||
scale_post = (
|
||||
{
|
||||
"finite": bool(torch.isfinite(repacked_scale.detach().float()).all()),
|
||||
"min": float(repacked_scale.detach().float().min()),
|
||||
"max": float(repacked_scale.detach().float().max()),
|
||||
}
|
||||
if repacked_scale is not None
|
||||
else {}
|
||||
)
|
||||
|
||||
torch.manual_seed(2301)
|
||||
ref_w = torch.cat([dequantized_weight(art, f"{b}.weight") for b, _ in shards], dim=0) if len(shards) > 1 else dequantized_weight(art, f"{base}.weight")
|
||||
outs = {}
|
||||
for act_dtype in (torch.bfloat16, torch.float16):
|
||||
x = torch.randn(512, in_features, dtype=act_dtype, device=device)
|
||||
result = layer(x)
|
||||
out = (result[0] if isinstance(result, tuple) else result).float()
|
||||
ref = x.float() @ ref_w.T.to(device)
|
||||
finite = bool(torch.isfinite(out).all())
|
||||
rel = float((out - ref).norm() / ref.norm())
|
||||
outs[str(act_dtype).replace("torch.", "")] = rel if finite else "NaN"
|
||||
|
||||
return {
|
||||
"layer": name,
|
||||
"rel_l2_by_act_dtype": outs,
|
||||
"packed_bitwise_ok": packed_ok,
|
||||
"scale_finite_pre_repack": scale_ok,
|
||||
"scale_post_repack": scale_post,
|
||||
}
|
||||
|
||||
|
||||
def synthetic_artifact(block: int) -> dict[str, torch.Tensor]:
|
||||
"""Random int8-quantized tensors in the artifact's layout, for stack-only isolation."""
|
||||
from compressed_tensors.compressors.pack_quantized.helpers import pack_to_int32
|
||||
|
||||
def quant(name: str, out: int, inp: int) -> None:
|
||||
w = torch.randn(out, inp, dtype=torch.float32)
|
||||
scale = w.abs().amax(dim=1, keepdim=True).clamp_min(1e-12) / 127.0
|
||||
q = torch.round(w / scale).clamp(-127, 127).to(torch.int8)
|
||||
art[f"{name}.weight_packed"] = pack_to_int32(q.contiguous(), 8).contiguous()
|
||||
art[f"{name}.weight_scale"] = scale.to(torch.float32).contiguous()
|
||||
art[f"{name}.weight_shape"] = torch.tensor([out, inp], dtype=torch.int64)
|
||||
|
||||
art: dict[str, torch.Tensor] = {}
|
||||
p = f"transformer_blocks.{block}"
|
||||
for part in ("to_q", "to_k", "to_v"):
|
||||
quant(f"{p}.attn.{part}", 4096, 4096)
|
||||
quant(f"{p}.attn.to_out.0", 4096, 4096)
|
||||
quant(f"{p}.img_mlp.proj", 12288, 4096)
|
||||
quant(f"{p}.img_mlp.out", 4096, 12288)
|
||||
return art
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--artifact", type=pathlib.Path, required=True)
|
||||
parser.add_argument("--block", type=int, default=0)
|
||||
parser.add_argument(
|
||||
"--synthetic",
|
||||
action="store_true",
|
||||
help="ignore --artifact; use random int8 weights (isolates the stack from the checkpoint)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
import vllm.distributed
|
||||
from vllm.config import VllmConfig, set_current_vllm_config
|
||||
from vllm.distributed import (
|
||||
destroy_distributed_environment,
|
||||
destroy_model_parallel,
|
||||
initialize_model_parallel,
|
||||
)
|
||||
from vllm.model_executor.layers.linear import (
|
||||
ColumnParallelLinear,
|
||||
QKVParallelLinear,
|
||||
RowParallelLinear,
|
||||
)
|
||||
|
||||
vllm.distributed.init_distributed_environment(
|
||||
backend="gloo",
|
||||
world_size=1,
|
||||
rank=0,
|
||||
local_rank=0,
|
||||
distributed_init_method="tcp://127.0.0.1:29601",
|
||||
)
|
||||
with set_current_vllm_config(VllmConfig()):
|
||||
initialize_model_parallel(tensor_model_parallel_size=1)
|
||||
|
||||
config = json.loads((args.artifact / "transformer" / "config.json").read_text())
|
||||
from vllm_omni.quantization import build_quant_config, resolve_component_quant_config
|
||||
|
||||
quant_config = resolve_component_quant_config(
|
||||
build_quant_config(config["quantization_config"]), "transformer"
|
||||
)
|
||||
if args.synthetic:
|
||||
art = synthetic_artifact(args.block)
|
||||
else:
|
||||
art = load_file(str(args.artifact / "transformer" / "diffusion_pytorch_model.safetensors"))
|
||||
b = args.block
|
||||
p = f"transformer.transformer_blocks.{b}"
|
||||
|
||||
results = []
|
||||
with torch.device("cuda"):
|
||||
to_qkv = QKVParallelLinear(
|
||||
hidden_size=4096, head_size=128, total_num_heads=32, bias=False,
|
||||
quant_config=quant_config, prefix=f"{p}.attn.to_qkv",
|
||||
)
|
||||
results.append(
|
||||
check(
|
||||
"attn.to_qkv (fused q/k/v)",
|
||||
to_qkv,
|
||||
art,
|
||||
f"transformer_blocks.{b}.attn.to_qkv",
|
||||
[
|
||||
(f"transformer_blocks.{b}.attn.to_q", "q"),
|
||||
(f"transformer_blocks.{b}.attn.to_k", "k"),
|
||||
(f"transformer_blocks.{b}.attn.to_v", "v"),
|
||||
],
|
||||
4096,
|
||||
torch.bfloat16,
|
||||
)
|
||||
)
|
||||
|
||||
to_out = RowParallelLinear(
|
||||
4096, 4096, bias=False, input_is_parallel=True, return_bias=False,
|
||||
quant_config=quant_config, prefix=f"{p}.attn.to_out.0",
|
||||
)
|
||||
results.append(
|
||||
check(
|
||||
"attn.to_out.0 (row)",
|
||||
to_out,
|
||||
art,
|
||||
f"transformer_blocks.{b}.attn.to_out.0",
|
||||
[(f"transformer_blocks.{b}.attn.to_out.0", None)],
|
||||
4096,
|
||||
torch.bfloat16,
|
||||
)
|
||||
)
|
||||
|
||||
proj = ColumnParallelLinear(
|
||||
4096, 12288, bias=False, gather_output=False, return_bias=False,
|
||||
quant_config=quant_config, prefix=f"{p}.img_mlp.proj",
|
||||
)
|
||||
results.append(
|
||||
check(
|
||||
"img_mlp.proj (column)",
|
||||
proj,
|
||||
art,
|
||||
f"transformer_blocks.{b}.img_mlp.proj",
|
||||
[(f"transformer_blocks.{b}.img_mlp.proj", None)],
|
||||
4096,
|
||||
torch.bfloat16,
|
||||
)
|
||||
)
|
||||
|
||||
mlp_out = RowParallelLinear(
|
||||
12288, 4096, bias=False, input_is_parallel=True, return_bias=False,
|
||||
quant_config=quant_config, prefix=f"{p}.img_mlp.out",
|
||||
)
|
||||
results.append(
|
||||
check(
|
||||
"img_mlp.out (row, wide-in)",
|
||||
mlp_out,
|
||||
art,
|
||||
f"transformer_blocks.{b}.img_mlp.out",
|
||||
[(f"transformer_blocks.{b}.img_mlp.out", None)],
|
||||
12288,
|
||||
torch.bfloat16,
|
||||
)
|
||||
)
|
||||
|
||||
destroy_model_parallel()
|
||||
destroy_distributed_environment()
|
||||
print(json.dumps(results, indent=1))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5 probe client: reference-conditioned requests against the int8
|
||||
weight-only compressed-tensors artifact, with the structure gate applied to
|
||||
every response.
|
||||
|
||||
Matrix pass (the issue's capacity matrix): one-, two-, three-, and
|
||||
four-reference fixtures at seeds 2301-2304 — wall time and per-image
|
||||
structure metrics land in the record for every request. Every request
|
||||
mirrors the sealed Diffusers baseline attempt 2026-10-07T170729Z-q3-4r
|
||||
(same frozen references, same rendered prompt, 20 steps, true_cfg_scale
|
||||
1.0, fixture resolution, no negative prompt), submitted one at a time
|
||||
through POST /v1/images/edits.
|
||||
|
||||
The structure gate (the lesson from issue 1's FP8 misread): a run only
|
||||
counts when the output is structurally sound — fully opaque RGBA and
|
||||
non-flat RGB. Broken FP8 output measured ~96.5-97% transparent with
|
||||
near-flat grey RGB; the BF16 path is 100% opaque. The gate numbers are
|
||||
recorded on every request regardless; `gate.py` summarizes them.
|
||||
|
||||
Usage: probe.py --out RUN_DIR [--matrix] [--host H:P]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
|
||||
W = pathlib.Path.home() / "Projects/vllm-omni-val/issue5"
|
||||
SUITE = (
|
||||
pathlib.Path.home()
|
||||
/ ".local/share/multi-model-helper-server/data/evaluation/media/image/references-suite-2"
|
||||
)
|
||||
STEPS = 20
|
||||
CFG = 1.0
|
||||
MATRIX_FIXTURES = (
|
||||
"edit-expression-ysolde", # 1 reference
|
||||
"edit-compose-pair", # 2 references
|
||||
"edit-compose-three", # 3 references (derived; see fixtures.json)
|
||||
"edit-compose-four", # 4 references
|
||||
)
|
||||
MATRIX_SEEDS = (2301, 2302, 2303, 2304)
|
||||
|
||||
|
||||
def structure_metrics(png: bytes) -> dict:
|
||||
"""Opacity + non-flatness numbers for the structure gate."""
|
||||
image = Image.open(io.BytesIO(png))
|
||||
array = np.asarray(image.convert("RGBA"))
|
||||
alpha = array[..., 3]
|
||||
rgb = array[..., :3].astype(np.float64)
|
||||
return {
|
||||
"opaque_fraction": round(float((alpha == 255).mean()), 4),
|
||||
"alpha_mean": round(float(alpha.mean()), 1),
|
||||
"rgb_std": [round(float(rgb[..., i].std()), 1) for i in range(3)],
|
||||
}
|
||||
|
||||
|
||||
def submit(host: str, fixture: dict, seed: int) -> tuple[dict, bytes | None]:
|
||||
files = []
|
||||
handles = []
|
||||
for ref in fixture["references"]:
|
||||
path = SUITE / f"{ref}.png"
|
||||
handle = open(path, "rb")
|
||||
handles.append(handle)
|
||||
files.append(("image[]", (path.name, handle, "image/png")))
|
||||
data = {
|
||||
"model": "Qwen/Qwen-Image-2.1",
|
||||
"prompt": fixture["prompt"],
|
||||
"seed": str(seed),
|
||||
"num_inference_steps": str(STEPS),
|
||||
"true_cfg_scale": str(CFG),
|
||||
"size": f"{fixture['width']}x{fixture['height']}",
|
||||
"n": "1",
|
||||
"response_format": "b64_json",
|
||||
"output_format": "png",
|
||||
"return_stage_metrics": "true",
|
||||
}
|
||||
rec: dict = {
|
||||
"fixture_id": None,
|
||||
"seed": seed,
|
||||
"n_references": len(fixture["references"]),
|
||||
"steps": STEPS,
|
||||
"true_cfg_scale": CFG,
|
||||
"size": f"{fixture['width']}x{fixture['height']}",
|
||||
}
|
||||
t0 = time.time()
|
||||
try:
|
||||
response = requests.post(
|
||||
f"http://{host}/v1/images/edits", data=data, files=files, timeout=1800
|
||||
)
|
||||
rec["wall_s"] = round(time.time() - t0, 2)
|
||||
rec["http"] = response.status_code
|
||||
if response.status_code == 200:
|
||||
body = response.json()
|
||||
png = base64.b64decode(body["data"][0]["b64_json"])
|
||||
rec["bytes"] = len(png)
|
||||
rec["sha256"] = hashlib.sha256(png).hexdigest()
|
||||
rec["structure"] = structure_metrics(png)
|
||||
rec["usage"] = body.get("usage")
|
||||
for key in ("stage_metrics", "metrics", "timings"):
|
||||
if body.get(key):
|
||||
rec[key] = body[key]
|
||||
return rec, png
|
||||
rec["error"] = response.text[:2000]
|
||||
return rec, None
|
||||
except Exception as exc: # noqa: BLE001 - recorded as the probe result
|
||||
rec["wall_s"] = round(time.time() - t0, 2)
|
||||
rec["error"] = f"{type(exc).__name__}: {exc}"
|
||||
return rec, None
|
||||
finally:
|
||||
for handle in handles:
|
||||
handle.close()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--out", required=True, type=pathlib.Path)
|
||||
parser.add_argument("--host", default="127.0.0.1:8091")
|
||||
parser.add_argument("--matrix", action="store_true")
|
||||
parser.add_argument(
|
||||
"--fixture",
|
||||
default="edit-compose-four",
|
||||
help="single fixture id for --single",
|
||||
)
|
||||
parser.add_argument("--single", action="store_true", help="one fixture, all seeds")
|
||||
parser.add_argument(
|
||||
"--seeds",
|
||||
type=lambda s: tuple(int(x) for x in s.split(",")),
|
||||
default=MATRIX_SEEDS,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
fixtures = json.loads((W / "fixtures.json").read_text())
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
plan: list[tuple[str, pathlib.Path, int]] = []
|
||||
if args.matrix:
|
||||
for fixture_id in MATRIX_FIXTURES:
|
||||
for seed in args.seeds:
|
||||
plan.append(
|
||||
(
|
||||
fixture_id,
|
||||
args.out / "matrix" / f"{fixture_id}.s{seed}.png",
|
||||
seed,
|
||||
)
|
||||
)
|
||||
if args.single:
|
||||
for seed in args.seeds:
|
||||
plan.append(
|
||||
(args.fixture, args.out / "matrix" / f"{args.fixture}.s{seed}.png", seed)
|
||||
)
|
||||
|
||||
records = []
|
||||
for fixture_id, out_path, seed in plan:
|
||||
rec, png = submit(args.host, fixtures[fixture_id], seed)
|
||||
rec["fixture_id"] = fixture_id
|
||||
records.append(rec)
|
||||
print(json.dumps(rec), flush=True)
|
||||
if png is not None:
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_bytes(png)
|
||||
elif rec.get("http") == 200:
|
||||
print(f"warning: 200 but no image for {fixture_id} seed {seed}", flush=True)
|
||||
|
||||
suffix = "matrix" if args.matrix else f"single-{args.fixture}"
|
||||
(args.out / f"{suffix}.json").write_text(json.dumps(records, indent=1) + "\n")
|
||||
ok = all(r.get("http") == 200 and "sha256" in r for r in records)
|
||||
print(("PASS" if ok else "FAIL") + f" {len(records)} requests", flush=True)
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5 probe: PSNR of the int8 artifact's vLLM outputs against the
|
||||
sealed Diffusers baseline attempt 2026-10-07T170729Z-q3-4r.
|
||||
|
||||
Usage: psnr.py --fixture FIXTURE_ID RUN_DIR [RUN_DIR ...]
|
||||
|
||||
Compares each RUN_DIR/matrix/FIXTURE_ID.sSEED.png against the baseline
|
||||
sample with the same fixture and seed. RGBA outputs are composited over
|
||||
white, matching issue 1's comparator. The sealed baseline covers the 1-,
|
||||
2-, and 4-reference fixtures; the derived 3-reference fixture has no
|
||||
sealed row (gate.py + visual check cover it).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import pathlib
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
ATTEMPT = (
|
||||
pathlib.Path.home()
|
||||
/ ".local/share/multi-model-helper-server/data/evaluation/media/image/attempts/2026-10-07T170729Z-q3-4r"
|
||||
)
|
||||
|
||||
|
||||
def to_rgb(array: np.ndarray) -> np.ndarray:
|
||||
if array.shape[2] == 4:
|
||||
alpha = array[..., 3:4].astype(np.float64) / 255.0
|
||||
bg = np.full_like(array[..., :3], 255, dtype=np.float64)
|
||||
return np.round(
|
||||
array[..., :3].astype(np.float64) * alpha + bg * (1 - alpha)
|
||||
).clip(0, 255).astype(np.uint8)
|
||||
return array[..., :3]
|
||||
|
||||
|
||||
def psnr(a: np.ndarray, b: np.ndarray) -> float:
|
||||
mse = float(np.mean((a.astype(np.float64) - b.astype(np.float64)) ** 2))
|
||||
return float("inf") if mse == 0 else 10 * math.log10(255.0**2 / mse)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--fixture", default="edit-compose-four")
|
||||
parser.add_argument("runs", nargs="+", type=pathlib.Path)
|
||||
args = parser.parse_args()
|
||||
fixture = args.fixture
|
||||
|
||||
samples = json.loads((ATTEMPT / "samples.json").read_text())["samples"]
|
||||
baseline = {
|
||||
s["seed"]: ATTEMPT / s["file"]
|
||||
for s in samples
|
||||
if s["fixture_id"] == fixture and s["status"] == "succeeded"
|
||||
}
|
||||
summary = {}
|
||||
for run_dir in args.runs:
|
||||
rows = []
|
||||
for seed, base_path in sorted(baseline.items()):
|
||||
ours = run_dir / "matrix" / f"{fixture}.s{seed}.png"
|
||||
if not ours.exists():
|
||||
rows.append({"seed": seed, "error": "missing"})
|
||||
continue
|
||||
bm, om = Image.open(base_path), Image.open(ours)
|
||||
ba, oa = np.asarray(bm), np.asarray(om)
|
||||
if ba.shape[:2] != oa.shape[:2]:
|
||||
rows.append(
|
||||
{"seed": seed, "error": f"size {oa.shape[:2]} vs {ba.shape[:2]}"}
|
||||
)
|
||||
continue
|
||||
use_rgba = bm.mode == "RGBA" and om.mode == "RGBA"
|
||||
a, b = (ba, oa) if use_rgba else (to_rgb(ba), to_rgb(oa))
|
||||
rows.append(
|
||||
{
|
||||
"seed": seed,
|
||||
"psnr_db": round(psnr(a, b), 2),
|
||||
"modes": f"{bm.mode}/{om.mode}",
|
||||
}
|
||||
)
|
||||
vals = [r["psnr_db"] for r in rows if "psnr_db" in r]
|
||||
if vals:
|
||||
summary[run_dir.name] = {
|
||||
"rows": rows,
|
||||
"mean_db": round(sum(vals) / len(vals), 2),
|
||||
"min_db": min(vals),
|
||||
"max_db": max(vals),
|
||||
"n": len(vals),
|
||||
}
|
||||
else:
|
||||
summary[run_dir.name] = {"rows": rows}
|
||||
print(json.dumps(summary, indent=1))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5: quantize the Qwen-Image-2.1 DiT to int8 weight-only (W8A16) and
|
||||
save it as a compressed-tensors pack-quantized checkpoint.
|
||||
|
||||
Quantizes exactly the linears the vllm-omni transformer constructs with
|
||||
``quant_config`` — per-block attention (to_q/to_k/to_v/to_out.0) and
|
||||
img_mlp (proj/gate_layer/out) — and copies every boundary layer (img_in,
|
||||
txt_in, time_text_embed, modulation, norm_out, proj_out) plus the RMSNorm
|
||||
q/k scales through in BF16. Per-output-channel symmetric int8, scales in
|
||||
fp32, weights packed 4x uint8 (biased by 128) per int32 via
|
||||
compressed-tensors' own ``pack_to_int32`` so the artifact matches the
|
||||
llm-compressor storage convention bit for bit.
|
||||
|
||||
The artifact is a full diffusers-layout model directory: the quantized
|
||||
``transformer/`` is real, the remaining components are symlinks into the
|
||||
sealed snapshot. ``transformer/config.json`` carries the quantization
|
||||
metadata wrapped per-component (``{"transformer": {...}}``) so vLLM-Omni's
|
||||
auto-detection quantizes the DiT only — the text encoder and VAE stay BF16.
|
||||
|
||||
A MANIFEST.json (inputs, versions, per-file sha256, quantization stats) is
|
||||
written beside the weights. Nothing here enters Git; the artifact lives
|
||||
under the helper-system state root.
|
||||
|
||||
Usage:
|
||||
quantize_dit_int8wo.py --snapshot SNAPSHOT_DIR --out ARTIFACT_DIR
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import hashlib
|
||||
import json
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
from compressed_tensors.compressors.pack_quantized.helpers import pack_to_int32
|
||||
from compressed_tensors.config import CompressionFormat
|
||||
from compressed_tensors.quantization import (
|
||||
QuantizationArgs,
|
||||
QuantizationConfig,
|
||||
QuantizationScheme,
|
||||
QuantizationStatus,
|
||||
QuantizationStrategy,
|
||||
QuantizationType,
|
||||
)
|
||||
from safetensors.torch import load_file, save_file
|
||||
|
||||
SNAPSHOT_COMMIT = "790c92633540aa0cb11d9abf19eb46d861714758"
|
||||
# Boundary layers the vllm-omni model pins to quant_config=None (BF16), plus
|
||||
# the non-linear tensors; everything else in a block is quantized.
|
||||
IGNORED_TARGETS = ["img_in", "txt_in", "time_text_embed", "modulation", "norm_out", "proj_out"]
|
||||
QUANTIZED_KEY = re.compile(
|
||||
r"^transformer_blocks\.\d+\."
|
||||
r"(attn\.(to_q|to_k|to_v|to_out\.0)|img_mlp\.(proj|gate_layer|out))\.weight$"
|
||||
)
|
||||
SYMLINKED_COMPONENTS = ("scheduler", "text_encoder", "vae", "processor")
|
||||
SYMLINKED_FILES = ("model_index.json",)
|
||||
|
||||
|
||||
def build_quantization_dict(scheme: str) -> dict:
|
||||
"""Serialized compressed-tensors metadata (llm-compressor schema).
|
||||
|
||||
scheme="int8wo": int8 weight-only, pack-quantized storage.
|
||||
scheme="w8a8dyn": int8 weights (static per-channel) + dynamic per-token
|
||||
symmetric int8 activations, int-quantized storage — the non-Marlin
|
||||
cutlass path on sm_86.
|
||||
"""
|
||||
weights = QuantizationArgs(
|
||||
num_bits=8,
|
||||
type=QuantizationType.INT,
|
||||
symmetric=True,
|
||||
strategy=QuantizationStrategy.CHANNEL,
|
||||
dynamic=False,
|
||||
observer="minmax",
|
||||
)
|
||||
input_activations = (
|
||||
QuantizationArgs(
|
||||
num_bits=8,
|
||||
type=QuantizationType.INT,
|
||||
symmetric=True,
|
||||
strategy=QuantizationStrategy.TOKEN,
|
||||
dynamic=True,
|
||||
)
|
||||
if scheme == "w8a8dyn"
|
||||
else None
|
||||
)
|
||||
scheme_obj = QuantizationScheme(targets=["Linear"], weights=weights, input_activations=input_activations)
|
||||
config = QuantizationConfig(
|
||||
config_groups={"group_0": scheme_obj},
|
||||
format=CompressionFormat.int_quantized if scheme == "w8a8dyn" else CompressionFormat.pack_quantized,
|
||||
ignore=list(IGNORED_TARGETS),
|
||||
quant_method="compressed-tensors",
|
||||
quantization_status=QuantizationStatus.FROZEN,
|
||||
)
|
||||
return config.to_dict()
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1 << 20), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def quantize_weight(weight: torch.Tensor, scheme: str) -> tuple[dict[str, torch.Tensor], dict]:
|
||||
"""Per-output-channel symmetric int8.
|
||||
|
||||
scheme="int8wo": q = round(W/s), s = rowmax(|W|)/127, packed via
|
||||
compressed-tensors' packer (uint8 biased by 128, 4 per int32).
|
||||
scheme="w8a8dyn": same quantization, stored unpacked as plain int8
|
||||
(int-quantized format) with fp32 scales.
|
||||
"""
|
||||
w = weight.detach().to(torch.float32)
|
||||
scale = w.abs().amax(dim=1, keepdim=True).clamp_min(1e-12) / 127.0
|
||||
q = torch.round(w / scale).clamp(-127, 127).to(torch.int8)
|
||||
dequant = q.to(torch.float32) * scale
|
||||
err = (w - dequant).norm() / w.norm().clamp_min(1e-12)
|
||||
stats = {"rel_l2": float(err), "out": int(w.shape[0]), "in": int(w.shape[1])}
|
||||
if scheme == "w8a8dyn":
|
||||
return {"weight": q.contiguous(), "weight_scale": scale.to(torch.float32).contiguous()}, stats
|
||||
return {
|
||||
"weight_packed": pack_to_int32(q.contiguous(), 8).contiguous(),
|
||||
"weight_scale": scale.to(torch.float32).contiguous(),
|
||||
"weight_shape": torch.tensor(weight.shape, dtype=torch.int64),
|
||||
}, stats
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--snapshot", type=Path, required=True, help="sealed snapshot dir")
|
||||
parser.add_argument("--out", type=Path, required=True, help="artifact dir to create")
|
||||
parser.add_argument(
|
||||
"--scheme",
|
||||
choices=("int8wo", "w8a8dyn"),
|
||||
default="int8wo",
|
||||
help="int8 weight-only (pack-quantized, Marlin path) or W8A8 int8-dynamic (int-quantized, cutlass path)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
snapshot = args.snapshot.resolve()
|
||||
if not (snapshot / "transformer" / "config.json").exists():
|
||||
sys.exit(f"snapshot transformer config not found under {snapshot}")
|
||||
if args.out.exists():
|
||||
sys.exit(f"refusing to overwrite existing {args.out}")
|
||||
artifact = args.out
|
||||
(artifact / "transformer").mkdir(parents=True)
|
||||
|
||||
index = json.loads(
|
||||
(snapshot / "transformer" / "diffusion_pytorch_model.safetensors.index.json").read_text()
|
||||
)
|
||||
shards = sorted(set(index["weight_map"].values()))
|
||||
state: dict[str, torch.Tensor] = {}
|
||||
quant_stats: dict[str, dict] = {}
|
||||
quant_params = bf16_params = 0
|
||||
for shard in shards:
|
||||
tensors = load_file(str(snapshot / "transformer" / shard))
|
||||
for name, tensor in tensors.items():
|
||||
if QUANTIZED_KEY.match(name):
|
||||
emitted, stats = quantize_weight(tensor, args.scheme)
|
||||
if args.scheme == "w8a8dyn":
|
||||
# int-quantized storage: plain int8 weight + scale, mirroring
|
||||
# the vLLM layer parameter names (weight / weight_scale).
|
||||
state[name] = emitted["weight"]
|
||||
state[f"{name}_scale"] = emitted["weight_scale"]
|
||||
else:
|
||||
state[f"{name}_packed"] = emitted["weight_packed"]
|
||||
state[f"{name}_scale"] = emitted["weight_scale"]
|
||||
state[f"{name}_shape"] = emitted["weight_shape"]
|
||||
quant_stats[name] = stats
|
||||
quant_params += tensor.numel()
|
||||
print(f"q {name} {tuple(tensor.shape)} rel_l2={stats['rel_l2']:.5f}", flush=True)
|
||||
else:
|
||||
state[name] = tensor
|
||||
bf16_params += tensor.numel()
|
||||
del tensors
|
||||
|
||||
weights_path = artifact / "transformer" / "diffusion_pytorch_model.safetensors"
|
||||
save_file(state, str(weights_path), metadata={"format": "pt"})
|
||||
total_bytes = weights_path.stat().st_size
|
||||
|
||||
config = json.loads((snapshot / "transformer" / "config.json").read_text())
|
||||
config["quantization_config"] = {"transformer": build_quantization_dict(args.scheme)}
|
||||
(artifact / "transformer" / "config.json").write_text(json.dumps(config, indent=2) + "\n")
|
||||
|
||||
for name in SYMLINKED_FILES:
|
||||
target = snapshot / name
|
||||
(artifact / name).symlink_to(target)
|
||||
for name in SYMLINKED_COMPONENTS:
|
||||
target = snapshot / name
|
||||
(artifact / name).symlink_to(target, target_is_directory=True)
|
||||
|
||||
files = [
|
||||
{"path": "transformer/config.json", "bytes": (artifact / "transformer" / "config.json").stat().st_size},
|
||||
{"path": "transformer/diffusion_pytorch_model.safetensors", "bytes": total_bytes},
|
||||
]
|
||||
for entry in files:
|
||||
entry["sha256"] = sha256_file(artifact / entry["path"])
|
||||
tree_json = snapshot.parent.parent / "trees" / f"{SNAPSHOT_COMMIT}.json"
|
||||
manifest = {
|
||||
"schema": f"issue5-{args.scheme}-v1",
|
||||
"created_utc": dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat(),
|
||||
"description": (
|
||||
f"Qwen-Image-2.1 DiT {args.scheme} "
|
||||
+ (
|
||||
"W8A8 int8-dynamic, int-quantized"
|
||||
if args.scheme == "w8a8dyn"
|
||||
else "int8 weight-only W8A16, pack-quantized"
|
||||
)
|
||||
+ ", compressed-tensors"
|
||||
),
|
||||
"inputs": {
|
||||
"snapshot": str(snapshot),
|
||||
"revision": SNAPSHOT_COMMIT,
|
||||
"tree_manifest": str(tree_json),
|
||||
"tree_sha256": sha256_file(tree_json) if tree_json.exists() else None,
|
||||
},
|
||||
"versions": {
|
||||
"python": platform.python_version(),
|
||||
"torch": torch.__version__,
|
||||
"compressed_tensors": __import__("compressed_tensors").__version__,
|
||||
},
|
||||
"quantization": {
|
||||
"method": "compressed-tensors",
|
||||
"format": "pack-quantized",
|
||||
"weights": "int8 symmetric per-output-channel, static, minmax",
|
||||
"config": build_quantization_dict(args.scheme),
|
||||
"ignored_layers": list(IGNORED_TARGETS),
|
||||
"note": "ignore list mirrors the vllm-omni model's quant_config=None boundary layers",
|
||||
},
|
||||
"stats": {
|
||||
"quantized_tensors": len(quant_stats),
|
||||
"quantized_params": quant_params,
|
||||
"bf16_params": bf16_params,
|
||||
"mean_rel_l2": sum(s["rel_l2"] for s in quant_stats.values()) / max(len(quant_stats), 1),
|
||||
"max_rel_l2": max((s["rel_l2"] for s in quant_stats.values()), default=0.0),
|
||||
"weights_file_bytes": total_bytes,
|
||||
},
|
||||
"files": files,
|
||||
}
|
||||
(artifact / "MANIFEST.json").write_text(json.dumps(manifest, indent=1) + "\n")
|
||||
print(json.dumps(manifest["stats"], indent=1))
|
||||
print(f"artifact: {artifact}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Issue 5 probe: serve the int8 weight-only compressed-tensors artifact on
|
||||
# the RTX 3090 with one labeled flag set. VRAM is polled once per second for
|
||||
# the whole server lifetime, matching issue 2's instrument (nvidia-smi csv
|
||||
# series). The default flag set is cpu-offload + eager; pass extras after
|
||||
# the label to override/extend (they are appended, vLLM last-one-wins).
|
||||
#
|
||||
# Usage: serve.sh LABEL [extra vllm flags...]
|
||||
set -uo pipefail
|
||||
|
||||
BASE=$HOME/Projects/vllm-omni-val
|
||||
W=$BASE/issue5
|
||||
LABEL=$1; shift
|
||||
RUN=$W/runs/$LABEL
|
||||
mkdir -p "$RUN"
|
||||
|
||||
STATE=$HOME/.local/state/multi-model-helper-server/state/image-service
|
||||
ARTIFACT=${ARTIFACT:-$STATE/artifacts/qwen-image-2.1-dit-int8wo}
|
||||
export HF_HOME=$STATE/weights
|
||||
export HF_HUB_OFFLINE=1
|
||||
|
||||
( while sleep 1; do nvidia-smi --query-gpu=memory.used,utilization.gpu --format=csv,noheader; done ) > "$RUN/vram.log" 2>&1 &
|
||||
POLL=$!
|
||||
T0=$(date +%s)
|
||||
echo "model: $ARTIFACT" > "$RUN/flags.txt"
|
||||
echo "flags: $*" >> "$RUN/flags.txt"
|
||||
|
||||
$BASE/venv/bin/vllm serve "$ARTIFACT" --omni --port 8091 \
|
||||
--served-model-name Qwen/Qwen-Image-2.1 \
|
||||
--enforce-eager --enable-cpu-offload "$@" > "$RUN/serve.log" 2>&1
|
||||
RC=$?
|
||||
kill $POLL 2>/dev/null
|
||||
echo "serve_rc=$RC wall=$(( $(date +%s) - T0 ))s" >> "$RUN/serve.log"
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5: verify the int8 weight-only artifact round-trips to the BF16 original.
|
||||
|
||||
Unpacks ``weight_packed`` with compressed-tensors' own unpacker, dequantizes
|
||||
with the stored per-channel scales, and compares against the sealed snapshot's
|
||||
BF16 tensors. Expected rel-L2 ~1% (the quantizer's measured error); a large
|
||||
error means the artifact itself is corrupt, a small error means any serve-path
|
||||
collapse lives downstream of the checkpoint (loading/repack/kernel).
|
||||
|
||||
Usage: verify_artifact.py --artifact ARTIFACT_DIR --snapshot SNAPSHOT_DIR [--tensors N]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
import torch
|
||||
from compressed_tensors.compressors.pack_quantized.helpers import unpack_from_int32
|
||||
from safetensors.torch import load_file
|
||||
|
||||
QUANTIZED_KEY = "weight_packed" # checkpoint suffix; stripping it yields the base ".weight" name
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--artifact", type=pathlib.Path, required=True)
|
||||
parser.add_argument("--snapshot", type=pathlib.Path, required=True)
|
||||
parser.add_argument("--tensors", type=int, default=6, help="number of quantized tensors to check")
|
||||
args = parser.parse_args()
|
||||
|
||||
art = load_file(str(args.artifact / "transformer" / "diffusion_pytorch_model.safetensors"))
|
||||
index = json.loads(
|
||||
(args.snapshot / "transformer" / "diffusion_pytorch_model.safetensors.index.json").read_text()
|
||||
)["weight_map"]
|
||||
shard_cache: dict[str, dict] = {}
|
||||
|
||||
checked = 0
|
||||
for name in sorted(k.removesuffix("_packed") for k in art if k.endswith(f".{QUANTIZED_KEY}")):
|
||||
if checked >= args.tensors:
|
||||
break
|
||||
base_name = name
|
||||
shard = index[base_name]
|
||||
if shard not in shard_cache:
|
||||
shard_cache.clear() # keep at most one ~7 GB shard resident
|
||||
shard_cache[shard] = load_file(str(args.snapshot / "transformer" / shard))
|
||||
ref = shard_cache[shard][base_name].to(torch.float32)
|
||||
shape = tuple(art[f"{base_name}_shape"].tolist())
|
||||
q = unpack_from_int32(art[f"{base_name}_packed"], 8, shape)
|
||||
dequant = q.to(torch.float32) * art[f"{base_name}_scale"]
|
||||
rel = float((dequant - ref).norm() / ref.norm())
|
||||
print(
|
||||
f"{base_name}: shape {shape} rel_l2={rel:.5f} "
|
||||
f"scale {tuple(art[f'{base_name}_scale'].shape)}/{art[f'{base_name}_scale'].dtype}"
|
||||
)
|
||||
checked += 1
|
||||
print(f"checked {checked} tensors")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python
|
||||
"""Issue 5 round two: isolate the compressed-tensors W8A8-int8-dynamic path.
|
||||
|
||||
Mirrors marlin_isolate.py but for the int-quantized W8A8 dynamic scheme
|
||||
(int8 weights, static per-output-channel scales, dynamic symmetric per-token
|
||||
int8 activations) whose kernel on sm_86 is the cutlass int8 scaled-MM —
|
||||
no Marlin in the path. Loads synthetic int8 tensors through the production
|
||||
weight-loader machinery and compares layer(x) against a float32 reference
|
||||
computed from the same dequantized weights with per-token dynamic int8
|
||||
activation quantization applied to the reference input.
|
||||
|
||||
Expected rel-L2 when the chain is correct: a few percent (int8 weight error
|
||||
~1% plus int8 per-token activation rounding).
|
||||
|
||||
Usage: w8a8_isolate.py [--block N]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def synth(out: int, inp: int, ones: bool = False) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Per-channel symmetric int8 weight + scale, mirroring the artifact builder.
|
||||
|
||||
ones=True emits an analytically known weight (q=127, scale=1/127 -> W=1):
|
||||
with an all-ones input row the layer output must be exactly in_features.
|
||||
"""
|
||||
if ones:
|
||||
q = torch.full((out, inp), 127, dtype=torch.int8)
|
||||
return q, torch.full((out, 1), 1.0 / 127.0, dtype=torch.float32)
|
||||
w = torch.randn(out, inp, dtype=torch.float32)
|
||||
scale = w.abs().amax(dim=1, keepdim=True).clamp_min(1e-12) / 127.0
|
||||
q = torch.round(w / scale).clamp(-127, 127).to(torch.int8)
|
||||
return q, scale.to(torch.float32)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--block", type=int, default=0)
|
||||
parser.add_argument("--ones", action="store_true",
|
||||
help="analytic weights (W=1) and ones input; output must equal in_features")
|
||||
parser.add_argument("--act-dtype", choices=("bfloat16", "float16"), default="bfloat16")
|
||||
args = parser.parse_args()
|
||||
|
||||
import vllm.distributed
|
||||
from vllm.config import VllmConfig, set_current_vllm_config
|
||||
from vllm.distributed import (
|
||||
destroy_distributed_environment,
|
||||
destroy_model_parallel,
|
||||
initialize_model_parallel,
|
||||
)
|
||||
from vllm.model_executor.layers.linear import QKVParallelLinear, RowParallelLinear
|
||||
from compressed_tensors.quantization import (
|
||||
QuantizationArgs,
|
||||
QuantizationConfig,
|
||||
QuantizationScheme,
|
||||
QuantizationStatus,
|
||||
QuantizationStrategy,
|
||||
QuantizationType,
|
||||
)
|
||||
from compressed_tensors.config import CompressionFormat
|
||||
from vllm_omni.quantization import build_quant_config, resolve_component_quant_config
|
||||
|
||||
weights = QuantizationArgs(num_bits=8, type=QuantizationType.INT, symmetric=True,
|
||||
strategy=QuantizationStrategy.CHANNEL, dynamic=False, observer="minmax")
|
||||
acts = QuantizationArgs(num_bits=8, type=QuantizationType.INT, symmetric=True,
|
||||
strategy=QuantizationStrategy.TOKEN, dynamic=True)
|
||||
scheme = QuantizationScheme(targets=["Linear"], weights=weights, input_activations=acts)
|
||||
disk = QuantizationConfig(
|
||||
config_groups={"group_0": scheme}, format=CompressionFormat.int_quantized,
|
||||
ignore=["img_in", "txt_in", "time_text_embed", "modulation", "norm_out", "proj_out"],
|
||||
quant_method="compressed-tensors", quantization_status=QuantizationStatus.FROZEN,
|
||||
).to_dict()
|
||||
quant_config = resolve_component_quant_config(
|
||||
build_quant_config({"transformer": disk}), "transformer"
|
||||
)
|
||||
|
||||
vllm.distributed.init_distributed_environment(
|
||||
backend="gloo", world_size=1, rank=0, local_rank=0,
|
||||
distributed_init_method="tcp://127.0.0.1:29602",
|
||||
)
|
||||
with set_current_vllm_config(VllmConfig()):
|
||||
initialize_model_parallel(tensor_model_parallel_size=1)
|
||||
|
||||
device = torch.device("cuda")
|
||||
torch.manual_seed(2301)
|
||||
tensors = {
|
||||
"q": synth(4096, 4096, args.ones),
|
||||
"k": synth(4096, 4096, args.ones),
|
||||
"v": synth(4096, 4096, args.ones),
|
||||
"to_out": synth(4096, 4096, args.ones),
|
||||
}
|
||||
|
||||
b = args.block
|
||||
p = f"transformer.transformer_blocks.{b}"
|
||||
results = []
|
||||
with torch.device(device.type):
|
||||
to_qkv = QKVParallelLinear(
|
||||
hidden_size=4096, head_size=128, total_num_heads=32, bias=False,
|
||||
quant_config=quant_config, prefix=f"{p}.attn.to_qkv",
|
||||
)
|
||||
for part, shard in (("q", "q"), ("k", "k"), ("v", "v")):
|
||||
q, s = tensors[part]
|
||||
to_qkv.weight.weight_loader(to_qkv.weight, q.to(device), shard)
|
||||
to_qkv.weight_scale.weight_loader(to_qkv.weight_scale, s.to(device), shard)
|
||||
to_qkv.quant_method.process_weights_after_loading(to_qkv)
|
||||
|
||||
to_out = RowParallelLinear(
|
||||
4096, 4096, bias=False, input_is_parallel=True, return_bias=False,
|
||||
quant_config=quant_config, prefix=f"{p}.attn.to_out.0",
|
||||
)
|
||||
q, s = tensors["to_out"]
|
||||
to_out.weight.weight_loader(to_out.weight, q.to(device))
|
||||
to_out.weight_scale.weight_loader(to_out.weight_scale, s.to(device))
|
||||
loaded_bitwise_ok = bool(torch.equal(to_out.weight.detach().cpu(), q))
|
||||
loaded_scale_ok = bool(torch.allclose(to_out.weight_scale.detach().float().cpu(), s))
|
||||
to_out.quant_method.process_weights_after_loading(to_out)
|
||||
|
||||
for name, layer, parts, in_features in (
|
||||
("attn.to_qkv (fused q/k/v)", to_qkv, ("q", "k", "v"), 4096),
|
||||
("attn.to_out.0 (row)", to_out, ("to_out",), 4096),
|
||||
):
|
||||
if args.ones:
|
||||
x = torch.ones(4, in_features, dtype=getattr(torch, args.act_dtype), device=device)
|
||||
else:
|
||||
x = torch.randn(512, in_features, dtype=getattr(torch, args.act_dtype), device=device)
|
||||
result = layer(x)
|
||||
out = (result[0] if isinstance(result, tuple) else result).float()
|
||||
|
||||
if args.ones:
|
||||
expected = torch.full_like(out, float(in_features))
|
||||
rel = float((out - expected).norm() / expected.norm())
|
||||
finite = bool(torch.isfinite(out).all())
|
||||
results.append({
|
||||
"layer": name,
|
||||
"rel_l2": round(rel, 6) if finite else "NaN",
|
||||
"out_finite": finite,
|
||||
"out_row0_first8": [round(v, 2) for v in out[0, :8].tolist()],
|
||||
})
|
||||
continue
|
||||
|
||||
ref_w = torch.cat([tensors[k][0].float() * tensors[k][1] for k in parts], dim=0)
|
||||
# Reference with the same dynamic per-token symmetric int8 activation
|
||||
# quantization the kernel applies (dequantized acts, dequantized weights).
|
||||
xs = x.float()
|
||||
act_scale = xs.abs().amax(dim=1, keepdim=True).clamp_min(1e-12) / 127.0
|
||||
x_dq = torch.round(xs / act_scale).clamp(-127, 127) * act_scale
|
||||
ref = x_dq @ ref_w.T.to(device)
|
||||
finite = bool(torch.isfinite(out).all())
|
||||
rel = float((out - ref).norm() / ref.norm())
|
||||
row = {"layer": name, "rel_l2": round(rel, 5) if finite else "NaN", "out_finite": finite}
|
||||
if name.startswith("attn.to_out"):
|
||||
row["loaded_bitwise_ok"] = loaded_bitwise_ok
|
||||
row["loaded_scale_ok"] = loaded_scale_ok
|
||||
# Reference computed from the layer's own post-repack weight
|
||||
# parameter ((K, N) after the transpose) and scales.
|
||||
w_layer = to_out.weight.detach().float() # (K, N)
|
||||
s_layer = to_out.weight_scale.detach().float().squeeze(1) # (N,)
|
||||
ref_layer = x_dq @ (w_layer * s_layer.unsqueeze(0)).to(device)
|
||||
row["rel_l2_vs_layer_weight"] = round(float((out - ref_layer).norm() / ref_layer.norm()), 5)
|
||||
results.append(row)
|
||||
|
||||
destroy_model_parallel()
|
||||
destroy_distributed_environment()
|
||||
print(json.dumps(results, indent=1))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -232,6 +232,11 @@ ignore = [
|
||||
# F-correctness rules still apply.
|
||||
"vllm_omni/model_executor/models/nemotron_voicechat/nemo_vendored/**" = ["E501", "E721", "E741", "F401", "F841", "I001", "N803", "N814", "UP006", "UP007", "UP008", "UP011", "UP032", "UP035", "W291", "W293"]
|
||||
"vllm_omni/experimental/fullduplex/joyvl/decision/prompts.py" = ["E501"] # System/summarizer prompts are long prose lines
|
||||
# Kandinsky 6 keeps native/BigVGAN names (T/H/W, `l`) and NVIDIA vocoder
|
||||
# docstring length so the port stays diffable against the source modules.
|
||||
"vllm_omni/diffusion/models/kandinsky6/kandinsky6_transformer.py" = ["N803", "E741"]
|
||||
"vllm_omni/diffusion/models/kandinsky6/pipeline_kandinsky6.py" = ["N803"]
|
||||
"vllm_omni/diffusion/models/kandinsky6/modeling_kandinsky6_audio.py" = ["E501", "E741", "N803", "UP008"]
|
||||
|
||||
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
||||
"librosa".msg = "The librosa module is banned, use vllm.multimodal helpers instead"
|
||||
@@ -277,6 +282,7 @@ markers = [
|
||||
"parallel: Parallelism/distributed tests",
|
||||
"sp: Sequence parallelism tests",
|
||||
"example: Doc example code tests",
|
||||
"nvidia_only: Tests that require NVIDIA CUDA and must not run on ROCm",
|
||||
# platform markers
|
||||
# Tag with [hardware-platform] for tests.helpers.mark.get_supported_platforms().
|
||||
# Add [gpu] on accelerator platforms that should also get pytest.mark.gpu.
|
||||
@@ -364,6 +370,8 @@ extend-ignore-identifiers-re = [
|
||||
# (output feature dim), not "doubt".
|
||||
".*dout.*",
|
||||
".*DOUT.*",
|
||||
# Kandinsky nabla threshold argument (`thr`), not the word "the".
|
||||
"^thr$",
|
||||
]
|
||||
extend-ignore-re = [
|
||||
"\\.\\.\\.n,d->\\.\\.\\.nd",
|
||||
@@ -404,3 +412,8 @@ IY = "IY"
|
||||
# Speech DAC codec identifiers (convnet_type, transconvnet_type) and is not a
|
||||
# typo of "convent".
|
||||
convnet = "convnet"
|
||||
# Upstream Kandinsky system prompt. "promt" and "scren" are part of the
|
||||
# conditioning text and must not be corrected.
|
||||
thr = "thr"
|
||||
promt = "promt"
|
||||
scren = "scren"
|
||||
Loaded 100 of 573 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user