技術約9分で読めます

Qwen-Image 2.1をM1 MaxでTaylorSeerキャッシュにかけたら1枚10分が3分半になった

いけさん目次

2026-09-22 追記: 表情だけ変える部分編集で位置が保てるかを測り、片方だけの着せ替えと背景の透過も試した → Qwen-Image 2.1の部分編集は、引数1つで位置ずれが約0.1pxになった

前回のQwen-Image 2.1のローカル版をAnimaと比べた記事では、M1 Max 64GBで832×1216の画像を1枚出すのに約10分かかった。参照画像を使う編集は約12分。
Animaのときは少ないステップで出せるTurbo LoRAがあったが、Qwen-Image 2.1向けのものは、9月21日に探した範囲では見つからなかった。モデルを学習し直さずに速くする方法を、生成に使っているDiffusersのスクリプトで試した。

検証環境

項目内容
マシンMacBook Pro、Apple M1 Max、メモリ64GB
OSmacOS 27.0
実行環境Python 3.12.12、PyTorch 2.14.0(MPS)、Diffusers 0.41.0.dev0(コミット 80c7ed26
モデルQwen-Image 2.1(BF16、ModelScopeから取得した公開重み)
共通の生成条件832×1216、40ステップ、シード42、CFG1.0、ネガティブプロンプトなし
比較対象キャッシュなしの生成(前回の記事の出力と計測値をそのまま使った)

時間は前回と同じく、プロンプトの読み取りから画像への復元までの生成処理を測り、モデルの読み込みは含めていない。各条件1回ずつ計測した。

Qwen-Image 2.1向けの高速化LoRAの有無

旧Qwen-Imageには、4ステップや8ステップで生成できるようにするLightningというLoRAがある。
Qwen-Image 2.1はTransformerの構造が変わっていて、旧版は画像側と文章側を別々に処理する2系統の60層、2.1は1系統の32層になった。層の数も幅も違うので、旧版のLoRAは使えない。

9月21日にLightningの配布元、Hugging Face、ModelScopeを探したが、2.1向けの高速化LoRAや蒸留版は見つからなかった。
Lightningのリポジトリには、2.1への対応予定を尋ねるissue #90が9月21日に立っていて、9月22日の時点で返信はない。

学習なしで使える特徴キャッシュ

9月21日に、Qwen-Image 2.1専用のComfyUIノードComfyui-Spectrum-Qwen2.1が公開された。
拡散モデルは、ステップが進んでもTransformerの内部の値が少しずつしか変わらない。このノードは、数ステップだけ実際に計算し、残りのステップの値は多項式で予測してTransformer本体を飛ばす。元はSpectrumという論文の手法で、モデルの学習はいらない。

生成はComfyUIではなくDiffusersのスクリプトで動かしている。
Diffusers本体にも同じ種類の特徴キャッシュがいくつか入っていて、Qwen-Image 2.1のTransformerは enable_cache() を呼べる作りになっていた。今回はその中のTaylorSeerを使った。前のステップとの差分から、次のステップの値をテイラー展開で予測する。

TaylorSeer Liteの設定

TaylorSeerには、Transformerのブロックを全部飛ばして最後の出力層の値だけを予測するLiteモードがある。Diffusersの説明には、メモリの使用を抑えた軽量版と書いてある。今回はこちらを使った。

設定意味
use_lite_modeTrue予測するステップでは32ブロックを全部飛ばし、出力層の値を予測で埋める
disable_cache_before_step3最初の3ステップは必ず実際に計算する
cache_interval3そのあとは3ステップごとに1回、実際に計算する
max_order11次の差分まで使って予測する

40ステップのうち、実際にTransformerを通すのは、最初の3ステップと、そのあとの5・8・11…38ステップ目の12回で、計15回になる。

from diffusers import TaylorSeerCacheConfig

pipe.transformer.enable_cache(
    TaylorSeerCacheConfig(
        cache_interval=3,
        disable_cache_before_step=3,
        max_order=1,
        use_lite_mode=True,
    )
)

パイプラインの読み込みと生成の呼び出しは、前回の記事のコードと同じ。

KVキャッシュとの併用で出たエラーと回避策

そのまま実行すると、2ステップ目で次のエラーが出て止まった。

RuntimeError: The size of tensor a (3952) must match the size of tensor b (4075) at non-singleton dimension 1

Qwen-Image 2.1のパイプラインは、既定でKVキャッシュ(use_kv_cache=True)が有効になっている。
文章と参照画像の部分はステップが進んでも計算結果が変わらないので、1ステップ目で求めた値を保存し、2ステップ目以降は生成する画像の部分だけを計算する。
同じキャッシュという名前でも、KVキャッシュは変わらない値をそのまま使い回すもので、値を予測で埋めるTaylorSeerとは別物。

このため、Transformerの出力の長さが1ステップ目とそれ以降で変わる。前回の記事と同じ俯瞰のプロンプト(832×1216)では、1ステップ目が文章側の123トークンと画像の3952トークンを合わせた4075トークン、2ステップ目以降は画像の3952トークンだけになった。
TaylorSeerは前後のステップの出力の差を取るので、長さが合わずにエラーになっていた。

use_kv_cache=False にすれば動いたが、参照画像を使う編集では参照画像の部分も毎ステップ計算し直すため、実計算1回が17.4秒から31.6秒に増えた。

回避策として、出力の形が変わったら前の値を捨てて、そのステップを初回として扱うようにした。Diffusers本体には手を入れずに、スクリプトの中で TaylorSeerState.update を実行時に差し替えている(モンキーパッチ)。

import diffusers.hooks.taylorseer_cache as ts

_orig_update = ts.TaylorSeerState.update

def _update(self, outputs):
    if not self.is_inactive and self.last_update_step is not None:
        for i, feat in enumerate(outputs):
            prev = self.taylor_factors.get(i, {}).get(0)
            if prev is not None and prev.shape != feat.shape:
                self.taylor_factors = {}
                self.last_update_step = None
                break
    return _orig_update(self, outputs)

ts.TaylorSeerState.update = _update

最初の3ステップは必ず実際に計算する設定なので、1ステップ目を捨てても、2ステップ目と3ステップ目の差分から予測を始められる。確かめたのは、この最初の3ステップを実計算にする設定だけ。
回避策を入れた出力は、use_kv_cache=False にしたTaylorSeerの出力と比べて、画素の平均差(RGB値の差の絶対値の平均、0〜255)が0.0だった。文章だけからの生成でも参照画像を使う編集でも同じで、回避策を入れても出力は変わらなかった。

Diffusersにはissue #14829として再現手順とこの回避策を報告し、メンテナからPRを出してほしいと返信があったので、同じ内容をPR #14831として出した。まだ取り込まれていないので、回避策を入れた生成スクリプトをLiltingChannelLaboに置いた。

俯瞰の生成とかなちゃんの編集での比較

前回の記事と同じ、文章だけから出す俯瞰の画像(表ではT2I)と、オリキャラのかなちゃんの立ち絵を俯瞰へ変える編集の2つで比べた。プロンプトと参照画像は前回と同じ。

ケース条件生成処理実計算実計算1回の平均GPU側の確保メモリ・最大
T2I 俯瞰キャッシュなし614.7秒40回15.1秒約45.8GB
T2I 俯瞰TaylorSeer、KVキャッシュなし224.6秒15回14.5秒約45.8GB
T2I 俯瞰TaylorSeer+KVキャッシュ218.8秒15回14.2秒約45.8GB
編集 かなちゃん俯瞰キャッシュなし721.2秒40回17.4秒約49.0GB
編集 かなちゃん俯瞰TaylorSeer、KVキャッシュなし484.4秒15回31.6秒約47.0GB
編集 かなちゃん俯瞰TaylorSeer+KVキャッシュ277.6秒15回16.8秒約48.4GB

予測で済ませたステップは1回0.1秒もかからないので、生成処理の時間はほぼ実計算の回数で決まった。
T2Iは614.7秒から218.8秒で約2.8倍。編集は、KVキャッシュを切ると484.4秒で約1.5倍どまりだったが、回避策でKVキャッシュを残すと277.6秒で約2.6倍になった。メモリの最大値はキャッシュなしとほぼ同じだった。

T2I キャッシュなし
614.7秒
キャッシュなしで生成した、金髪の人物が上を見上げる俯瞰画像。リボンはストライプ柄
T2I TaylorSeer+KVキャッシュ
218.8秒
TaylorSeerキャッシュで生成した同じ俯瞰画像。リボンがチェック柄に変わっている
編集 キャッシュなし
721.2秒
キャッシュなしでかなちゃんの立ち絵を俯瞰へ編集した画像
編集 TaylorSeer+KVキャッシュ
277.6秒
TaylorSeerキャッシュでかなちゃんの立ち絵を俯瞰へ編集した画像
ケースキャッシュなしとの違い
T2I 俯瞰構図、人物、影の向きは同じ。リボンの柄がストライプからチェックに変わった
編集 かなちゃん俯瞰サイドポニー、青いシュシュ、制服、足元の影までほぼ同じ

立ち絵・走る場面・真後ろでの比較

前回の記事でキャッシュなしの出力がある3条件も、同じTaylorSeerの設定で生成した。ローブの立ち絵と走る場面は文章だけからの生成、真後ろはかなちゃんの立ち絵を参照画像にした編集。

ケースキャッシュなしTaylorSeer+KVキャッシュ倍率画素の平均差(0〜255)
T2I ローブの立ち絵588.2秒216.4秒2.72倍2.76
T2I 走る場面584.1秒218.0秒2.68倍5.98
編集 かなちゃん真後ろ722.0秒280.2秒2.58倍0.62
立ち絵 キャッシュなし
588.2秒
キャッシュなしで生成した白と金のローブの立ち絵
立ち絵 TaylorSeer
216.4秒
TaylorSeerキャッシュで生成した白と金のローブの立ち絵
走る場面 キャッシュなし
584.1秒
キャッシュなしで生成した、夕暮れの城を背にした人物
走る場面 TaylorSeer
218.0秒
TaylorSeerキャッシュで生成した、夕暮れの城を背にした人物
真後ろ キャッシュなし
722.0秒
キャッシュなしで真後ろ向きに編集したかなちゃん。サイドポニーは画面左
真後ろ TaylorSeer
280.2秒
TaylorSeerキャッシュで真後ろ向きに編集したかなちゃん。サイドポニーは画面左

5条件とも約2.6〜2.8倍になった。構図、ポーズ、衣装、キャラの特徴が変わった条件はなく、違いは俯瞰のリボンの柄のような細部だけだった。
画素の平均差は、真後ろの0.62から走る場面の5.98まで開きがある。差がいちばん大きい走る場面でも、人物と構図は同じだった。

実計算の間隔を5と8に広げた場合

俯瞰のT2Iで、cache_interval を3から5と8に変えて、実計算の回数をさらに減らした。

cache_interval実計算生成処理倍率画素の平均差(0〜255)
キャッシュなし40回614.7秒
315回218.8秒2.81倍4.73
511回161.0秒3.82倍7.21
88回118.9秒5.17倍13.23
キャッシュなし
614.7秒
キャッシュなしの俯瞰画像
間隔3
218.8秒
間隔3の俯瞰画像
間隔5
161.0秒
間隔5の俯瞰画像。リボンが無地になった
間隔8
118.9秒
間隔8の俯瞰画像。リボンが無地で、全体に柔らかい

顔のまわりを同じ位置で切り出した。

キャッシュなし
キャッシュなしの顔まわりの切り出し
間隔3
間隔3の顔まわりの切り出し
間隔5
間隔5の顔まわりの切り出し
間隔8
間隔8の顔まわりの切り出し。髪の細い線が減っている
cache_intervalキャッシュなしとの違い
3リボンの柄がストライプからチェックに変わった。線の細かさは同じくらい
5リボンが無地になった
8リボンは無地。髪の細い線が減り、全体に柔らかい

間隔8でも構図と人物は崩れなかったが、絵は柔らかくなった。
Comfyui-Spectrum-Qwen2.1の作者もREADMEで、出力がベースより柔らかくなると書いている。

関連記事

Qwen-Image 2.1を試した記事。

参考リンク