Skip to content

Commit 759164b

Browse files
ConstBobsayakpaulgithub-actions[bot]
authored
[Cosmos3] Mixed W8A8/W8A16 denoising for ModelOpt FP8 checkpoints (#14664)
* Add opt-in mixed W8A8/W8A16 denoising for Cosmos3 ModelOpt FP8 checkpoints. Keep native ModelOpt GEMM on middle steps and dequant-linear W8A16 on the first/last steps so CFG cond/uncond share one precision per scheduler step. * Read Cosmos3 mixed-precision schedule from the checkpoint policy. Enable first/last W8A16 only when transformer/config.json declares diffusion_step_policy, so distilled FP8 stays native W8A8 instead of inheriting a hardcoded 3+3 window. * Harden Cosmos3 mixed-precision loading and document official Hub fp8 schedules. Read the checkpoint runtime policy from on-disk transformer/config.json when the live ModelOpt config omits it, fail closed on incomplete policies, and allow FP32 activations on the W8A16 path. * Address review: simplify FP8 mixed docs, no-op non-ModelOpt backends, drop unit tests. W8A8/W8A16 is explained without listing call-site overrides; TorchAO and other quantizers keep their native forwards; focused tests are removed until Hub fp8 usage is clearer. * Document FP8 mixed default vs none trade-offs without ModelOpt restore boilerplate. Keep serialized restore in the ModelOpt guide and spell out that all-W8A8 is faster but can flicker on multi-step video. * Apply style fixes * Regenerate Cosmos modular auto docstrings for mixed-precision inputs. --------- Co-authored-by: Sayak Paul <spsayakpaul@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 1f7be81 commit 759164b

7 files changed

Lines changed: 966 additions & 130 deletions

File tree

docs/source/en/api/pipelines/cosmos3.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,38 @@ Two checkpoints are released on the Hub — [`nvidia/Cosmos3-Nano`](https://hugg
4343
> [!TIP]
4444
> Make sure to check out the Schedulers [guide](../../using-diffusers/schedulers) to learn how to explore the tradeoff between scheduler speed and quality, and see the [reuse components across pipelines](../../using-diffusers/loading#reusing-models-in-multiple-pipelines) section to learn how to efficiently load the same components into multiple pipelines.
4545
46+
## FP8 mixed W8A8/W8A16 denoising
47+
48+
Official ModelOpt FP8 checkpoints live on the Hub `fp8` revision (for example [`nvidia/Cosmos3-Nano`](https://huggingface.co/nvidia/Cosmos3-Nano) with `revision="fp8"`).
49+
50+
All of these checkpoints are quantized the same way. **W8A8** uses 8-bit weights and 8-bit activations (the restored ModelOpt GEMM). **W8A16** reuses those same 8-bit weights but skips activation quantization: the FP8 weight is dequantized and a standard linear runs on BF16/FP16/FP32 activations.
51+
52+
Running W8A8 on every step can produce visible flickering in multi-step video generation. The video Nano / Super / Super-I2V FP8 checkpoints therefore declare a schedule in `transformer/config.json`: **W8A16 on the first 3 and last 3 steps**, **W8A8 in the middle**. Diffusers reads those counts from the checkpoint rather than hardcoding them. Precision is chosen once per scheduler step so classifier-free guidance cond/uncond calls match.
53+
54+
Image generation and few-step distilled checkpoints do not show that flickering, so Super-T2I and the distilled 4-step FP8 repos declare no schedule and stay W8A8 on every step. The schedule is also **ModelOpt FP8 only**: other quantization backends (for example TorchAO) keep their native forwards.
55+
56+
Load the `fp8` revision with the same restore path as the [ModelOpt guide](../../quantization/modelopt) (`revision="fp8"` already carries the quantization config). Mixed precision then follows the checkpoint automatically:
57+
58+
```python
59+
import torch
60+
from diffusers import Cosmos3OmniPipeline
61+
62+
pipe = Cosmos3OmniPipeline.from_pretrained(
63+
"nvidia/Cosmos3-Nano",
64+
revision="fp8",
65+
dtype=torch.bfloat16,
66+
device_map="cuda",
67+
)
68+
result = pipe(prompt="...", num_inference_steps=35)
69+
```
70+
71+
Two generate-time choices:
72+
73+
- **Default** (`mixed_precision_format=None`): if the checkpoint declares `diffusion_step_policy`, run W8A16 on the first/last N steps and native W8A8 in the middle. That is the intended recipe for multi-step **video** FP8 (less flickering than all-W8A8). Distilled 4-step and Super-T2I FP8 omit the policy, so the default is already all W8A8.
74+
- **`mixed_precision_format="none"`**: keep every step on native W8A8. Faster, because W8A16 is dequant + `torch.nn.functional.linear` rather than the restored FP8 GEMM, but multi-step video can flicker. Use this to A/B the schedule or to match a fully quantized baseline.
75+
76+
On one Blackwell workstation, Cosmos3-Nano `@fp8` at 720×1280 / 35 steps was about **27% slower** (T2I) and **13% slower** (49-frame T2V) with the default mixed schedule than with `"none"`. Those numbers are not a throughput guarantee. Pass `"fp8"` only to force the first/last-N schedule on a ModelOpt FP8 checkpoint that has no policy.
77+
4678
## Prompt upsampling
4779

4880
Cosmos 3 was trained on long, highly descriptive captions. For optimal quality, short text prompts should be **upsampled into a specific JSON structure** before they are passed to the pipeline. The upsampler lives in the [cosmos-framework](https://github.com/NVIDIA/cosmos-framework) package.
@@ -1117,6 +1149,9 @@ config (from the checkpoint's `modular_model_index.json`) and `guidance_scale` i
11171149
1.0 since guidance is baked into the weights — passing any other value for either raises an error,
11181150
and `negative_prompt` is warned about and ignored.
11191151

1152+
FP8 distilled checkpoints (`revision="fp8"`) do not declare a mixed-precision policy, so every
1153+
step stays native W8A8.
1154+
11201155
Prompts follow the same descriptive JSON structure as the non-distilled models, so short text
11211156
must be upsampled first — use `--mode text2image` (T2I) or `--mode image2video` (I2V) as
11221157
described in [Prompt upsampling](#prompt-upsampling), then pass the JSON via `json.dumps(...)`.

src/diffusers/modular_pipelines/cosmos/denoise.py

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
import torch
44

55
from ...models.transformers.transformer_cosmos3 import Cosmos3OmniTransformer
6+
from ...pipelines.cosmos.mixed_precision import (
7+
Cosmos3MixedPrecisionConfig,
8+
apply_cosmos3_mixed_precision_step,
9+
reset_cosmos3_mixed_precision,
10+
)
611
from ...schedulers import FlowMatchEulerDiscreteScheduler, UniPCMultistepScheduler
712
from ..modular_pipeline import (
813
BlockState,
@@ -463,18 +468,61 @@ def loop_inputs(self) -> list[InputParam]:
463468
InputParam(
464469
name="num_warmup_steps", type_hint=int, required=True, description="Number of scheduler warmup steps."
465470
),
471+
InputParam(
472+
name="mixed_precision_format",
473+
type_hint=str,
474+
default=None,
475+
description="None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.",
476+
),
477+
InputParam(
478+
name="mixed_precision_first_steps",
479+
type_hint=int,
480+
default=None,
481+
description="Optional leading W8A16 step count.",
482+
),
483+
InputParam(
484+
name="mixed_precision_last_steps",
485+
type_hint=int,
486+
default=None,
487+
description="Optional trailing W8A16 step count.",
488+
),
489+
InputParam(
490+
name="mixed_precision_reasoner_policy",
491+
type_hint=str,
492+
default=None,
493+
description="Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).",
494+
),
466495
]
467496

468497
@torch.no_grad()
469498
def __call__(self, components: Cosmos3OmniModularPipeline, state: PipelineState) -> PipelineState:
470499
block_state = self.get_block_state(state)
471-
with self.progress_bar(total=block_state.num_inference_steps) as progress_bar:
472-
for i, t in enumerate(block_state.timesteps):
473-
components, block_state = self.loop_step(components, block_state, i=i, t=t)
474-
if i == len(block_state.timesteps) - 1 or (
475-
(i + 1) > block_state.num_warmup_steps and (i + 1) % components.scheduler.order == 0
476-
):
477-
progress_bar.update()
500+
mixed_precision = Cosmos3MixedPrecisionConfig.resolve(
501+
components.transformer,
502+
mixed_precision_format=getattr(block_state, "mixed_precision_format", None),
503+
mixed_precision_first_steps=getattr(block_state, "mixed_precision_first_steps", None),
504+
mixed_precision_last_steps=getattr(block_state, "mixed_precision_last_steps", None),
505+
mixed_precision_reasoner_policy=getattr(block_state, "mixed_precision_reasoner_policy", None),
506+
)
507+
trace = []
508+
try:
509+
with self.progress_bar(total=block_state.num_inference_steps) as progress_bar:
510+
for i, t in enumerate(block_state.timesteps):
511+
apply_cosmos3_mixed_precision_step(
512+
components.transformer,
513+
mixed_precision,
514+
i,
515+
len(block_state.timesteps),
516+
trace=trace,
517+
)
518+
components, block_state = self.loop_step(components, block_state, i=i, t=t)
519+
if i == len(block_state.timesteps) - 1 or (
520+
(i + 1) > block_state.num_warmup_steps and (i + 1) % components.scheduler.order == 0
521+
):
522+
progress_bar.update()
523+
finally:
524+
reset_cosmos3_mixed_precision(components.transformer, mixed_precision)
525+
components._mixed_precision_trace = trace
478526
self.set_block_state(state, block_state)
479527
return components, state
480528

@@ -849,6 +897,15 @@ class Cosmos3TransferDenoiseStep(Cosmos3DenoiseLoopWrapper):
849897
The number of denoising steps.
850898
num_warmup_steps (`int`):
851899
Number of scheduler warmup steps.
900+
mixed_precision_format (`str`, *optional*):
901+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
902+
ModelOpt FP8 only.
903+
mixed_precision_first_steps (`int`, *optional*):
904+
Optional leading W8A16 step count.
905+
mixed_precision_last_steps (`int`, *optional*):
906+
Optional trailing W8A16 step count.
907+
mixed_precision_reasoner_policy (`str`, *optional*):
908+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
852909
control_latents (`list`):
853910
Clean control latents for this chunk, one per hint in canonical order.
854911
latents (`Tensor`):

src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,15 @@ class Cosmos3VisionCoreDenoiseStep(SequentialPipelineBlocks):
396396
Torch generator for deterministic generation.
397397
num_inference_steps (`int`):
398398
The number of denoising steps.
399+
mixed_precision_format (`str`, *optional*):
400+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
401+
ModelOpt FP8 only.
402+
mixed_precision_first_steps (`int`, *optional*):
403+
Optional leading W8A16 step count.
404+
mixed_precision_last_steps (`int`, *optional*):
405+
Optional trailing W8A16 step count.
406+
mixed_precision_reasoner_policy (`str`, *optional*):
407+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
399408
**denoiser_input_fields (`None`, *optional*):
400409
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
401410
guidance_scale (`float`, *optional*, defaults to 6.0):
@@ -469,6 +478,15 @@ class Cosmos3VisionSoundCoreDenoiseStep(SequentialPipelineBlocks):
469478
The number of denoising steps.
470479
sound_latents (`Tensor`, *optional*):
471480
Pre-generated noisy sound latents.
481+
mixed_precision_format (`str`, *optional*):
482+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
483+
ModelOpt FP8 only.
484+
mixed_precision_first_steps (`int`, *optional*):
485+
Optional leading W8A16 step count.
486+
mixed_precision_last_steps (`int`, *optional*):
487+
Optional trailing W8A16 step count.
488+
mixed_precision_reasoner_policy (`str`, *optional*):
489+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
472490
**denoiser_input_fields (`None`, *optional*):
473491
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
474492
guidance_scale (`float`, *optional*, defaults to 6.0):
@@ -557,6 +575,15 @@ class Cosmos3VisionActionCoreDenoiseStep(SequentialPipelineBlocks):
557575
Action-frame indexes fixed by action conditioning.
558576
action_latents (`Tensor`, *optional*):
559577
Pre-generated noisy action latents.
578+
mixed_precision_format (`str`, *optional*):
579+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
580+
ModelOpt FP8 only.
581+
mixed_precision_first_steps (`int`, *optional*):
582+
Optional leading W8A16 step count.
583+
mixed_precision_last_steps (`int`, *optional*):
584+
Optional trailing W8A16 step count.
585+
mixed_precision_reasoner_policy (`str`, *optional*):
586+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
560587
**denoiser_input_fields (`None`, *optional*):
561588
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
562589
guidance_scale (`float`, *optional*, defaults to 6.0):
@@ -647,6 +674,15 @@ class Cosmos3VisionSoundActionCoreDenoiseStep(SequentialPipelineBlocks):
647674
Action-frame indexes fixed by action conditioning.
648675
action_latents (`Tensor`, *optional*):
649676
Pre-generated noisy action latents.
677+
mixed_precision_format (`str`, *optional*):
678+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
679+
ModelOpt FP8 only.
680+
mixed_precision_first_steps (`int`, *optional*):
681+
Optional leading W8A16 step count.
682+
mixed_precision_last_steps (`int`, *optional*):
683+
Optional trailing W8A16 step count.
684+
mixed_precision_reasoner_policy (`str`, *optional*):
685+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
650686
**denoiser_input_fields (`None`, *optional*):
651687
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
652688
guidance_scale (`float`, *optional*, defaults to 6.0):
@@ -748,6 +784,15 @@ class Cosmos3TransferChunkDenoiseStep(SequentialPipelineBlocks):
748784
Frame rate of the generated video.
749785
num_inference_steps (`int`):
750786
The number of denoising steps.
787+
mixed_precision_format (`str`, *optional*):
788+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
789+
ModelOpt FP8 only.
790+
mixed_precision_first_steps (`int`, *optional*):
791+
Optional leading W8A16 step count.
792+
mixed_precision_last_steps (`int`, *optional*):
793+
Optional trailing W8A16 step count.
794+
mixed_precision_reasoner_policy (`str`, *optional*):
795+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
751796
**denoiser_input_fields (`None`, *optional*):
752797
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
753798
guidance_scale (`float`, *optional*, defaults to 6.0):
@@ -890,6 +935,15 @@ class Cosmos3TransferCoreDenoiseStep(SequentialPipelineBlocks):
890935
Frame rate of the generated video.
891936
num_inference_steps (`int`):
892937
The number of denoising steps.
938+
mixed_precision_format (`str`, *optional*):
939+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
940+
ModelOpt FP8 only.
941+
mixed_precision_first_steps (`int`, *optional*):
942+
Optional leading W8A16 step count.
943+
mixed_precision_last_steps (`int`, *optional*):
944+
Optional trailing W8A16 step count.
945+
mixed_precision_reasoner_policy (`str`, *optional*):
946+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
893947
**denoiser_input_fields (`None`, *optional*):
894948
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
895949
guidance_scale (`float`, *optional*, defaults to 6.0):
@@ -1012,6 +1066,15 @@ class Cosmos3AutoCoreDenoiseStep(ConditionalPipelineBlocks):
10121066
Frame rate of the generated video.
10131067
num_inference_steps (`int`):
10141068
The number of denoising steps.
1069+
mixed_precision_format (`str`, *optional*):
1070+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
1071+
ModelOpt FP8 only.
1072+
mixed_precision_first_steps (`int`, *optional*):
1073+
Optional leading W8A16 step count.
1074+
mixed_precision_last_steps (`int`, *optional*):
1075+
Optional trailing W8A16 step count.
1076+
mixed_precision_reasoner_policy (`str`, *optional*):
1077+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
10151078
**denoiser_input_fields (`None`, *optional*):
10161079
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
10171080
guidance_scale (`float`, *optional*, defaults to 6.0):
@@ -1215,6 +1278,15 @@ class Cosmos3OmniBlocks(SequentialPipelineBlocks):
12151278
Torch generator for deterministic generation.
12161279
num_inference_steps (`int`):
12171280
The number of denoising steps.
1281+
mixed_precision_format (`str`, *optional*):
1282+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
1283+
ModelOpt FP8 only.
1284+
mixed_precision_first_steps (`int`, *optional*):
1285+
Optional leading W8A16 step count.
1286+
mixed_precision_last_steps (`int`, *optional*):
1287+
Optional trailing W8A16 step count.
1288+
mixed_precision_reasoner_policy (`str`, *optional*):
1289+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
12181290
**denoiser_input_fields (`None`, *optional*):
12191291
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
12201292
guidance_scale (`float`, *optional*, defaults to 6.0):

src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3_distilled.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ class Cosmos3DistilledVisionCoreDenoiseStep(SequentialPipelineBlocks):
114114
guidance_scale (`float`, *optional*):
115115
Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is
116116
forced to 1.0. Passing a value other than 1.0 raises an error.
117+
mixed_precision_format (`str`, *optional*):
118+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
119+
ModelOpt FP8 only.
120+
mixed_precision_first_steps (`int`, *optional*):
121+
Optional leading W8A16 step count.
122+
mixed_precision_last_steps (`int`, *optional*):
123+
Optional trailing W8A16 step count.
124+
mixed_precision_reasoner_policy (`str`, *optional*):
125+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
117126
**denoiser_input_fields (`None`, *optional*):
118127
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
119128
@@ -206,6 +215,15 @@ class Cosmos3DistilledBlocks(SequentialPipelineBlocks):
206215
guidance_scale (`float`, *optional*):
207216
Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is
208217
forced to 1.0. Passing a value other than 1.0 raises an error.
218+
mixed_precision_format (`str`, *optional*):
219+
None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is
220+
ModelOpt FP8 only.
221+
mixed_precision_first_steps (`int`, *optional*):
222+
Optional leading W8A16 step count.
223+
mixed_precision_last_steps (`int`, *optional*):
224+
Optional trailing W8A16 step count.
225+
mixed_precision_reasoner_policy (`str`, *optional*):
226+
Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).
209227
**denoiser_input_fields (`None`, *optional*):
210228
conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.
211229
output_type (`str`, *optional*, defaults to pil):

0 commit comments

Comments
 (0)