Skip to content

Commit 5ca3fc7

Browse files
committed
Outrider brief — Add ChebBooster (Chebyshev feature cache) to 🤗 diffusers
1 parent 0310c97 commit 5ca3fc7

12 files changed

Lines changed: 1004 additions & 2 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Copyright 2025 The HuggingFace Team. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Control harness for the ChebBooster (Chebyshev cache) PR.
16+
17+
Three-arm comparison on FLUX.1-dev with identical seeds/prompts and matched
18+
`cache_interval` + warmup:
19+
20+
(a) no-cache reference
21+
(b) in-repo TaylorSeer cache (the control arm)
22+
(c) Chebyshev cache (ChebBooster port)
23+
24+
Reports wall-clock latency per arm and quality of (b) and (c) against the
25+
no-cache reference (PSNR always; LPIPS and CLIP score when `lpips` /
26+
`open_clip_torch` are installed). The claim this harness is meant to check:
27+
at matched latency, Chebyshev cache >= TaylorSeer cache on quality. If the
28+
delta is within noise, the cache variant is redundant and the PR should not
29+
be opened upstream.
30+
31+
Usage:
32+
python benchmarking_chebyshev_cache.py --num-prompts 8 --num-inference-steps 28
33+
"""
34+
35+
import argparse
36+
import csv
37+
import time
38+
39+
import torch
40+
41+
from diffusers import ChebyshevCacheConfig, FluxPipeline, TaylorSeerCacheConfig
42+
43+
44+
CKPT_ID = "black-forest-labs/FLUX.1-dev"
45+
RESULT_FILENAME = "chebyshev_cache_control.csv"
46+
47+
# Placeholder prompt set — replace with the DrawBench prompts shipped in the
48+
# reference repo (ChebBooster-FLUX/DrawBench.jsonl) for the full control run.
49+
PROMPTS = [
50+
"A photograph of an astronaut riding a horse on Mars.",
51+
"A bowl of fruit on a wooden table, studio lighting.",
52+
"A cyberpunk city street at night in the rain.",
53+
"An oil painting of a lighthouse during a storm.",
54+
]
55+
56+
57+
def parse_args():
58+
parser = argparse.ArgumentParser()
59+
parser.add_argument("--num-prompts", type=int, default=len(PROMPTS))
60+
parser.add_argument("--num-inference-steps", type=int, default=28)
61+
parser.add_argument("--cache-interval", type=int, default=5)
62+
parser.add_argument("--disable-cache-before-step", type=int, default=3)
63+
parser.add_argument("--cheb-order", type=int, default=6)
64+
parser.add_argument("--seed", type=int, default=0)
65+
return parser.parse_args()
66+
67+
68+
def load_pipeline():
69+
pipe = FluxPipeline.from_pretrained(CKPT_ID, torch_dtype=torch.bfloat16)
70+
pipe.to("cuda")
71+
pipe.set_progress_bar_config(disable=True)
72+
return pipe
73+
74+
75+
def run_arm(pipe, prompts, num_inference_steps, seed, cache_config=None):
76+
if cache_config is not None:
77+
pipe.transformer.enable_cache(cache_config)
78+
79+
images = []
80+
start = time.perf_counter()
81+
for i, prompt in enumerate(prompts):
82+
generator = torch.Generator(device="cuda").manual_seed(seed + i)
83+
image = pipe(prompt, num_inference_steps=num_inference_steps, generator=generator).images[0]
84+
images.append(image)
85+
torch.cuda.synchronize()
86+
latency = (time.perf_counter() - start) / len(prompts)
87+
88+
if cache_config is not None:
89+
pipe.transformer.disable_cache()
90+
return images, latency
91+
92+
93+
def psnr_vs_reference(images, reference_images):
94+
values = []
95+
for image, reference in zip(images, reference_images):
96+
x = torch.tensor(list(image.getdata()), dtype=torch.float32) / 255.0
97+
y = torch.tensor(list(reference.getdata()), dtype=torch.float32) / 255.0
98+
mse = torch.mean((x - y) ** 2).item()
99+
values.append(float("inf") if mse == 0 else 10.0 * torch.log10(torch.tensor(1.0 / mse)).item())
100+
return sum(values) / len(values)
101+
102+
103+
def lpips_vs_reference(images, reference_images):
104+
try:
105+
import lpips
106+
import numpy as np
107+
except ImportError:
108+
return None
109+
110+
loss_fn = lpips.LPIPS(net="vgg").to("cuda")
111+
values = []
112+
for image, reference in zip(images, reference_images):
113+
x = torch.from_numpy(np.array(image)).permute(2, 0, 1)[None].float().to("cuda") / 127.5 - 1.0
114+
y = torch.from_numpy(np.array(reference)).permute(2, 0, 1)[None].float().to("cuda") / 127.5 - 1.0
115+
with torch.no_grad():
116+
values.append(loss_fn(x, y).item())
117+
return sum(values) / len(values)
118+
119+
120+
def main():
121+
args = parse_args()
122+
prompts = PROMPTS[: args.num_prompts]
123+
shared_kwargs = {
124+
"cache_interval": args.cache_interval,
125+
"disable_cache_before_step": args.disable_cache_before_step,
126+
}
127+
128+
pipe = load_pipeline()
129+
130+
reference_images, reference_latency = run_arm(pipe, prompts, args.num_inference_steps, args.seed)
131+
132+
taylorseer_images, taylorseer_latency = run_arm(
133+
pipe,
134+
prompts,
135+
args.num_inference_steps,
136+
args.seed,
137+
cache_config=TaylorSeerCacheConfig(max_order=1, **shared_kwargs),
138+
)
139+
140+
chebyshev_images, chebyshev_latency = run_arm(
141+
pipe,
142+
prompts,
143+
args.num_inference_steps,
144+
args.seed,
145+
cache_config=ChebyshevCacheConfig(cheb_order=args.cheb_order, **shared_kwargs),
146+
)
147+
148+
rows = [
149+
{
150+
"arm": "no-cache",
151+
"latency_s_per_image": reference_latency,
152+
"speedup": 1.0,
153+
"psnr_vs_reference": float("inf"),
154+
"lpips_vs_reference": 0.0,
155+
},
156+
{
157+
"arm": "taylorseer",
158+
"latency_s_per_image": taylorseer_latency,
159+
"speedup": reference_latency / taylorseer_latency,
160+
"psnr_vs_reference": psnr_vs_reference(taylorseer_images, reference_images),
161+
"lpips_vs_reference": lpips_vs_reference(taylorseer_images, reference_images),
162+
},
163+
{
164+
"arm": "chebyshev",
165+
"latency_s_per_image": chebyshev_latency,
166+
"speedup": reference_latency / chebyshev_latency,
167+
"psnr_vs_reference": psnr_vs_reference(chebyshev_images, reference_images),
168+
"lpips_vs_reference": lpips_vs_reference(chebyshev_images, reference_images),
169+
},
170+
]
171+
172+
with open(RESULT_FILENAME, "w", newline="") as f:
173+
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
174+
writer.writeheader()
175+
writer.writerows(rows)
176+
177+
for row in rows:
178+
print(row)
179+
print(f"\nResults written to {RESULT_FILENAME}")
180+
181+
182+
if __name__ == "__main__":
183+
main()

docs/source/en/api/cache.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ Cache methods speedup diffusion transformers by storing and reusing intermediate
4141

4242
[[autodoc]] apply_taylorseer_cache
4343

44+
## ChebyshevCacheConfig
45+
46+
[[autodoc]] ChebyshevCacheConfig
47+
48+
[[autodoc]] apply_chebyshev_cache
49+
4450
## MagCacheConfig
4551

4652
[[autodoc]] MagCacheConfig

docs/source/en/optimization/cache.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,34 @@ config = TaylorSeerCacheConfig(
112112
pipe.transformer.enable_cache(config)
113113
```
114114

115+
## ChebBooster (Chebyshev) Cache
116+
117+
[ChebBooster](https://arxiv.org/abs/2608.23429) uses the same cache machinery as TaylorSeer Cache but replaces the Taylor-series extrapolation with a numerically-stable barycentric Chebyshev interpolation over a rolling window of full-compute activations. Taylor extrapolation degrades as the order and prediction horizon grow; the Chebyshev interpolant stays stable at higher orders, which the authors report translates into better quality at the same speedup (up to 3.68x latency and 5.12x FLOPs reduction on DiT-XL/2, PixArt-Sigma, and FLUX.1-dev, per the paper).
118+
119+
To enable Chebyshev Cache, create a [`ChebyshevCacheConfig`] and pass it to your pipeline's transformer:
120+
121+
- `cache_interval`: Number of steps to reuse cached outputs before performing a full forward pass
122+
- `disable_cache_before_step`: Initial steps that use full computations to gather the interpolation history window
123+
- `cheb_order`: Maximum number of full-compute activations kept in the rolling history window (the paper's `n`, defaults to `6`)
124+
125+
```python
126+
import torch
127+
from diffusers import FluxPipeline, ChebyshevCacheConfig
128+
129+
pipe = FluxPipeline.from_pretrained(
130+
"black-forest-labs/FLUX.1-dev",
131+
torch_dtype=torch.bfloat16,
132+
).to("cuda")
133+
134+
config = ChebyshevCacheConfig(
135+
cache_interval=5,
136+
cheb_order=6,
137+
disable_cache_before_step=3,
138+
cheb_factors_dtype=torch.float32,
139+
)
140+
pipe.transformer.enable_cache(config)
141+
```
142+
115143
## MagCache
116144

117145
[MagCache](https://github.com/Zehong-Ma/MagCache) accelerates inference by skipping transformer blocks based on the magnitude of the residual update. It observes that the magnitude of updates (Output - Input) decays predictably over the diffusion process. By accumulating an "error budget" based on pre-computed magnitude ratios, it dynamically decides when to skip computation and reuse the previous residual.

src/diffusers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
)
176176
_import_structure["hooks"].extend(
177177
[
178+
"ChebyshevCacheConfig",
178179
"FasterCacheConfig",
179180
"FirstBlockCacheConfig",
180181
"HookRegistry",
@@ -184,6 +185,7 @@
184185
"SmoothedEnergyGuidanceConfig",
185186
"TaylorSeerCacheConfig",
186187
"TextKVCacheConfig",
188+
"apply_chebyshev_cache",
187189
"apply_faster_cache",
188190
"apply_first_block_cache",
189191
"apply_layer_skip",
@@ -1036,6 +1038,7 @@
10361038
TangentialClassifierFreeGuidance,
10371039
)
10381040
from .hooks import (
1041+
ChebyshevCacheConfig,
10391042
FasterCacheConfig,
10401043
FirstBlockCacheConfig,
10411044
HookRegistry,
@@ -1045,6 +1048,7 @@
10451048
SmoothedEnergyGuidanceConfig,
10461049
TaylorSeerCacheConfig,
10471050
TextKVCacheConfig,
1051+
apply_chebyshev_cache,
10481052
apply_faster_cache,
10491053
apply_first_block_cache,
10501054
apply_layer_skip,

src/diffusers/hooks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717

1818
if is_torch_available():
19+
from .chebyshev_cache import ChebyshevCacheConfig, apply_chebyshev_cache
1920
from .context_parallel import apply_context_parallel
2021
from .faster_cache import FasterCacheConfig, apply_faster_cache
2122
from .first_block_cache import FirstBlockCacheConfig, apply_first_block_cache

0 commit comments

Comments
 (0)