Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29d2f157db | ||
|
|
ddcc050a94 | ||
|
|
50f4e675c1 | ||
|
|
159dceaf65 | ||
|
|
d10bf78c60 |
No files matched your search
+14
-4
@@ -16,21 +16,31 @@ nav:
|
|||||||
- Examples:
|
- Examples:
|
||||||
- examples/README.md
|
- examples/README.md
|
||||||
- Offline Inference:
|
- Offline Inference:
|
||||||
|
- GLM-Image Multistage End-to-End Inference: user_guide/examples/offline_inference/glm_image.md
|
||||||
|
- HunyuanImage-3.0 Image-to-Text Inference: user_guide/examples/offline_inference/hunyuan_image3.md
|
||||||
- Image-To-Image: user_guide/examples/offline_inference/image_to_image.md
|
- Image-To-Image: user_guide/examples/offline_inference/image_to_image.md
|
||||||
- Image-To-Video: user_guide/examples/offline_inference/image_to_video.md
|
- Image-To-Video: user_guide/examples/offline_inference/image_to_video.md
|
||||||
- Speech-To-Video: user_guide/examples/offline_inference/speech_to_video.md
|
- InternVLA-A1: user_guide/examples/offline_inference/internvla_a1.md
|
||||||
|
- MiMo-Audio: user_guide/examples/offline_inference/mimo_audio.md
|
||||||
|
- Qwen2.5-Omni: user_guide/examples/offline_inference/qwen2_5_omni.md
|
||||||
|
- Qwen3-Omni: user_guide/examples/offline_inference/qwen3_omni.md
|
||||||
- Text-To-Audio: user_guide/examples/offline_inference/text_to_audio.md
|
- Text-To-Audio: user_guide/examples/offline_inference/text_to_audio.md
|
||||||
- Text-To-Image: user_guide/examples/offline_inference/text_to_image.md
|
- Text-To-Image: user_guide/examples/offline_inference/text_to_image.md
|
||||||
- Text-To-Speech: user_guide/examples/offline_inference/text_to_speech.md
|
- Text-To-Speech: user_guide/examples/offline_inference/text_to_speech.md
|
||||||
- Text-To-Video: user_guide/examples/offline_inference/text_to_video.md
|
- Text-To-Video: user_guide/examples/offline_inference/text_to_video.md
|
||||||
- X-To-Text: user_guide/examples/offline_inference/x_to_text.md
|
|
||||||
- X-To-Video-Audio: user_guide/examples/offline_inference/x_to_video_audio.md
|
- X-To-Video-Audio: user_guide/examples/offline_inference/x_to_video_audio.md
|
||||||
- Online Serving:
|
- Online Serving:
|
||||||
|
- BAGEL-7B-MoT: user_guide/examples/online_serving/bagel.md
|
||||||
|
- vLLM-Omni Helm Chart: user_guide/examples/online_serving/chart-helm.md
|
||||||
|
- Diffusers Backend Adapter: user_guide/examples/online_serving/diffusers_pipeline_adapter.md
|
||||||
|
- GLM-Image Online Serving: user_guide/examples/online_serving/glm_image.md
|
||||||
- Image-To-Image: user_guide/examples/online_serving/image_to_image.md
|
- Image-To-Image: user_guide/examples/online_serving/image_to_image.md
|
||||||
- Image-To-Video: user_guide/examples/online_serving/image_to_video.md
|
- Image-To-Video: user_guide/examples/online_serving/image_to_video.md
|
||||||
- Speech-To-Video: user_guide/examples/online_serving/speech_to_video.md
|
- MiMo-Audio: user_guide/examples/online_serving/mimo_audio.md
|
||||||
- Text-To-Image: user_guide/examples/online_serving/text_to_image.md
|
- Qwen2.5-Omni: user_guide/examples/online_serving/qwen2_5_omni.md
|
||||||
|
- Qwen3-Omni: user_guide/examples/online_serving/qwen3_omni.md
|
||||||
- Text-To-Speech: user_guide/examples/online_serving/text_to_speech.md
|
- Text-To-Speech: user_guide/examples/online_serving/text_to_speech.md
|
||||||
|
- Text-To-Image: user_guide/examples/online_serving/text_to_image.md
|
||||||
- Text-To-Video: user_guide/examples/online_serving/text_to_video.md
|
- Text-To-Video: user_guide/examples/online_serving/text_to_video.md
|
||||||
- General:
|
- General:
|
||||||
- usage/*
|
- usage/*
|
||||||
|
|||||||
@@ -24,22 +24,15 @@ SERVING_MODE_TITLES = {
|
|||||||
"online_serving": "Online serving",
|
"online_serving": "Online serving",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keep the Examples navigation focused on reusable modality/task entry points.
|
# Generated example pages keep their source-derived URLs even when their
|
||||||
# Model-specific scripts remain available under ``examples/`` and are surfaced
|
# navigation category changes. Quantization helpers are feature documentation,
|
||||||
# from these shared pages instead of getting one page per model.
|
# not user-serving examples, so keep them under the existing Features /
|
||||||
GENERAL_EXAMPLE_SLUGS = frozenset(
|
# Quantization section instead of creating a second top-level Quantization
|
||||||
{
|
# category under Examples.
|
||||||
"image_to_image",
|
EXAMPLE_NAV_TARGETS = {
|
||||||
"image_to_video",
|
"quantization": "quantization",
|
||||||
"speech_to_video",
|
|
||||||
"text_to_audio",
|
|
||||||
"text_to_image",
|
|
||||||
"text_to_speech",
|
|
||||||
"text_to_video",
|
|
||||||
"x_to_text",
|
|
||||||
"x_to_video_audio",
|
|
||||||
}
|
}
|
||||||
)
|
GENERATED_QUANTIZATION_EXAMPLES_TITLE = "Generated Examples"
|
||||||
|
|
||||||
|
|
||||||
def load_model_display_names() -> dict[str, str]:
|
def load_model_display_names() -> dict[str, str]:
|
||||||
@@ -63,13 +56,6 @@ def load_model_display_names() -> dict[str, str]:
|
|||||||
MODEL_DISPLAY_NAMES = load_model_display_names()
|
MODEL_DISPLAY_NAMES = load_model_display_names()
|
||||||
|
|
||||||
|
|
||||||
def is_general_example(example: "Example") -> bool:
|
|
||||||
"""Return whether an example should get a generated serving page."""
|
|
||||||
if example.category not in SERVING_MODE_TITLES:
|
|
||||||
return True
|
|
||||||
return example.path.stem in GENERAL_EXAMPLE_SLUGS
|
|
||||||
|
|
||||||
|
|
||||||
def fix_case(text: str) -> str:
|
def fix_case(text: str) -> str:
|
||||||
subs = {
|
subs = {
|
||||||
"api": "API",
|
"api": "API",
|
||||||
@@ -372,9 +358,12 @@ def update_nav_file(examples: list[Example]):
|
|||||||
items.append({example.nav_title: rel_path.as_posix()})
|
items.append({example.nav_title: rel_path.as_posix()})
|
||||||
return items
|
return items
|
||||||
|
|
||||||
# Build the new Examples section - start with preserved items.
|
# Build the new Examples section - start with preserved items. Categories
|
||||||
|
# not explicitly routed elsewhere retain the historical Examples behavior.
|
||||||
examples_section = preserved_items.copy()
|
examples_section = preserved_items.copy()
|
||||||
for category in sorted(examples_by_category.keys()):
|
for category in sorted(examples_by_category.keys()):
|
||||||
|
if EXAMPLE_NAV_TARGETS.get(category, "examples") != "examples":
|
||||||
|
continue
|
||||||
items = category_items(category)
|
items = category_items(category)
|
||||||
if items:
|
if items:
|
||||||
category_title = fix_case(category.replace("_", " ").title())
|
category_title = fix_case(category.replace("_", " ").title())
|
||||||
@@ -383,6 +372,36 @@ def update_nav_file(examples: list[Example]):
|
|||||||
# Update the nav structure
|
# Update the nav structure
|
||||||
nav_list[user_guide_idx]["User Guide"][examples_idx]["Examples"] = examples_section
|
nav_list[user_guide_idx]["User Guide"][examples_idx]["Examples"] = examples_section
|
||||||
|
|
||||||
|
features_content = next(
|
||||||
|
(item["Features"] for item in user_guide_content if isinstance(item, dict) and "Features" in item),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
quantization_content = None
|
||||||
|
if isinstance(features_content, list):
|
||||||
|
quantization_content = next(
|
||||||
|
(item["Quantization"] for item in features_content if isinstance(item, dict) and "Quantization" in item),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(quantization_content, list):
|
||||||
|
# Remove only the group owned by this hook so repeated builds are
|
||||||
|
# idempotent and hand-authored Quantization navigation is retained.
|
||||||
|
quantization_content[:] = [
|
||||||
|
item
|
||||||
|
for item in quantization_content
|
||||||
|
if not (isinstance(item, dict) and GENERATED_QUANTIZATION_EXAMPLES_TITLE in item)
|
||||||
|
]
|
||||||
|
|
||||||
|
quantization_examples = examples_by_category.get("quantization")
|
||||||
|
if quantization_examples:
|
||||||
|
if not isinstance(quantization_content, list):
|
||||||
|
logger.warning(
|
||||||
|
"Could not find 'User Guide' -> 'Features' -> 'Quantization' section; "
|
||||||
|
"omitting generated quantization examples from navigation"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
quantization_content.append({GENERATED_QUANTIZATION_EXAMPLES_TITLE: category_items("quantization")})
|
||||||
|
|
||||||
# Write back to file
|
# Write back to file
|
||||||
nav_data["nav"] = nav_list
|
nav_data["nav"] = nav_list
|
||||||
with open(NAV_FILE, "w", encoding="utf-8") as f:
|
with open(NAV_FILE, "w", encoding="utf-8") as f:
|
||||||
@@ -413,8 +432,6 @@ def on_startup(command: Literal["build", "gh-deploy", "serve"], dirty: bool):
|
|||||||
for path in category.glob("*/*.md"):
|
for path in category.glob("*/*.md"):
|
||||||
examples.append(Example(path.parent, category.stem))
|
examples.append(Example(path.parent, category.stem))
|
||||||
|
|
||||||
examples = [example for example in examples if is_general_example(example)]
|
|
||||||
|
|
||||||
# Generate the example documentation
|
# Generate the example documentation
|
||||||
for example in sorted(examples, key=lambda e: e.path.stem):
|
for example in sorted(examples, key=lambda e: e.path.stem):
|
||||||
example_name = f"{example.path.stem}.md"
|
example_name = f"{example.path.stem}.md"
|
||||||
|
|||||||
@@ -4,17 +4,6 @@ This recipe uses online FP8 weight quantization, tiled VAE decode, and a single
|
|||||||
resident partition. GB10 is a unified-memory platform, so unlike the discrete-GPU
|
resident partition. GB10 is a unified-memory platform, so unlike the discrete-GPU
|
||||||
recipes it uses **no offload of any kind** — see the capacity note below.
|
recipes it uses **no offload of any kind** — see the capacity note below.
|
||||||
|
|
||||||
Validated on:
|
|
||||||
|
|
||||||
- Host: DGX Spark (GB10), aarch64
|
|
||||||
- GPUs: 1 (unified memory)
|
|
||||||
- Driver: <FILL_DRIVER_VERSION>
|
|
||||||
- vLLM: 0.26.0
|
|
||||||
- vLLM-Omni: `main` at `e1aa6eae75c460cd1893bc320546e81e66973831`
|
|
||||||
- Workloads: T2VA and Ref2VA, 960x576, `duration=8.0`, 50 steps, `flow_shift=12`,
|
|
||||||
`seed=1101`
|
|
||||||
|
|
||||||
|
|
||||||
## Capacity requirements
|
## Capacity requirements
|
||||||
|
|
||||||
| Resource | DGX Spark (GB10) |
|
| Resource | DGX Spark (GB10) |
|
||||||
@@ -22,8 +11,7 @@ Validated on:
|
|||||||
| Unified memory | 128 GB LPDDR5X (~121 GiB usable) |
|
| Unified memory | 128 GB LPDDR5X (~121 GiB usable) |
|
||||||
| GPUs | 1 |
|
| GPUs | 1 |
|
||||||
| Checkpoint storage | 135 GiB per partition |
|
| Checkpoint storage | 135 GiB per partition |
|
||||||
| Measured peak allocator high-water, T2VA (FP8) | 97.7 GiB |
|
| Measured peak allocator high-water (FP8) | 97.7 GiB |
|
||||||
| Measured peak allocator high-water, Ref2VA (FP8) | 102.8 GiB |
|
|
||||||
|
|
||||||
`FL2VA` and `Ref2VA` are separate 135 GiB checkpoint partitions. Start one server
|
`FL2VA` and `Ref2VA` are separate 135 GiB checkpoint partitions. Start one server
|
||||||
at a time.
|
at a time.
|
||||||
@@ -41,21 +29,9 @@ math relative to the RTX and datacenter recipes:
|
|||||||
fit in 121 GiB. Online FP8 quantizes the DiT only (62 GiB to ~31 GiB); the
|
fit in 121 GiB. Online FP8 quantizes the DiT only (62 GiB to ~31 GiB); the
|
||||||
Qwen3-VL text encoder and both VAEs stay BF16.
|
Qwen3-VL text encoder and both VAEs stay BF16.
|
||||||
|
|
||||||
With ~97.7 GiB of the pool held by the allocator at peak, T2VA leaves roughly
|
With ~97.7 GiB of the pool held by the allocator at peak, there is roughly 23 GiB
|
||||||
23 GiB of headroom for the OS, page cache, and any other process on the box.
|
of headroom for the OS, page cache, and any other process on the box. Treat this
|
||||||
Ref2VA is tighter on both counts. The allocator high-water rises to 102.8 GiB,
|
platform as single-tenant while a request is in flight.
|
||||||
and the allocator figure understates true pool usage.
|
|
||||||
|
|
||||||
Sampling `/proc/meminfo` at 1 Hz across a 50-step Ref2VA request on a host with
|
|
||||||
`MemTotal: 127600524 kB` (121.7 GiB), `MemAvailable` started at 8.1 GiB, fell to
|
|
||||||
a trough of **7.4 GiB**, and recovered to 8.9 GiB. At the trough the pool held
|
|
||||||
roughly **114.3 GiB** — 11.4 GiB more than the 102.8 GiB the allocator reports,
|
|
||||||
which is the CUDA context, non-PyTorch allocations, and page cache the header
|
|
||||||
does not count.
|
|
||||||
|
|
||||||
Treat this platform as strictly single-tenant for Ref2VA. There is no room for a
|
|
||||||
second process, and adding reference images, reference videos, or a larger output
|
|
||||||
shape will run into the OOM killer.
|
|
||||||
|
|
||||||
## One GB10: 960x576, 8 seconds
|
## One GB10: 960x576, 8 seconds
|
||||||
|
|
||||||
@@ -118,52 +94,6 @@ ffprobe -v error -show_entries \
|
|||||||
Both requests return `200 OK` with an MP4 body: H.264 video at the requested
|
Both requests return `200 OK` with an MP4 body: H.264 video at the requested
|
||||||
geometry plus a 32 kHz stereo AAC track.
|
geometry plus a 32 kHz stereo AAC track.
|
||||||
|
|
||||||
## One GB10: Ref2VA, 960x576, 8 seconds
|
|
||||||
|
|
||||||
`FL2VA` and `Ref2VA` are separate 135 GiB partitions and only one fits at a time.
|
|
||||||
Stop the FL2VA server before starting this one.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
CUDA_VISIBLE_DEVICES=0 \
|
|
||||||
FLASHINFER_DISABLE_VERSION_CHECK=1 \
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD=spawn \
|
|
||||||
VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400 \
|
|
||||||
vllm serve /path/to/MiniMax-H3/Ref2VA \
|
|
||||||
--omni --trust-remote-code --host 0.0.0.0 --port 8000 \
|
|
||||||
--init-timeout 3600 \
|
|
||||||
--num-gpus 1 --tensor-parallel-size 1 --text-encoder-tp-size 1 \
|
|
||||||
--usp 1 --ring 1 --vae-patch-parallel-size 1 \
|
|
||||||
--vae-parallel-mode tile --vae-use-tiling \
|
|
||||||
--quantization fp8 --enforce-eager \
|
|
||||||
--diffusion-attention-backend CUDNN_ATTN
|
|
||||||
```
|
|
||||||
|
|
||||||
The 7200 s timeout used for T2VA is too short here: a 50-step Ref2VA request
|
|
||||||
takes over an hour on this platform. Use 14400 s.
|
|
||||||
|
|
||||||
Unlike `t2va`, `ref2va` defaults to a 16:9 output ratio, so `aspect_ratio` can be
|
|
||||||
omitted when `width` and `height` are supplied.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl --fail-with-body -sS -X POST http://127.0.0.1:8000/v1/videos/sync \
|
|
||||||
-D ref2va-headers.txt \
|
|
||||||
-w 'client_e2e_s=%{time_total}\n' \
|
|
||||||
--max-time 14400 \
|
|
||||||
-F 'input_reference=@/path/to/reference.jpg;type=image/jpeg' \
|
|
||||||
-F 'prompt=A hand rests on a wooden table in warm evening light, then slowly opens and turns toward the camera, with quiet room ambience.' \
|
|
||||||
-F 'width=960' \
|
|
||||||
-F 'height=576' \
|
|
||||||
-F 'fps=24' \
|
|
||||||
-F 'num_inference_steps=50' \
|
|
||||||
-F 'flow_shift=12' \
|
|
||||||
-F 'seed=1101' \
|
|
||||||
-F 'extra_params={"task":"ref2va","duration":8.0,"audio_flow_shift":3.0}' \
|
|
||||||
-o h3-gb10-ref2va.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
The response is `200 OK` with an MP4 body: H.264 at the requested geometry plus a
|
|
||||||
32 kHz stereo AAC track. `ffprobe` reports 8.032 s for `duration=8.0`.
|
|
||||||
|
|
||||||
## Measured latency
|
## Measured latency
|
||||||
|
|
||||||
Single GB10, FP8, eager, cuDNN attention, tiled VAE, one request at a time:
|
Single GB10, FP8, eager, cuDNN attention, tiled VAE, one request at a time:
|
||||||
@@ -172,69 +102,32 @@ Single GB10, FP8, eager, cuDNN attention, tiled VAE, one request at a time:
|
|||||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||||
| T2VA, 4 s, smoke shape | 10 | — | — | 8.68 s | — | 0.49 s | 95.9 s | — |
|
| T2VA, 4 s, smoke shape | 10 | — | — | 8.68 s | — | 0.49 s | 95.9 s | — |
|
||||||
| T2VA, 8 s, 960x576 | 50 | 0.25 s | 2088 s | 42.61 s | 70 s | 4.84 s | 2169.4 s (36 min 9 s) | 97.7 GiB |
|
| T2VA, 8 s, 960x576 | 50 | 0.25 s | 2088 s | 42.61 s | 70 s | 4.84 s | 2169.4 s (36 min 9 s) | 97.7 GiB |
|
||||||
| Ref2VA, 8 s, 960x576, one image (3 runs) | 10 | 8.43-10.94 s | 665.5-748.8 s | 66.55-74.88 s | 67.9-69.8 s | 3.63 s | 770.3-861.1 s | 102.8 GiB |
|
|
||||||
| Ref2VA, 8 s, 960x576, one image | 50 | 9.66 s | 4039.6 s | 80.79 s | 69.4 s | 3.63 s | 4157.0 s (69 min 17 s) | 102.8 GiB |
|
|
||||||
|
|
||||||
How these were measured:
|
How these were measured:
|
||||||
|
|
||||||
- Stage times come from `--enable-diffusion-pipeline-profiler`, read from the
|
- Stage times come from `--enable-diffusion-pipeline-profiler`, read from the
|
||||||
`X-Stage-Durations` response header of `/v1/videos/sync`. The keys are
|
`X-Stage-Durations` response header of `/v1/videos/sync`.
|
||||||
`MiniMaxH3Pipeline.encode_prompt` (text encode),
|
|
||||||
`MiniMaxH3Pipeline.diffuse` (denoise), and `MiniMaxH3Pipeline.decode`
|
|
||||||
(VAE decode).
|
|
||||||
- MP4 mux is not a header field. It is the
|
|
||||||
`Video response encoding (MP4 bytes)` line in the server log.
|
|
||||||
- Peak memory is the `X-Peak-Memory-MB` header, which reports the CUDA allocator
|
- Peak memory is the `X-Peak-Memory-MB` header, which reports the CUDA allocator
|
||||||
high-water mark (`torch.cuda.max_memory_reserved`) for the request. It excludes
|
high-water mark (`torch.cuda.max_memory_reserved`) for the request. It excludes
|
||||||
the CUDA context and non-PyTorch allocations, so true pool usage is somewhat
|
the CUDA context and non-PyTorch allocations, so true pool usage is somewhat
|
||||||
higher. `nvidia-smi --query-gpu=memory.used` is not usable on GB10 — unified
|
higher. `nvidia-smi --query-gpu=memory.used` is not usable on GB10 — unified
|
||||||
memory has no discrete VRAM to report and the field returns `[N/A]`. See the
|
memory has no discrete VRAM to report and the field returns `[N/A]`.
|
||||||
capacity section for a `/proc/meminfo` cross-check.
|
- Text encode and VAE decode were measured on a separate 10-step run at the same
|
||||||
- For T2VA, text encode and VAE decode were measured on a separate 10-step run at
|
960x576 / 8 s geometry. Both are independent of step count, so they transfer to
|
||||||
the same 960x576 / 8 s geometry. Both are independent of step count, so they
|
the 50-step row unchanged.
|
||||||
transfer to the 50-step row unchanged. The Ref2VA MP4 mux figure transfers the
|
|
||||||
same way, from the 10-step run at identical output geometry and duration.
|
|
||||||
- Ref2VA peak memory is byte-identical between the 10-step and 50-step runs
|
|
||||||
(`X-Peak-Memory-MB: 105318.000` in both), confirming that peak is set by output
|
|
||||||
geometry and reference input rather than step count.
|
|
||||||
- Stage times do not sum exactly to end-to-end. Queueing, result transfer, and
|
- Stage times do not sum exactly to end-to-end. Queueing, result transfer, and
|
||||||
engine bookkeeping sit outside the profiled stages.
|
engine bookkeeping sit outside the profiled stages.
|
||||||
|
|
||||||
Denoise dominates at 96% of end-to-end, so step count and geometry are the only
|
Denoise dominates at 96% of end-to-end, so step count and geometry are the only
|
||||||
knobs that materially change the wall clock on this platform.
|
knobs that materially change the wall clock on this platform.
|
||||||
|
|
||||||
Ref2VA costs 1.92x the wall clock of T2VA at the same geometry and step count:
|
Repeated identical 10-step requests varied by up to 23% in denoise time
|
||||||
4157.0 s versus 2169.4 s. Text encoding shows the most dramatic ratio — 9.66 s
|
|
||||||
versus 0.25 s, 39x — but at 0.2% of end-to-end it is not what makes Ref2VA slow.
|
|
||||||
The cost is in denoise. A single reference image expands the Qwen3-VL
|
|
||||||
presentation to 6883 tokens (logged as `MiniMax H3 ref2va Qwen presentation:
|
|
||||||
6883 tokens, 1 reference images`), and those tokens sit in the attention context
|
|
||||||
of every denoise step. Per-step time rises from 42.61 s to 80.79 s, and that
|
|
||||||
1.90x multiplies across all 50 steps. VAE decode is unchanged at ~69 s, as
|
|
||||||
expected for an identical output shape.
|
|
||||||
|
|
||||||
Repeated identical 10-step T2VA requests varied by up to 23% in denoise time
|
|
||||||
(397 s to 490 s) on a machine that had already been under sustained load. Treat
|
(397 s to 490 s) on a machine that had already been under sustained load. Treat
|
||||||
these as single-run measurements on one machine, not a throughput guarantee, and
|
these as single-run measurements on one machine, not a throughput guarantee, and
|
||||||
expect the slower end of that range for back-to-back jobs.
|
expect the slower end of that range for back-to-back jobs.
|
||||||
|
|
||||||
Ref2VA per-step time is load-dependent in the same way. On an idle machine the
|
|
||||||
first 10-step request ran at 66.55 s per step. The 50-step request that followed
|
|
||||||
occupied the box for 69 consecutive minutes and averaged 80.79 s per step. Two
|
|
||||||
further 10-step requests taken immediately afterwards settled at 74.88 s and
|
|
||||||
74.31 s — 12.5% slower than the idle run, and within 0.8% of each other, so this
|
|
||||||
is a stable thermal state rather than measurement noise.
|
|
||||||
|
|
||||||
Plan for 81 s per step when sizing a full 50-step Ref2VA job; 66 s per step is
|
|
||||||
only reachable on a cold machine and does not hold for the length of one request.
|
|
||||||
|
|
||||||
Ref2VA text encode converged downward across the four runs (10.94 s, 9.66 s,
|
|
||||||
8.52 s, 8.43 s) as first-touch costs fell away; treat ~8.5 s as steady state.
|
|
||||||
VAE decode was stable at 67.9-69.8 s throughout.
|
|
||||||
|
|
||||||
Set `VLLM_OMNI_VIDEO_SYNC_TIMEOUT` well above the expected request time — the
|
Set `VLLM_OMNI_VIDEO_SYNC_TIMEOUT` well above the expected request time — the
|
||||||
default 1800 s is shorter than a 50-step run on this platform, and 7200 s is
|
default 1800 s is shorter than a 50-step run on this platform.
|
||||||
shorter than a 50-step Ref2VA run.
|
|
||||||
|
|
||||||
## Known limitations
|
## Known limitations
|
||||||
|
|
||||||
@@ -246,17 +139,11 @@ shorter than a 50-step Ref2VA run.
|
|||||||
- `t2va` rejects requests without an explicit `aspect_ratio`. When `width` and
|
- `t2va` rejects requests without an explicit `aspect_ratio`. When `width` and
|
||||||
`height` are both supplied they take precedence and `aspect_ratio` only has to
|
`height` are both supplied they take precedence and `aspect_ratio` only has to
|
||||||
name one of the supported ratios.
|
name one of the supported ratios.
|
||||||
- Sustained load degrades throughput. See the variance notes above.
|
- Sustained load degrades throughput. See the variance note above.
|
||||||
- Only one partition is resident. For Ref2VA, stop the FL2VA server and restart
|
- Only one partition is resident. For Ref2VA, stop the FL2VA server and restart
|
||||||
with `/path/to/MiniMax-H3/Ref2VA`. Ref2VA with one reference image is measured
|
the same command with `/path/to/MiniMax-H3/Ref2VA`. Ref2VA reference video
|
||||||
above; reference videos and additional images push the token count higher and
|
count and prompt length increase activation memory; begin with one request at
|
||||||
were not measured. With `MemAvailable` bottoming out at 7.4 GiB during a
|
a time and keep the short edge at 576.
|
||||||
single-image request, there is very little room to grow — re-measure before
|
|
||||||
adding references or raising the output shape.
|
|
||||||
- Ref2VA requires a vLLM-Omni build newer than `v0.26.0`. See the version note
|
|
||||||
under **Validated on**: FP8 weight loading and image-only Ref2VA are both
|
|
||||||
broken on `release/v0.26.0`.suggestion is `v0.26.1`.
|
|
||||||
- Online FP8 is incompatible with layerwise offload — the offload path produces a
|
- Online FP8 is incompatible with layerwise offload — the offload path produces a
|
||||||
weight stride the Cutlass FP8 kernel rejects. This is not a practical
|
weight stride the Cutlass FP8 kernel rejects. This is not a practical
|
||||||
restriction here since offload is unusable on GB10 anyway.
|
restriction here since offload is unusable on GB10 anyway.
|
||||||
```
|
|
||||||
@@ -69,7 +69,6 @@ def test_h3_fused_rope_matches_reference_and_preserves_unrotated_dims():
|
|||||||
|
|
||||||
attention = object.__new__(MiniMaxH3Attention)
|
attention = object.__new__(MiniMaxH3Attention)
|
||||||
nn.Module.__init__(attention)
|
nn.Module.__init__(attention)
|
||||||
attention.rot_dim = 96
|
|
||||||
attention.rope = RotaryEmbedding(is_neox_style=True, half_head_dim=False)
|
attention.rope = RotaryEmbedding(is_neox_style=True, half_head_dim=False)
|
||||||
attention.rope._forward_method = attention.rope.forward_native
|
attention.rope._forward_method = attention.rope.forward_native
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from pathlib import Path
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
@@ -46,9 +47,111 @@ def test_model_display_names_require_string_mapping(tmp_path, monkeypatch):
|
|||||||
generate_examples.load_model_display_names()
|
generate_examples.load_model_display_names()
|
||||||
|
|
||||||
|
|
||||||
def test_only_general_serving_examples_get_generated_pages():
|
def test_quantization_examples_keep_source_urls_under_feature_navigation(tmp_path, monkeypatch):
|
||||||
general_example = SimpleNamespace(category="offline_inference", path=Path("text_to_image"))
|
nav_file = tmp_path / "docs" / ".nav.yml"
|
||||||
model_example = SimpleNamespace(category="offline_inference", path=Path("qwen3_omni"))
|
nav_file.parent.mkdir(parents=True)
|
||||||
|
nav_file.write_text(
|
||||||
|
yaml.safe_dump(
|
||||||
|
{
|
||||||
|
"nav": [
|
||||||
|
{
|
||||||
|
"User Guide": [
|
||||||
|
{
|
||||||
|
"Examples": [
|
||||||
|
"examples/README.md",
|
||||||
|
{"Offline Inference": [{"Old": "user_guide/examples/offline_inference/old.md"}]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{"Features": [{"Quantization": [{"Overview": "user_guide/quantization/overview.md"}]}]},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort_keys=False,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
assert generate_examples.is_general_example(general_example)
|
monkeypatch.setattr(generate_examples, "ROOT_DIR", tmp_path)
|
||||||
assert not generate_examples.is_general_example(model_example)
|
monkeypatch.setattr(generate_examples, "NAV_FILE", nav_file)
|
||||||
|
monkeypatch.setattr(generate_examples, "EXAMPLE_DOC_DIR", tmp_path / "docs/user_guide/examples")
|
||||||
|
|
||||||
|
quantization_example = SimpleNamespace(
|
||||||
|
category="quantization",
|
||||||
|
path=tmp_path / "examples/quantization/check_modelopt_fp8_export.py",
|
||||||
|
nav_title="Check Modelopt FP8 Export",
|
||||||
|
)
|
||||||
|
offline_example = SimpleNamespace(
|
||||||
|
category="offline_inference",
|
||||||
|
path=tmp_path / "examples/offline_inference/demo.py",
|
||||||
|
nav_title="Demo",
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_examples.update_nav_file([quantization_example, offline_example])
|
||||||
|
generate_examples.update_nav_file([quantization_example, offline_example])
|
||||||
|
|
||||||
|
nav_data = yaml.safe_load(nav_file.read_text(encoding="utf-8"))
|
||||||
|
user_guide = nav_data["nav"][0]["User Guide"]
|
||||||
|
examples = next(item["Examples"] for item in user_guide if "Examples" in item)
|
||||||
|
features = next(item["Features"] for item in user_guide if "Features" in item)
|
||||||
|
quantization = next(item["Quantization"] for item in features if "Quantization" in item)
|
||||||
|
|
||||||
|
assert not any(isinstance(item, dict) and "Quantization" in item for item in examples)
|
||||||
|
generated_examples = [
|
||||||
|
item[generate_examples.GENERATED_QUANTIZATION_EXAMPLES_TITLE]
|
||||||
|
for item in quantization
|
||||||
|
if generate_examples.GENERATED_QUANTIZATION_EXAMPLES_TITLE in item
|
||||||
|
]
|
||||||
|
assert len(generated_examples) == 1
|
||||||
|
assert generated_examples[0] == [
|
||||||
|
{"Check Modelopt FP8 Export": "user_guide/examples/quantization/check_modelopt_fp8_export.md"}
|
||||||
|
]
|
||||||
|
|
||||||
|
# A later build without quantization examples must not leave a stale
|
||||||
|
# generated group in the hand-authored Quantization section.
|
||||||
|
generate_examples.update_nav_file([offline_example])
|
||||||
|
nav_data = yaml.safe_load(nav_file.read_text(encoding="utf-8"))
|
||||||
|
user_guide = nav_data["nav"][0]["User Guide"]
|
||||||
|
features = next(item["Features"] for item in user_guide if "Features" in item)
|
||||||
|
quantization = next(item["Quantization"] for item in features if "Quantization" in item)
|
||||||
|
assert not any(
|
||||||
|
isinstance(item, dict) and generate_examples.GENERATED_QUANTIZATION_EXAMPLES_TITLE in item
|
||||||
|
for item in quantization
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_quantization_examples_are_not_added_to_examples_without_feature_target(tmp_path, monkeypatch, caplog):
|
||||||
|
nav_file = tmp_path / "docs" / ".nav.yml"
|
||||||
|
nav_file.parent.mkdir(parents=True)
|
||||||
|
nav_file.write_text(
|
||||||
|
yaml.safe_dump(
|
||||||
|
{
|
||||||
|
"nav": [
|
||||||
|
{
|
||||||
|
"User Guide": [
|
||||||
|
{"Examples": ["examples/README.md"]},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort_keys=False,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(generate_examples, "ROOT_DIR", tmp_path)
|
||||||
|
monkeypatch.setattr(generate_examples, "NAV_FILE", nav_file)
|
||||||
|
monkeypatch.setattr(generate_examples, "EXAMPLE_DOC_DIR", tmp_path / "docs/user_guide/examples")
|
||||||
|
|
||||||
|
quantization_example = SimpleNamespace(
|
||||||
|
category="quantization",
|
||||||
|
path=tmp_path / "examples/quantization/check_modelopt_fp8_export.py",
|
||||||
|
nav_title="Check Modelopt FP8 Export",
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_examples.update_nav_file([quantization_example])
|
||||||
|
|
||||||
|
nav_data = yaml.safe_load(nav_file.read_text(encoding="utf-8"))
|
||||||
|
examples = nav_data["nav"][0]["User Guide"][0]["Examples"]
|
||||||
|
assert not any(isinstance(item, dict) and "Quantization" in item for item in examples)
|
||||||
|
assert "omitting generated quantization examples from navigation" in caplog.text
|
||||||
@@ -68,7 +68,6 @@ class RMSNorm(CustomOp):
|
|||||||
def __init__(self, hidden_size: int, eps: float = 1e-6, dtype: torch.dtype = torch.float32) -> None:
|
def __init__(self, hidden_size: int, eps: float = 1e-6, dtype: torch.dtype = torch.float32) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.weight = nn.Parameter(torch.ones(hidden_size, dtype=dtype))
|
self.weight = nn.Parameter(torch.ones(hidden_size, dtype=dtype))
|
||||||
self.hidden_size = hidden_size
|
|
||||||
self.variance_epsilon = eps
|
self.variance_epsilon = eps
|
||||||
|
|
||||||
def _forward_fused(self, x: torch.Tensor) -> torch.Tensor:
|
def _forward_fused(self, x: torch.Tensor) -> torch.Tensor:
|
||||||
@@ -108,19 +107,6 @@ class RMSNorm(CustomOp):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return self.forward_native(x)
|
return self.forward_native(x)
|
||||||
|
|
||||||
def forward_musa(
|
|
||||||
self,
|
|
||||||
x: torch.Tensor,
|
|
||||||
) -> torch.Tensor:
|
|
||||||
# Preserve the aten::rms_norm graph so dynamic Inductor can fuse the
|
|
||||||
# H3 Q/K norm with its inline RoPE path on MUSA.
|
|
||||||
return F.rms_norm(
|
|
||||||
x,
|
|
||||||
(self.hidden_size,),
|
|
||||||
self.weight,
|
|
||||||
self.variance_epsilon,
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward_npu(
|
def forward_npu(
|
||||||
self,
|
self,
|
||||||
x: torch.Tensor,
|
x: torch.Tensor,
|
||||||
|
|||||||
@@ -193,16 +193,7 @@ class RotaryEmbedding(CustomOp):
|
|||||||
cos: torch.Tensor,
|
cos: torch.Tensor,
|
||||||
sin: torch.Tensor,
|
sin: torch.Tensor,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
# Keep the full-dimension NeoX form inline for MUSA compilation.
|
|
||||||
# Other layouts use the shared native implementation.
|
|
||||||
if self.half_head_dim or self.interleaved:
|
|
||||||
return self.forward_native(x, cos, sin)
|
return self.forward_native(x, cos, sin)
|
||||||
if cos.dim() == 3:
|
|
||||||
cos, sin = cos[0], sin[0]
|
|
||||||
cos = cos.unsqueeze(-2)
|
|
||||||
sin = sin.unsqueeze(-2)
|
|
||||||
x1, x2 = x.chunk(2, dim=-1)
|
|
||||||
return x * cos + torch.cat((-x2, x1), dim=-1) * sin
|
|
||||||
|
|
||||||
def forward_native(
|
def forward_native(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -333,7 +333,6 @@ class MiniMaxH3Attention(nn.Module):
|
|||||||
)
|
)
|
||||||
self.num_heads = self.qkv_proj.num_heads
|
self.num_heads = self.qkv_proj.num_heads
|
||||||
self.num_kv_heads = self.qkv_proj.num_kv_heads
|
self.num_kv_heads = self.qkv_proj.num_kv_heads
|
||||||
self.rot_dim = 6 * arch.rope_inv_freq_len
|
|
||||||
self.q_norm = _norm(arch.attention_head_dim, eps=arch.qk_norm_eps)
|
self.q_norm = _norm(arch.attention_head_dim, eps=arch.qk_norm_eps)
|
||||||
self.k_norm = _norm(arch.attention_head_dim, eps=arch.qk_norm_eps)
|
self.k_norm = _norm(arch.attention_head_dim, eps=arch.qk_norm_eps)
|
||||||
self.rope = RotaryEmbedding(is_neox_style=True, half_head_dim=False)
|
self.rope = RotaryEmbedding(is_neox_style=True, half_head_dim=False)
|
||||||
@@ -366,7 +365,7 @@ class MiniMaxH3Attention(nn.Module):
|
|||||||
x: [T, heads, head_dim]; freqs: [T, rot_dim]. In the unfused path, cos/sin
|
x: [T, heads, head_dim]; freqs: [T, rot_dim]. In the unfused path, cos/sin
|
||||||
are cast to the activation dtype before the elementwise math.
|
are cast to the activation dtype before the elementwise math.
|
||||||
"""
|
"""
|
||||||
rot_dim = self.rot_dim
|
rot_dim = freqs.shape[-1]
|
||||||
x_rot, x_pass = x[..., :rot_dim], x[..., rot_dim:]
|
x_rot, x_pass = x[..., :rot_dim], x[..., rot_dim:]
|
||||||
cos = torch.cos(freqs).to(x.dtype) # [T, rot_dim]
|
cos = torch.cos(freqs).to(x.dtype) # [T, rot_dim]
|
||||||
sin = torch.sin(freqs).to(x.dtype)
|
sin = torch.sin(freqs).to(x.dtype)
|
||||||
|
|||||||
Reference in new issue
Block a user