Views
No views yet
| Description | Image |
|---|---|
| Flux.1-Schnell | |
| Flux.1-Dev |
torch.compiletorch.channels_last memory format for the decoder outputtorch.float8_e4m3fntorchao's float8_dynamic_activation_float8_weightconv_1x1_as_mm = Trueepilogue_fusion = Falsecoordinate_descent_tuning = Truecoordinate_descent_check_all_directions = Truetorch.export + Ahead-of-time Inductor (AOTI) + CUDAGraphstorch.compile / torch.export) EXCEPT FOR dynamic float8 quantization.
Disable quantization if you want the same quality results as the baseline while still being
quite a bit faster."A cat playing with a ball of yarn":| Configuration | Output |
|---|---|
| Baseline | |
| Fully-optimized (with quantization) |
torch==2.8.0.dev20250605+cu126 - note that we rely on some fixes since 2.7torchao==0.12.0.dev20250610+cu126 - note that we rely on a fix in the 06/10 nightlydiffusers - with this fix includedflash_attn_3==3.0.0b1pip install -U diffusers
pip install --pre torch==2.8.0.dev20250605+cu126 --index-url https://download.pytorch.org/whl/nightly/cu126
pip install --pre torchao==0.12.0.dev20250609+cu126 --index-url https://download.pytorch.org/whl/nightly/cu126python gen_image.py --prompt "An astronaut standing next to a giant lemon" --output-file output.png --use-cached-modeltorch.export + AOTI. To generate these binaries for subsequent runs, run
the above command without the --use-cached-model flag.[!IMPORTANT] The binaries won't work for hardware that is sufficiently different from the hardware they were obtained on. For example, if the binaries were obtained on an H100, they won't work on A100. Further, the binaries are currently Linux-only and include dependencies on specific versions of system libs such as libstdc++; they will not work if they were generated in a sufficiently different environment than the one present at runtime. The PyTorch Compiler team is working on solutions for more portable binaries / artifact caching.
run_benchmark.py is the main script for benchmarking the different optimization techniques.
Usage:usage: run_benchmark.py [-h] [--ckpt CKPT] [--prompt PROMPT] [--cache-dir CACHE_DIR]
[--device {cuda,cpu}] [--num_inference_steps NUM_INFERENCE_STEPS]
[--output-file OUTPUT_FILE] [--trace-file TRACE_FILE] [--disable_bf16]
[--compile_export_mode {compile,export_aoti,disabled}]
[--disable_fused_projections] [--disable_channels_last] [--disable_fa3]
[--disable_quant] [--disable_inductor_tuning_flags]
options:
-h, --help show this help message and exit
--ckpt CKPT Model checkpoint path (default: black-forest-labs/FLUX.1-schnell)
--prompt PROMPT Text prompt (default: A cat playing with a ball of yarn)
--cache-dir CACHE_DIR
Cache directory for storing exported models (default:
~/.cache/flux-fast)
--device {cuda,cpu} Device to use (default: cuda)
--num_inference_steps NUM_INFERENCE_STEPS
Number of denoising steps (default: 4)
--output-file OUTPUT_FILE
Output image file path (default: output.png)
--trace-file TRACE_FILE
Output PyTorch Profiler trace file path (default: None)
--disable_bf16 Disables usage of torch.bfloat16 (default: False)
--compile_export_mode {compile,export_aoti,disabled}
Configures how torch.compile or torch.export + AOTI are used (default:
export_aoti)
--disable_fused_projections
Disables fused q,k,v projections (default: False)
--disable_channels_last
Disables usage of torch.channels_last memory format (default: False)
--disable_fa3 Disables use of Flash Attention V3 (default: False)
--disable_quant Disables usage of dynamic float8 quantization (default: False)
--disable_inductor_tuning_flags
Disables use of inductor tuning flags (default: False)# Run with all optimizations and output a trace file alongside benchmark numbers
python run_benchmark.py --trace-file profiler_trace.json.gz.png image file corresponding to the experiment (e.g. output.png). The path can be configured via --output-file.profiler_trace.json.gz). The path can be configured via --trace-filetorch.float32 dtype.
There's no practical reason do this over loading in torch.bfloat16, and the results are slow enough
that they ruin the readability of the graph above when included (~7.5 sec).1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8prompt = "A cat playing with a ball of yarn"
9image = pipe(prompt, num_inference_steps=4).images[0]1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell", torch_dtype=torch.bfloat16
6).to("cuda")
7
8prompt = "A cat playing with a ball of yarn"
9image = pipe(prompt, num_inference_steps=4).images[0]1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8# Compile the compute-intensive portions of the model: denoising transformer / decoder
9# "max-autotune" mode tunes kernel hyperparameters and applies CUDAGraphs
10pipeline.transformer = torch.compile(
11 pipeline.transformer, mode="max-autotune", fullgraph=True
12)
13pipeline.vae.decode = torch.compile(
14 pipeline.vae.decode, mode="max-autotune", fullgraph=True
15)
16
17# warmup for a few iterations; trigger compilation
18for _ in range(3):
19 pipeline(
20 "dummy prompt to trigger torch compilation",
21 output_type="pil",
22 num_inference_steps=4,
23 ).images[0]
24
25prompt = "A cat playing with a ball of yarn"
26image = pipe(prompt, num_inference_steps=4).images[0]1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8# Use channels_last memory format
9pipeline.vae = pipeline.vae.to(memory_format=torch.channels_last)
10
11# Combine attention projection matrices for (q, k, v)
12pipeline.transformer.fuse_qkv_projections()
13pipeline.vae.fuse_qkv_projections()
14
15# compilation details omitted (see above)
16...
17
18prompt = "A cat playing with a ball of yarn"
19image = pipe(prompt, num_inference_steps=4).images[0]torch.compile is able to perform this fusion automatically, so we do not
observe a speedup from the fusion (outside of noise) when torch.compile is enabled.1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8# Use channels_last memory format
9pipeline.vae.to(memory_format=torch.channels_last)
10
11# compilation details omitted (see above)
12...
13
14prompt = "A cat playing with a ball of yarn"
15image = pipe(prompt, num_inference_steps=4).images[0]FlashFusedFluxAttnProcessor3_0 that uses the flash_attn_interface
python bindings directly. We also ensure proper PyTorch custom op integration so that
the op integrates well with torch.compile / torch.export. Inputs are converted to float8 in an unscaled fashion before
kernel invocation and outputs are converted back to the original dtype on the way out.1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8# Use channels_last memory format
9pipeline.vae.to(memory_format=torch.channels_last)
10
11# Combine attention projection matrices for (q, k, v)
12pipeline.transformer.fuse_qkv_projections()
13pipeline.vae.fuse_qkv_projections()
14
15# Use FA3; reference FlashFusedFluxAttnProcessor3_0 impl for details
16pipeline.transformer.set_attn_processor(FlashFusedFluxAttnProcessor3_0())
17
18# compilation details omitted (see above)
19...
20
21prompt = "A cat playing with a ball of yarn"
22image = pipe(prompt, num_inference_steps=4).images[0]1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8# Use channels_last memory format
9pipeline.vae.to(memory_format=torch.channels_last)
10
11# Combine attention projection matrices for (q, k, v)
12pipeline.transformer.fuse_qkv_projections()
13pipeline.vae.fuse_qkv_projections()
14
15# Use FA3; reference FlashFusedFluxAttnProcessor3_0 impl for details
16pipeline.transformer.set_attn_processor(FlashFusedFluxAttnProcessor3_0())
17
18# Apply float8 quantization on weights and activations
19from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight
20
21quantize_(
22 pipeline.transformer,
23 float8_dynamic_activation_float8_weight(),
24)
25
26# compilation details omitted (see above)
27...
28
29prompt = "A cat playing with a ball of yarn"
30image = pipe(prompt, num_inference_steps=4).images[0]1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8# Use channels_last memory format
9pipeline.vae.to(memory_format=torch.channels_last)
10
11# Combine attention projection matrices for (q, k, v)
12pipeline.transformer.fuse_qkv_projections()
13pipeline.vae.fuse_qkv_projections()
14
15# Use FA3; reference FlashFusedFluxAttnProcessor3_0 impl for details
16pipeline.transformer.set_attn_processor(FlashFusedFluxAttnProcessor3_0())
17
18# Apply float8 quantization on weights and activations
19from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight
20
21quantize_(
22 pipeline.transformer,
23 float8_dynamic_activation_float8_weight(),
24)
25
26# Tune Inductor flags
27config = torch._inductor.config
28config.conv_1x1_as_mm = True # treat 1x1 convolutions as matrix muls
29# adjust autotuning algorithm
30config.coordinate_descent_tuning = True
31config.coordinate_descent_check_all_directions = True
32config.epilogue_fusion = False # do not fuse pointwise ops into matmuls
33
34# compilation details omitted (see above)
35...
36
37prompt = "A cat playing with a ball of yarn"
38image = pipe(prompt, num_inference_steps=4).images[0]torch.export + Ahead-Of-Time Inductor (AOTI). This will
serialize a binary, precompiled form of the model without initial compilation overhead.1# Apply torch.export + AOTI. If serialize=True, writes out the exported models within the cache_dir.
2# Otherwise, attempts to load previously-exported models from the cache_dir.
3# This function also applies CUDAGraphs on the loaded models.
4def use_export_aoti(pipeline, cache_dir, serialize=False):
5 from torch._inductor.package import load_package
6
7 # create cache dir if needed
8 pathlib.Path(cache_dir).mkdir(parents=True, exist_ok=True)
9
10 def _example_tensor(*shape):
11 return torch.randn(*shape, device="cuda", dtype=torch.bfloat16)
12
13 # === Transformer export ===
14 # torch.export requires a representative set of example args to be passed in
15 transformer_kwargs = {
16 "hidden_states": _example_tensor(1, 4096, 64),
17 "timestep": torch.tensor([1.], device="cuda", dtype=torch.bfloat16),
18 "guidance": None,
19 "pooled_projections": _example_tensor(1, 768),
20 "encoder_hidden_states": _example_tensor(1, 512, 4096),
21 "txt_ids": _example_tensor(512, 3),
22 "img_ids": _example_tensor(4096, 3),
23 "joint_attention_kwargs": {},
24 "return_dict": False,
25 }
26
27 # Possibly serialize model out
28 transformer_package_path = os.path.join(cache_dir, "exported_transformer.pt2")
29 if serialize:
30 # Apply export
31 exported_transformer: torch.export.ExportedProgram = torch.export.export(
32 pipeline.transformer, args=(), kwargs=transformer_kwargs
33 )
34
35 # Apply AOTI
36 path = torch._inductor.aoti_compile_and_package(
37 exported_transformer,
38 package_path=transformer_package_path,
39 inductor_configs={"max_autotune": True, "triton.cudagraphs": True},
40 )
41
42 loaded_transformer = load_package(
43 transformer_package_path, run_single_threaded=True
44 )
45
46 # warmup before cudagraphing
47 with torch.no_grad():
48 loaded_transformer(**transformer_kwargs)
49
50 # Apply CUDAGraphs. CUDAGraphs are utilized in torch.compile with mode="max-autotune", but
51 # they must be manually applied for torch.export + AOTI.
52 loaded_transformer = cudagraph(loaded_transformer)
53 pipeline.transformer.forward = loaded_transformer
54
55 # warmup after cudagraphing
56 with torch.no_grad():
57 pipeline.transformer(**transformer_kwargs)
58
59 # hack to get around export's limitations
60 pipeline.vae.forward = pipeline.vae.decode
61
62 vae_decode_kwargs = {
63 "return_dict": False,
64 }
65
66 # Possibly serialize model out
67 decoder_package_path = os.path.join(cache_dir, "exported_decoder.pt2")
68 if serialize:
69 # Apply export
70 exported_decoder: torch.export.ExportedProgram = torch.export.export(
71 pipeline.vae, args=(_example_tensor(1, 16, 128, 128),), kwargs=vae_decode_kwargs
72 )
73
74 # Apply AOTI
75 path = torch._inductor.aoti_compile_and_package(
76 exported_decoder,
77 package_path=decoder_package_path,
78 inductor_configs={"max_autotune": True, "triton.cudagraphs": True},
79 )
80
81 loaded_decoder = load_package(decoder_package_path, run_single_threaded=True)
82
83 # warmup before cudagraphing
84 with torch.no_grad():
85 loaded_decoder(_example_tensor(1, 16, 128, 128), **vae_decode_kwargs)
86
87 loaded_decoder = cudagraph(loaded_decoder)
88 pipeline.vae.decode = loaded_decoder
89
90 # warmup for a few iterations
91 for _ in range(3):
92 pipeline(
93 "dummy prompt to trigger torch compilation",
94 output_type="pil",
95 num_inference_steps=4,
96 ).images[0]
97
98 return pipelinetorch.compile, running a model loaded from the torch.export + AOTI workflow
doesn't use CUDAGraphs by default. This was found to result in a ~5% performance decrease vs. torch.compile.
To address this discrepancy, we manually record / replay CUDAGraphs over the exported models using the following helper:1# wrapper to automatically handle CUDAGraph record / replay over the given function
2def cudagraph(f):
3 from torch.utils._pytree import tree_map_only
4
5 _graphs = {}
6 def f_(*args, **kwargs):
7 key = hash(tuple(tuple(kwargs[a].shape) for a in sorted(kwargs.keys())
8 if isinstance(kwargs[a], torch.Tensor)))
9 if key in _graphs:
10 # use the cached wrapper if one exists. this will perform CUDAGraph replay
11 wrapped, *_ = _graphs[key]
12 return wrapped(*args, **kwargs)
13
14 # record a new CUDAGraph and cache it for future use
15 g = torch.cuda.CUDAGraph()
16 in_args, in_kwargs = tree_map_only(torch.Tensor, lambda t: t.clone(), (args, kwargs))
17 f(*in_args, **in_kwargs) # stream warmup
18 with torch.cuda.graph(g):
19 out_tensors = f(*in_args, **in_kwargs)
20 def wrapped(*args, **kwargs):
21 # note that CUDAGraphs require inputs / outputs to be in fixed memory locations.
22 # inputs must be copied into the fixed input memory locations.
23 [a.copy_(b) for a, b in zip(in_args, args) if isinstance(a, torch.Tensor)]
24 for key in kwargs:
25 if isinstance(kwargs[key], torch.Tensor):
26 in_kwargs[key].copy_(kwargs[key])
27 g.replay()
28 # clone() outputs on the way out to disconnect them from the fixed output memory
29 # locations. this allows for CUDAGraph reuse without accidentally overwriting memory
30 return [o.clone() for o in out_tensors]
31
32 # cache function that does CUDAGraph replay
33 _graphs[key] = (wrapped, g, in_args, in_kwargs, out_tensors)
34 return wrapped(*args, **kwargs)
35 return f_1from diffusers import FluxPipeline
2
3# Load the pipeline in full-precision and place its model components on CUDA.
4pipeline = FluxPipeline.from_pretrained(
5 "black-forest-labs/FLUX.1-schnell"
6).to("cuda")
7
8# Use channels_last memory format
9pipeline.vae.to(memory_format=torch.channels_last)
10
11# Combine attention projection matrices for (q, k, v)
12pipeline.transformer.fuse_qkv_projections()
13pipeline.vae.fuse_qkv_projections()
14
15# Use FA3; reference FlashFusedFluxAttnProcessor3_0 impl for details
16pipeline.transformer.set_attn_processor(FlashFusedFluxAttnProcessor3_0())
17
18# Apply float8 quantization on weights and activations
19from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight
20
21quantize_(
22 pipeline.transformer,
23 float8_dynamic_activation_float8_weight(),
24)
25
26# Tune Inductor flags
27config = torch._inductor.config
28config.conv_1x1_as_mm = True # treat 1x1 convolutions as matrix muls
29# adjust autotuning algorithm
30config.coordinate_descent_tuning = True
31config.coordinate_descent_check_all_directions = True
32config.epilogue_fusion = False # do not fuse pointwise ops into matmuls
33
34# Apply torch.export + AOTI with CUDAGraphs
35pipeline = use_export_aoti(pipeline, cache_dir=args.cache_dir, serialize=False)
36
37prompt = "A cat playing with a ball of yarn"
38image = pipe(prompt, num_inference_steps=4).images[0]