[diffusion] Restrict request-level quality to two validated tiers: lossless (default) and high (#33453)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
614825fd38
commit
c6f2a9c1d4
@@ -410,12 +410,19 @@ for variant in 0 1; do
|
|||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
### Choose a quality profile
|
### Choose the quality level
|
||||||
|
|
||||||
`quality` is a request-scoped sampling parameter. One resident server can
|
`quality` is a request-scoped sampling parameter with two validated levels:
|
||||||
switch between all four profiles; an approximate request mounts its audited
|
|
||||||
Cache-DiT policy at the batch boundary, and a later `lossless` request removes
|
- `"lossless"` (default): the exact reference path. Output is bit-exact
|
||||||
the hooks before denoising.
|
against the reference implementation and the CI ground truth.
|
||||||
|
- `"high"`: the audited accelerated path. Quality is guaranteed (the audited
|
||||||
|
Cache-DiT configuration measures SSIM 0.931 / PSNR 28.16 dB against
|
||||||
|
`lossless`), but output is no longer bit-identical to the reference.
|
||||||
|
|
||||||
|
One resident server serves both levels; a `quality: "high"` request mounts
|
||||||
|
its audited Cache-DiT policy at the batch boundary, and a later
|
||||||
|
`quality: "lossless"` request removes the hooks before denoising.
|
||||||
|
|
||||||
Start the validated server once:
|
Start the validated server once:
|
||||||
|
|
||||||
@@ -434,13 +441,14 @@ sglang serve \
|
|||||||
--port 30010
|
--port 30010
|
||||||
```
|
```
|
||||||
|
|
||||||
Then choose a request tag:
|
Then choose a request level:
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
|
|
||||||
<Tab title="lossless">
|
<Tab title="lossless (default)">
|
||||||
|
|
||||||
Native denoising with no feature-cache approximation. This is the default.
|
Native denoising with no feature-cache approximation. This is the default;
|
||||||
|
omitting the field is equivalent.
|
||||||
|
|
||||||
```json Request field
|
```json Request field
|
||||||
{
|
{
|
||||||
@@ -452,8 +460,8 @@ Native denoising with no feature-cache approximation. This is the default.
|
|||||||
|
|
||||||
<Tab title="high">
|
<Tab title="high">
|
||||||
|
|
||||||
The least aggressive approximate profile. Use it when output should stay
|
The audited accelerated path. Use it when you can trade bit-exactness for
|
||||||
closest to the same-seed lossless trajectory.
|
latency while keeping output closest to the same-seed lossless trajectory.
|
||||||
|
|
||||||
```json Request field
|
```json Request field
|
||||||
{
|
{
|
||||||
@@ -463,32 +471,6 @@ closest to the same-seed lossless trajectory.
|
|||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab title="medium">
|
|
||||||
|
|
||||||
The balanced profile: substantially lower latency with a larger change from
|
|
||||||
the same-seed lossless output.
|
|
||||||
|
|
||||||
```json Request field
|
|
||||||
{
|
|
||||||
"quality": "medium"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab title="low">
|
|
||||||
|
|
||||||
The fastest validated profile and the largest visual deviation. Use it for
|
|
||||||
latency-sensitive previews and high-throughput generation.
|
|
||||||
|
|
||||||
```json Request field
|
|
||||||
{
|
|
||||||
"quality": "low"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
The measured trade-off is:
|
The measured trade-off is:
|
||||||
@@ -497,8 +479,6 @@ The measured trade-off is:
|
|||||||
| --- | ---: | ---: | ---: | ---: | --- |
|
| --- | ---: | ---: | ---: | ---: | --- |
|
||||||
| `lossless` | 75.10 s | 1.00× | 1.000 | exact | Native reference path |
|
| `lossless` | 75.10 s | 1.00× | 1.000 | exact | Native reference path |
|
||||||
| `high` | 53.70 s | 1.40× | 0.931 | 28.16 dB | Smallest same-seed visual change |
|
| `high` | 53.70 s | 1.40× | 0.931 | 28.16 dB | Smallest same-seed visual change |
|
||||||
| `medium` | 30.23 s | 2.48× | 0.818 | 20.40 dB | Balanced latency and visual deviation |
|
|
||||||
| `low` | 25.81 s | 2.91× | 0.794 | 19.25 dB | Largest deviation; fastest preview path |
|
|
||||||
|
|
||||||
These numbers use 1344×768, 124-frame, 24 fps T2VA with 50 inference steps,
|
These numbers use 1344×768, 124-frame, 24 fps T2VA with 50 inference steps,
|
||||||
video flow shift 12, audio flow shift 3, and three fixed prompt/seed pairs on
|
video flow shift 12, audio flow shift 3, and three fixed prompt/seed pairs on
|
||||||
@@ -506,26 +486,26 @@ video flow shift 12, audio flow shift 3, and three fixed prompt/seed pairs on
|
|||||||
and a moving close-up portrait. `inference_time_s` is averaged across the three
|
and a moving close-up portrait. `inference_time_s` is averaged across the three
|
||||||
prompts; the quiet-scene point is itself the mean of two repeats.
|
prompts; the quiet-scene point is itself the mean of two repeats.
|
||||||
|
|
||||||
SSIM and PSNR compare decoded, frame-aligned output with the `lossless` result
|
SSIM and PSNR compare decoded, frame-aligned output with the `lossless`
|
||||||
for the same prompt and seed. They measure trajectory deviation, not absolute
|
result for the same prompt and seed. They measure trajectory deviation, not
|
||||||
perceptual quality: an approximate profile can produce a different but still
|
absolute perceptual quality: the `high` path can produce a different but
|
||||||
plausible realization. The profiles also change the joint audio-video denoise
|
still plausible realization. It also changes the joint audio-video denoise
|
||||||
trajectory, while these two metrics cover video only.
|
trajectory, while these two metrics cover video only.
|
||||||
|
|
||||||
Approximate profiles currently accept only the exact workload and 4×H200
|
`quality: "high"` currently accepts only the exact workload and 4×H200
|
||||||
deployment above; other hardware, task modes, request shapes, step counts, or
|
deployment above; other hardware, task modes, request shapes, step counts, or
|
||||||
flow shifts fail before denoising. Offline generation uses the same profile
|
flow shifts fail before denoising. Offline generation uses the same level
|
||||||
name, for example `sglang generate --quality medium`.
|
name, for example `sglang generate --quality high`.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
`quality` selects a model sampling profile and can change generated content.
|
`quality` selects a model sampling level and can change generated content.
|
||||||
`output_quality` controls only output-file compression; it is a separate field.
|
`output_quality` controls only output-file compression; it is a separate field.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
For manually tuned Cache-DiT experiments outside that validated profile, omit
|
For manually tuned Cache-DiT experiments outside that validated path, omit
|
||||||
the request `quality` field and set the process-wide environment controls
|
the request `quality` field and set the process-wide environment controls
|
||||||
directly. An explicit `quality: lossless` request overrides those controls and
|
directly. An explicit `quality: "lossless"` request overrides those controls
|
||||||
restores native denoising:
|
and restores native denoising:
|
||||||
|
|
||||||
```bash Command
|
```bash Command
|
||||||
SGLANG_CACHE_DIT_ENABLED=true \
|
SGLANG_CACHE_DIT_ENABLED=true \
|
||||||
@@ -548,8 +528,8 @@ Cache-DiT skips selected block computation and is approximate. It cannot be
|
|||||||
combined with FSDP inference or DiT layerwise offload. Breakable CUDA graph
|
combined with FSDP inference or DiT layerwise offload. Breakable CUDA graph
|
||||||
execution takes precedence and leaves Cache-DiT disabled. Tune the cache
|
execution takes precedence and leaves Cache-DiT disabled. Tune the cache
|
||||||
thresholds only after comparing both video and audio quality on the target
|
thresholds only after comparing both video and audio quality on the target
|
||||||
task profile. A real B200 request has completed, but the named profiles above
|
task profile. A real B200 request has completed, but the `quality: "high"`
|
||||||
remain fail-closed to the audited 4×H200 workload.
|
path above remains fail-closed to the audited 4×H200 workload.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
## 6. Runtime feature recipes
|
## 6. Runtime feature recipes
|
||||||
@@ -646,7 +626,7 @@ real H3 validation runs.
|
|||||||
|
|
||||||
- MiniMax-H3 produces the canonical 24 fps output; request duration is expressed through `target.duration_seconds`.
|
- MiniMax-H3 produces the canonical 24 fps output; request duration is expressed through `target.duration_seconds`.
|
||||||
- `target.duration_seconds` must be between 4 and 15 seconds, inclusive. The command picker defaults to the verified 5-second profile.
|
- `target.duration_seconds` must be between 4 and 15 seconds, inclusive. The command picker defaults to the verified 5-second profile.
|
||||||
- Use a 768-pixel short edge for the released quality profile. The aligned output dimensions are derived from `target.aspect_ratio`.
|
- Use a 768-pixel short edge for the released quality recipe. The aligned output dimensions are derived from `target.aspect_ratio`.
|
||||||
- `flow_shift` controls video diffusion and `audio_flow_shift` controls audio diffusion.
|
- `flow_shift` controls video diffusion and `audio_flow_shift` controls audio diffusion.
|
||||||
- V2V uses `task: "ref2va"` with a `video` or `video_audio` reference; it is served by the `Ref2VA` partition and is not a separate public task value.
|
- V2V uses `task: "ref2va"` with a `video` or `video_audio` reference; it is served by the `Ref2VA` partition and is not a separate public task value.
|
||||||
- `conditions[].start_time_seconds` selects a non-negative offset for a video reference. Its visual and audio streams are always sought together.
|
- `conditions[].start_time_seconds` selects a non-negative offset for a video reference. Its visual and audio streams are always sought together.
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ Use `sglang generate --help` and `sglang serve --help` for the full argument lis
|
|||||||
- `--image-path {PATH} [{PATH} ...]`: input image(s) for image-to-video or image-to-image generation
|
- `--image-path {PATH} [{PATH} ...]`: input image(s) for image-to-video or image-to-image generation
|
||||||
- `--num-inference-steps {STEPS}` and `--seed {SEED}`
|
- `--num-inference-steps {STEPS}` and `--seed {SEED}`
|
||||||
- `--num-outputs-per-prompt {N}` / `--num-outputs {N}`: generate multiple outputs for each prompt. A scalar seed expands as `seed + output_index`.
|
- `--num-outputs-per-prompt {N}` / `--num-outputs {N}`: generate multiple outputs for each prompt. A scalar seed expands as `seed + output_index`.
|
||||||
- `--quality {PROFILE}`: select a model-owned request quality/performance profile. Supported names and deployment constraints are model-specific.
|
- `--quality {lossless,high}`: request-level quality. `lossless` (default) keeps the exact reference path, bit-exact against the reference implementation; `high` opts into the model-owned validated accelerated path, whose quality stays guaranteed but is not bit-exact. Support and validated deployment constraints are model-specific.
|
||||||
- `--height {HEIGHT}`, `--width {WIDTH}`, `--num-frames {N}`, `--fps {FPS}`
|
- `--height {HEIGHT}`, `--width {WIDTH}`, `--num-frames {N}`, `--fps {FPS}`
|
||||||
- `--output-path {PATH}`, `--output-file-name {NAME}`, `--save-output`, `--return-frames`
|
- `--output-path {PATH}`, `--output-file-name {NAME}`, `--save-output`, `--return-frames`
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ class MiniMaxH3PipelineConfig(PipelineConfig):
|
|||||||
return getattr(value, "value", value)
|
return getattr(value, "value", value)
|
||||||
|
|
||||||
def validate_quality_deployment(self, server_args) -> None:
|
def validate_quality_deployment(self, server_args) -> None:
|
||||||
"""Fail closed unless the resident server matches the measured profile."""
|
"""Fail closed unless the resident server matches the deployment
|
||||||
|
audited for quality="high"."""
|
||||||
|
|
||||||
attention_backend = self._server_arg_value(server_args.attention_backend)
|
attention_backend = self._server_arg_value(server_args.attention_backend)
|
||||||
attention_backend = (
|
attention_backend = (
|
||||||
@@ -161,7 +162,7 @@ class MiniMaxH3PipelineConfig(PipelineConfig):
|
|||||||
}
|
}
|
||||||
if mismatches:
|
if mismatches:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"MiniMax-H3 approximate quality profiles are validated only for "
|
'MiniMax-H3 quality="high" is validated only for '
|
||||||
f"the strict 4xH200 fl2va deployment; mismatches: {mismatches}"
|
f"the strict 4xH200 fl2va deployment; mismatches: {mismatches}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ def generate_request_id() -> str:
|
|||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
# Validated request-level quality levels. "lossless" is the exact reference
|
||||||
|
# path (bit-exact against the CI golden outputs); "high" opts into validated
|
||||||
|
# accelerated paths whose quality is guaranteed but not bit-exact.
|
||||||
|
QUALITY_LEVELS: tuple[str, ...] = ("lossless", "high")
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_filename(name: str, replacement: str = "_", max_length: int = 150) -> str:
|
def _sanitize_filename(name: str, replacement: str = "_", max_length: int = 150) -> str:
|
||||||
"""Create a filesystem- and ffmpeg-friendly filename.
|
"""Create a filesystem- and ffmpeg-friendly filename.
|
||||||
|
|
||||||
@@ -123,9 +129,20 @@ class SamplingParams:
|
|||||||
)
|
)
|
||||||
output_quality: str | None = "default"
|
output_quality: str | None = "default"
|
||||||
output_compression: int | None = None
|
output_compression: int | None = None
|
||||||
# Model-owned, request-scoped approximate acceleration profile. Models
|
# Model-owned, request-scoped quality level.
|
||||||
# that support it must validate the deployment and workload explicitly.
|
#
|
||||||
# It intentionally participates in the dynamic-batch signature.
|
# - "lossless" (default): the exact reference path. Output is expected to
|
||||||
|
# be bit-identical to the HF reference implementation and to pass the
|
||||||
|
# CI golden/ground-truth comparisons.
|
||||||
|
# - "high": opt into validated accelerated paths. Quality stays
|
||||||
|
# guaranteed (the intent is to back every such path with mathematical
|
||||||
|
# acceptance thresholds, e.g. PSNR > 25 against the reference), but
|
||||||
|
# the output is no longer bit-exact versus the HF reference or the CI
|
||||||
|
# ground truth.
|
||||||
|
#
|
||||||
|
# Models that support "high" must validate the deployment and workload
|
||||||
|
# explicitly. It intentionally participates in the dynamic-batch
|
||||||
|
# signature.
|
||||||
quality: str = "lossless"
|
quality: str = "lossless"
|
||||||
|
|
||||||
# Frame interpolation
|
# Frame interpolation
|
||||||
@@ -408,9 +425,10 @@ class SamplingParams:
|
|||||||
f"prompt_path must be a txt file, got {self.prompt_path!r}"
|
f"prompt_path must be a txt file, got {self.prompt_path!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(self.quality, str) or not self.quality.strip():
|
if self.quality not in QUALITY_LEVELS:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"quality must be a non-empty string, got {self.quality!r}"
|
f"quality must be one of {list(QUALITY_LEVELS)}, "
|
||||||
|
f"got {self.quality!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# These are always required to be sane regardless of pipeline.
|
# These are always required to be sane regardless of pipeline.
|
||||||
@@ -932,9 +950,14 @@ class SamplingParams:
|
|||||||
add_argument(
|
add_argument(
|
||||||
"--quality",
|
"--quality",
|
||||||
type=str,
|
type=str,
|
||||||
|
choices=list(QUALITY_LEVELS),
|
||||||
help=(
|
help=(
|
||||||
"Select a model-owned quality/performance profile. "
|
"Request-level quality: 'lossless' (default) keeps the exact "
|
||||||
"Support and validated deployment constraints are model-specific."
|
"reference path, bit-exact against the reference "
|
||||||
|
"implementation; 'high' opts into the model-owned validated "
|
||||||
|
"accelerated path, whose quality stays guaranteed but is not "
|
||||||
|
"bit-exact. Support and validated deployment constraints are "
|
||||||
|
"model-specific."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
add_argument(
|
add_argument(
|
||||||
|
|||||||
+6
-7
@@ -27,11 +27,10 @@ MINIMAX_H3_MAX_DURATION_SECONDS = 15.0
|
|||||||
# The distilled checkpoint has exactly one positive denoise branch.
|
# The distilled checkpoint has exactly one positive denoise branch.
|
||||||
MINIMAX_H3_DEFAULT_BRANCHES: tuple = ({"name": "cond_1"},)
|
MINIMAX_H3_DEFAULT_BRANCHES: tuple = ({"name": "cond_1"},)
|
||||||
|
|
||||||
# Audited 4xH200 T2VA profiles. The tuple is
|
# Audited 4xH200 T2VA Cache-DiT parameters for quality="high":
|
||||||
# (warmup steps, residual-difference threshold, max consecutive cached steps).
|
# (warmup steps, residual-difference threshold, max consecutive cached steps).
|
||||||
MINIMAX_H3_QUALITY_PROFILES: dict[str, tuple[int, float, int] | None] = {
|
# Measured SSIM 0.931 / PSNR 28.16 dB against quality="lossless" on the
|
||||||
"lossless": None,
|
# validated workload. quality="lossless" (the default) uses no Cache-DiT
|
||||||
"high": (4, 0.04, 1),
|
# configuration at all. Process-wide SGLANG_CACHE_DIT_* environment controls
|
||||||
"medium": (4, 0.12, 3),
|
# remain available for manual experiments and are independent of this field.
|
||||||
"low": (4, 0.24, 3),
|
MINIMAX_H3_HIGH_QUALITY_CACHE_DIT_CONFIG: tuple[int, float, int] = (4, 0.04, 1)
|
||||||
}
|
|
||||||
|
|||||||
+7
-11
@@ -7,11 +7,9 @@ import math
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Mapping
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
from sglang.multimodal_gen.configs.sample.sampling_params import QUALITY_LEVELS
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.constants import (
|
|
||||||
MINIMAX_H3_QUALITY_PROFILES,
|
|
||||||
)
|
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.resolved_plan import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.resolved_plan import (
|
||||||
minimax_h3_plan_from_batch,
|
minimax_h3_plan_from_batch,
|
||||||
)
|
)
|
||||||
@@ -150,25 +148,23 @@ class MiniMaxH3PartitionAdmissionStage(PipelineStage):
|
|||||||
raise ValueError("MiniMax H3 request task must be a non-empty string")
|
raise ValueError("MiniMax H3 request task must be a non-empty string")
|
||||||
self.metadata.canonical_task(task)
|
self.metadata.canonical_task(task)
|
||||||
quality = getattr(batch.sampling_params, "quality", "lossless")
|
quality = getattr(batch.sampling_params, "quality", "lossless")
|
||||||
if quality not in MINIMAX_H3_QUALITY_PROFILES:
|
if quality not in QUALITY_LEVELS:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"unsupported MiniMax-H3 quality profile {quality!r}; supported: "
|
f"quality must be one of {list(QUALITY_LEVELS)}, got {quality!r}"
|
||||||
f"{list(MINIMAX_H3_QUALITY_PROFILES)}"
|
|
||||||
)
|
)
|
||||||
approximate = quality != "lossless"
|
high_quality = quality == "high"
|
||||||
attention_backend = str(server_args.attention_backend or "").strip().lower()
|
attention_backend = str(server_args.attention_backend or "").strip().lower()
|
||||||
if attention_backend == "sage_attn" and not batch.is_warmup:
|
if attention_backend == "sage_attn" and not batch.is_warmup:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"MiniMax-H3 does not support SageAttention: the current packed "
|
"MiniMax-H3 does not support SageAttention: the current packed "
|
||||||
"varlen path does not preserve model output"
|
"varlen path does not preserve model output"
|
||||||
)
|
)
|
||||||
if approximate and not batch.is_warmup:
|
if high_quality and not batch.is_warmup:
|
||||||
server_args.pipeline_config.validate_quality_deployment(server_args)
|
server_args.pipeline_config.validate_quality_deployment(server_args)
|
||||||
plan = minimax_h3_plan_from_batch(batch)
|
plan = minimax_h3_plan_from_batch(batch)
|
||||||
if plan is None:
|
if plan is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"MiniMax-H3 approximate quality profiles require a resolved "
|
'MiniMax-H3 quality="high" requires a resolved request plan'
|
||||||
"request plan"
|
|
||||||
)
|
)
|
||||||
shape = plan.shape
|
shape = plan.shape
|
||||||
actual = {
|
actual = {
|
||||||
@@ -212,7 +208,7 @@ class MiniMaxH3PartitionAdmissionStage(PipelineStage):
|
|||||||
)
|
)
|
||||||
if not exact or not shifts:
|
if not exact or not shifts:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"MiniMax-H3 approximate quality profiles are validated only for "
|
'MiniMax-H3 quality="high" is validated only for '
|
||||||
f"{_MINIMAX_H3_QUALITY_WORKLOAD}; got {actual}"
|
f"{_MINIMAX_H3_QUALITY_WORKLOAD}; got {actual}"
|
||||||
)
|
)
|
||||||
return batch
|
return batch
|
||||||
|
|||||||
+8
-14
@@ -24,7 +24,7 @@ from sglang.multimodal_gen.runtime.pipelines_core.stages.denoising import (
|
|||||||
DenoisingStage,
|
DenoisingStage,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.constants import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.constants import (
|
||||||
MINIMAX_H3_QUALITY_PROFILES,
|
MINIMAX_H3_HIGH_QUALITY_CACHE_DIT_CONFIG,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.task_profiles import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.task_profiles import (
|
||||||
MINIMAX_H3_FL2VA_KEYFRAME_SIGNATURES,
|
MINIMAX_H3_FL2VA_KEYFRAME_SIGNATURES,
|
||||||
@@ -387,7 +387,7 @@ class MiniMaxH3DenoisingStage(DenoisingStage):
|
|||||||
scheduler=None,
|
scheduler=None,
|
||||||
pipeline=pipeline,
|
pipeline=pipeline,
|
||||||
)
|
)
|
||||||
self._minimax_h3_quality_profile = "lossless"
|
self._minimax_h3_quality = "lossless"
|
||||||
self._minimax_h3_cache_mode: str | None = None
|
self._minimax_h3_cache_mode: str | None = None
|
||||||
|
|
||||||
def _owns_compile_warmup_lifecycle(self) -> bool:
|
def _owns_compile_warmup_lifecycle(self) -> bool:
|
||||||
@@ -395,7 +395,7 @@ class MiniMaxH3DenoisingStage(DenoisingStage):
|
|||||||
|
|
||||||
def _cache_dit_requested(self) -> bool:
|
def _cache_dit_requested(self) -> bool:
|
||||||
return (
|
return (
|
||||||
getattr(self, "_minimax_h3_quality_profile", "lossless") != "lossless"
|
getattr(self, "_minimax_h3_quality", "lossless") == "high"
|
||||||
or super()._cache_dit_requested()
|
or super()._cache_dit_requested()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -403,19 +403,15 @@ class MiniMaxH3DenoisingStage(DenoisingStage):
|
|||||||
self, num_inference_steps: int | tuple[int, int], batch: Req
|
self, num_inference_steps: int | tuple[int, int], batch: Req
|
||||||
) -> None:
|
) -> None:
|
||||||
quality = getattr(batch.sampling_params, "quality", "lossless")
|
quality = getattr(batch.sampling_params, "quality", "lossless")
|
||||||
if quality not in MINIMAX_H3_QUALITY_PROFILES:
|
|
||||||
raise ValueError(f"unsupported MiniMax-H3 quality profile {quality!r}")
|
|
||||||
explicit_fields = getattr(batch.sampling_params, "_explicit_fields", ())
|
explicit_fields = getattr(batch.sampling_params, "_explicit_fields", ())
|
||||||
generic_requested = (
|
generic_requested = (
|
||||||
super()._cache_dit_requested() and "quality" not in explicit_fields
|
super()._cache_dit_requested() and "quality" not in explicit_fields
|
||||||
)
|
)
|
||||||
desired_mode = (
|
desired_mode = (
|
||||||
quality
|
"high" if quality == "high" else ("generic" if generic_requested else None)
|
||||||
if quality != "lossless"
|
|
||||||
else ("generic" if generic_requested else None)
|
|
||||||
)
|
)
|
||||||
current_mode = getattr(self, "_minimax_h3_cache_mode", None)
|
current_mode = getattr(self, "_minimax_h3_cache_mode", None)
|
||||||
self._minimax_h3_quality_profile = quality
|
self._minimax_h3_quality = quality
|
||||||
|
|
||||||
# H3 is monolithic-only, and the scheduler executes one worker batch at
|
# H3 is monolithic-only, and the scheduler executes one worker batch at
|
||||||
# a time. Combined with `quality` in the dynamic-batch signature, this
|
# a time. Combined with `quality` in the dynamic-batch signature, this
|
||||||
@@ -435,7 +431,7 @@ class MiniMaxH3DenoisingStage(DenoisingStage):
|
|||||||
def _cache_dit_scm_masks(
|
def _cache_dit_scm_masks(
|
||||||
self, primary_num_steps: int, secondary_num_steps: int | None = None
|
self, primary_num_steps: int, secondary_num_steps: int | None = None
|
||||||
) -> tuple[str, str, list[int] | None, list[int] | None]:
|
) -> tuple[str, str, list[int] | None, list[int] | None]:
|
||||||
if getattr(self, "_minimax_h3_quality_profile", "lossless") != "lossless":
|
if getattr(self, "_minimax_h3_quality", "lossless") == "high":
|
||||||
return "none", "dynamic", None, None
|
return "none", "dynamic", None, None
|
||||||
return super()._cache_dit_scm_masks(primary_num_steps, secondary_num_steps)
|
return super()._cache_dit_scm_masks(primary_num_steps, secondary_num_steps)
|
||||||
|
|
||||||
@@ -447,16 +443,14 @@ class MiniMaxH3DenoisingStage(DenoisingStage):
|
|||||||
*,
|
*,
|
||||||
secondary: bool = False,
|
secondary: bool = False,
|
||||||
) -> CacheDitConfig:
|
) -> CacheDitConfig:
|
||||||
quality = getattr(self, "_minimax_h3_quality_profile", "lossless")
|
if secondary or getattr(self, "_minimax_h3_quality", "lossless") != "high":
|
||||||
profile = MINIMAX_H3_QUALITY_PROFILES[quality]
|
|
||||||
if profile is None or secondary:
|
|
||||||
return super()._build_cache_dit_config(
|
return super()._build_cache_dit_config(
|
||||||
num_inference_steps,
|
num_inference_steps,
|
||||||
steps_computation_mask,
|
steps_computation_mask,
|
||||||
scm_policy,
|
scm_policy,
|
||||||
secondary=secondary,
|
secondary=secondary,
|
||||||
)
|
)
|
||||||
warmup, threshold, max_cached = profile
|
warmup, threshold, max_cached = MINIMAX_H3_HIGH_QUALITY_CACHE_DIT_CONFIG
|
||||||
return CacheDitConfig(
|
return CacheDitConfig(
|
||||||
enabled=True,
|
enabled=True,
|
||||||
Fn_compute_blocks=1,
|
Fn_compute_blocks=1,
|
||||||
|
|||||||
+7
-4
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
from sglang.multimodal_gen.configs.pipeline_configs.minimax_h3 import (
|
from sglang.multimodal_gen.configs.pipeline_configs.minimax_h3 import (
|
||||||
MiniMaxH3PipelineConfig,
|
MiniMaxH3PipelineConfig,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.configs.sample.sampling_params import QUALITY_LEVELS
|
||||||
from sglang.multimodal_gen.runtime.entrypoints.openai.protocol import (
|
from sglang.multimodal_gen.runtime.entrypoints.openai.protocol import (
|
||||||
VideoGenerationsRequest,
|
VideoGenerationsRequest,
|
||||||
)
|
)
|
||||||
@@ -104,12 +105,14 @@ class MiniMaxH3VideoModelAdapter:
|
|||||||
request: VideoGenerationsRequest,
|
request: VideoGenerationsRequest,
|
||||||
name: str,
|
name: str,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
value = _parse_extra_value(_extra_value(request, name))
|
value = _extra_value(request, name)
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
if not isinstance(value, str) or not value.strip():
|
if value not in QUALITY_LEVELS:
|
||||||
raise ValueError(f"{name} must be a non-empty string")
|
raise ValueError(
|
||||||
return value.strip().lower()
|
f"{name} must be one of {list(QUALITY_LEVELS)}, got {value!r}"
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _reject_retired_cfg_fields(kwargs: dict[str, Any]) -> None:
|
def _reject_retired_cfg_fields(kwargs: dict[str, Any]) -> None:
|
||||||
|
|||||||
@@ -208,6 +208,21 @@ def test_video_adapter_lowers_only_native_fields_and_rejects_cfg():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bad_quality", ["ultra", "draft", "", 1])
|
||||||
|
def test_video_adapter_rejects_invalid_quality(bad_quality):
|
||||||
|
request = VideoGenerationsRequest(
|
||||||
|
prompt="contract",
|
||||||
|
task="t2va",
|
||||||
|
conditions=[],
|
||||||
|
target=TARGET,
|
||||||
|
quality=bad_quality,
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError, match="quality must be one of"):
|
||||||
|
MiniMaxH3SamplingParams.lower_video_request_kwargs(
|
||||||
|
request, {"prompt": request.prompt, "seed": request.seed}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _HopperCapability:
|
class _HopperCapability:
|
||||||
def to_int(self) -> int:
|
def to_int(self) -> int:
|
||||||
return 90
|
return 90
|
||||||
@@ -289,7 +304,7 @@ def test_quality_admission_fails_closed_outside_validated_request():
|
|||||||
with pytest.raises(ValueError, match="does not support SageAttention"):
|
with pytest.raises(ValueError, match="does not support SageAttention"):
|
||||||
stage.forward(batch, server_args)
|
stage.forward(batch, server_args)
|
||||||
|
|
||||||
batch.sampling_params.quality = "unsupported"
|
batch.sampling_params.quality = "ultra"
|
||||||
server_args.attention_backend = None
|
server_args.attention_backend = None
|
||||||
with pytest.raises(ValueError, match="unsupported MiniMax-H3 quality profile"):
|
with pytest.raises(ValueError, match="quality must be one of"):
|
||||||
stage.forward(batch, server_args)
|
stage.forward(batch, server_args)
|
||||||
|
|||||||
@@ -41,12 +41,17 @@ class TestSamplingParamsValidate(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError, r"num_outputs_per_prompt"):
|
with self.assertRaisesRegex(ValueError, r"num_outputs_per_prompt"):
|
||||||
SamplingParams(num_outputs_per_prompt=0)
|
SamplingParams(num_outputs_per_prompt=0)
|
||||||
|
|
||||||
def test_quality_must_be_a_non_empty_profile_name(self):
|
def test_quality_defaults_to_lossless(self):
|
||||||
|
self.assertEqual(SamplingParams().quality, "lossless")
|
||||||
|
|
||||||
|
def test_quality_accepts_the_two_validated_levels(self):
|
||||||
|
self.assertEqual(SamplingParams(quality="lossless").quality, "lossless")
|
||||||
self.assertEqual(SamplingParams(quality="high").quality, "high")
|
self.assertEqual(SamplingParams(quality="high").quality, "high")
|
||||||
with self.assertRaisesRegex(ValueError, r"quality must be a non-empty string"):
|
|
||||||
SamplingParams(quality="")
|
def test_quality_rejects_invalid_values(self):
|
||||||
with self.assertRaisesRegex(ValueError, r"quality must be a non-empty string"):
|
for bad in ("ultra", "draft", "fast", "", True, 1):
|
||||||
SamplingParams(quality=True) # type: ignore[arg-type]
|
with self.assertRaisesRegex(ValueError, r"quality must be one of"):
|
||||||
|
SamplingParams(quality=bad) # type: ignore[arg-type]
|
||||||
|
|
||||||
def test_seed_accepts_int_or_non_empty_int_list(self):
|
def test_seed_accepts_int_or_non_empty_int_list(self):
|
||||||
self.assertEqual(SamplingParams(seed=7).seed, 7)
|
self.assertEqual(SamplingParams(seed=7).seed, 7)
|
||||||
@@ -235,7 +240,7 @@ class TestSamplingParamsCliArgs(unittest.TestCase):
|
|||||||
def test_quality_is_request_scoped_cli_arg(self):
|
def test_quality_is_request_scoped_cli_arg(self):
|
||||||
self.assertNotIn("quality", self._parse_cli_kwargs([]))
|
self.assertNotIn("quality", self._parse_cli_kwargs([]))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self._parse_cli_kwargs(["--quality", "medium"])["quality"], "medium"
|
self._parse_cli_kwargs(["--quality", "high"])["quality"], "high"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_qwen_image_cli_path_preserves_model_defaults(self):
|
def test_qwen_image_cli_path_preserves_model_defaults(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user