[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:
Xiaoyu Zhang
2026-08-04 11:43:16 +08:00
committed by GitHub
co-authored by Claude Fable 5
parent 614825fd38
commit c6f2a9c1d4
10 changed files with 122 additions and 106 deletions
+32 -52
View File
@@ -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.
+1 -1
View File
@@ -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(
@@ -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,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
@@ -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,
@@ -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):