品質低下なしでサブ秒の Ideogram v4 を提供
fal.ai は FP4 量子化、演算融合、およびステップ削減技術により、Ideogram V4 の生成時間を 2.75 秒から 0.44 秒へ短縮し、6 倍の高速化を実現した。
キーポイント
FP4 量子化とハードウェア活用
Blackwell アーキテクチャ上で NVFP4(4 ビット浮動小数点)形式をネイティブに実行し、メモリフットプリントを大幅に削減して並列処理能力を高めた。
エピローグ演算の融合
GEMM 演算後の RMSNorm や gated-SiLU などの小規模演算を統合し、HBM(高帯域メモリ)への不要なデータ転送を防ぐことで、量子化による速度向上効果を最大化した。
品質維持のための蒸留技術
4 ビット化で生じる画質低下を解消するため、Quantization Aware Distillation(量子化認識蒸留)と Timestep Distillation(時間ステップ蒸留)を採用し、低ビットでも高解像度・高品質を維持した。
推論コストの劇的削減
クラスフリーガイダンスのブランチ統合とデノising ステップ数の削減により、必要なフォワードパス数を大幅に減らし、計算コストを最小化した。
Epilogue Fusion の基本原理とメリット
GEMM の直後に計算される演算(Epilogue)を融合させることで、中間結果の HBM への書き戻しと再読み込みを不要にし、メモリ転送コストとカーネル起動オーバーヘッドを削減します。
Blackwell アーキテクチャにおけるデータ処理単位
GEMM の CTA タイルは Epilogue 処理時にさらに細分化され、各スレッドが保持するフラグメント(Fragment)のサイズが RMSNorm の融合可能性を決定づけます。
head_dim=256 における融合の難易度
Ideogram v4 の head_dim=256 は単一のフラグメント(128要素)に収まらないため、RMSNorm に必要な全要素の乗算和計算が複数回の訪問やスレッド間通信を必要とし、単純な融合が困難になります。
影響分析・編集コメントを表示
影響分析
この技術的進歩は、高品質な画像生成 AI をリアルタイムアプリケーションやインタラクティブなワークフローに組み込む際の主要な障壁であった「遅延」と「コスト」を同時に解決する画期的な成果です。特に Blackwell アーキテクチャを活用した FP4 推論と、メモリ効率を最大化する演算融合の組み合わせは、業界全体における生成 AI のインフラ最適化の新たな基準を示すものであり、大規模モデルの実時間利用を加速させるでしょう。
編集コメント
生成 AI の実用化において最も課題となる「遅延」と「コスト」を、ハードウェア特性とアルゴリズム最適化の両面から解決した事例として極めて重要です。特にメモリ転送の削減に焦点を当てたアプローチは、今後の推論エンジン設計における重要な指針となります。
image @fal では、Ideogram V4 を高速で提供しています。1K 解像度において、従来 2.75 秒かかっていた生成が、現在は 0.44 秒でレンダリングされ、可視的な画質の低下なしに 6 倍の速度向上を実現しました。これは、考えられるあらゆる軸で画像生成コストを削減することで達成されたものであり、そのコストは「拡散トランスフォーマー(diffusion transformer)が実行するフォワードパスの数」×「各パスのコスト」という一つの式として捉えることができます。
Contents
FP4 とシステムレベルの最適化
量子化認識蒸留(Quantization Aware Distillation)
タイムステップ蒸留(Timestep Distillation)
image 二つの戦線があります。まず、各フォワードパスを安価にすることです:トランスフォーマーを FP4 で実行し、行列乗算(matmul)の周囲にある小さな演算を融合させて、メモリの往復コストを支払わないようにします。次に、パス数を減らすことです:二つのブランチから分類器フリーガイダンス(classifier-free guidance)を一つのブランチに蒸留し、多数のノイズ除去ステップを手数少ないステップに圧縮します。これらをすべて積み重ねれば、計算資源のわずかな一部で、完全な bf16 モデルと同じ画像を得ることができます。
この投稿では、私たちが構築した順序に沿って、全体の流れを概説します。まず、高速化を実現するカーネル融合を伴う FP4 順伝播から始め、次に 4 ビットが引き起こした品質問題と、それを解決した量子化意識型蒸留について述べます。最後に、ステップ数を劇的に削減したタイムステップ蒸留について解説します。FP4 は確かに高速ですが、素朴な実装の FP4 は bf16 と比較して視覚的に明らかに劣っており、さらにその場合でも多くのステップを要し、ガイダンスブランチも 2 つ実行する必要がありました。ここでは、これらすべての課題をどのように克服したかを示します。
FP4 でトランスフォーマーを実行する
最速の推論パスは、拡散トランスフォーマーを NVFP4 で実行するものです。FP4 は 4 ビット浮動小数点形式であり、1 ビットの符号ビット、2 ビットの指数部、1 ビットの仮数部から構成されるため、各値は限られたレベルのいずれかしか取り得ません。その代わり、意味のある速度向上と大幅なメモリフットプリントの削減が得られ、これによりより多くの並行処理を可能にし、画像あたりのレイテンシを低減できます。Blackwell アーキテクチャではこの形式がハードウェアでサポートされているため、行列乗算(matmul)はネイティブに 4 ビットで実行されます。
しかし、行列乗算が実際の時間消費の大部分を占める場合にのみ、4 ビットの行列乗算は効果を発揮します。拡散トランスフォーマーでは、必ずしもそうとは限りません。大きな GEMM(汎用行列乗算)の直後には、アテンションパス上の RMSNorm や FFN 内のゲート付き SiLU といった小さな演算が常に続きます。これらを融合しない場合、その小さな演算によって GEMM の出力全体を HBM(ハイバンド幅メモリ)へ書き出し、再び読み出す必要が生じます。行列乗算のコストは下がりましたが、除去しなかったメモリの往復転送が、そのメリットを相殺してしまいます。したがって、高速な FP4 パスにとって最初の課題は、これらの演算を GEMM 自体に融合させることです。
エピローグの融合により FP4 の効果を最大化する
エプログの融合について簡潔に
GEMM は、A @ B をタイル単位で生成します。各タイル内部では、Tensor Cores が乗算加算をストリーミングし、それを SM 自身のストレージ(Hopper ではレジスタ、Blackwell では TMEM)に格納されたアキュムレータへ蓄積していきます。このメインループが実行されている間、出力に対して HBM はまだ一切アクセスされておらず、答えはオンチップ上で集約されています。
しかし、A @ B がレイヤーの最終段階となることはほとんどありません。拡散トランスフォーマーでは、FFN は通常、GEMM に続くゲート付き SiLU(SwiGLU)で構成され、アテンション経路は GEMM に続く RMSNorm で構成されます。このパターンは LLM や DiT 全体に共通しており、「大きな GEMM の後に小さな後処理演算」が繰り返されます。演算自体は軽量ですが、問題は融合されていない場合、GEMM がタイル全体を HBM に書き出し、次のカーネルがそれを再度読み込んで演算を適用し、再び書き出すという点にあります。わずかな FLOPs(浮動小数点演算)が、フルなメモリ往復と第 2 のカーネル起動に繋がってしまいます。
エピローグ融合(Epilogue fusion)とは、GEMM の直後に、結果を HBM に保存する前に適用される演算のことです。出力はグローバルメモリへ一度だけ書き出され、すでに変換済みであり、第 2 のカーネルも不要で、メモリ往復も発生しません。Hopper および Blackwell では、エピローグ処理を次のタイルのメインループと重ねて実行できるため、小規模な演算においては追加計算コストはほぼ隠蔽されます。
CUTLASS はこれを Epilogue Visitor Trees (EVT) を通じて公開しています。各アキュムレータ断片に対して実行される visit() 関数を書き、集合的なエピローグがロードとストアを処理します。このメカニズムは Colfax の EVT に関する記事で詳しく説明されています。簡潔に言えば、ポイントワイズ融合(バイアス、ReLU、キャスト)はきれいに実装されます:各出力要素は独立しており、跨るべき縮約要素はなく、CUTLASS はこれら用の事前構築済みビジターを備えています。
本稿では RMSNorm の融合に焦点を当てます。これはベクトルをその自身の RMS で正規化します:y = x · rsqrt(mean(x²) + eps) · weight。ポイントワイズ演算とは異なり、全体ヘッドにわたる rstd が必要であり、これは縮約処理です。この縮約が融合を困難にする要因ですが、なぜそうなるのかを理解するには、エピローグが実際にはデータをどのように認識しているかを検討する必要があります。
Blackwell におけるタイルと断片
GEMM は CTA タイル単位で動作します:Blackwell (SM100) では、当社の NVFP4 GEMM は 256×256 の CTA タイルを使用します。エピローグはこの全体を一度に処理するのではなく、より小さな 128×128 のエピローグタイルで順次走査し、CTA あたり 4 つのエピローグサブタイルを提供します:
各エピローグのサブタイルは128スレッドに分割されており、各スレッドは128要素の断片(FragmentSize = 128)を保持します。visit() コールバックは一度に一つの断片を受け取り、そのスレッドが責任を持つ128値となります。これがエピローグが扱う作業単位であり、何と融合できるかに影響します。
これにより、head_dim=128 の RMSNorm 融合は極めて容易になります:128要素からなるヘッドはまさに一つのスレッドの断片に相当します。visit() が呼び出された際、そのスレッドはすでに自身のレジスタ内に一つのヘッドの全128値を保持しています。RMSNorm を計算するには、これら128値に対して平方和を求め(スレッド間でのシャッフルも不要、二番目の断片も不要、追加パスも不要)、スケーリングして保存します。この集約は単一の visit() 呼び出しで完結します。
Ideogram v4 はそれほど都合よくできていません。そのアテンションでは head_dim=256 を使用するため、一つのヘッドはもはや一つの断片に収まらず、二つの断片にまたがることになります。そのため、きれいな単一 visit() での集約が崩れてしまいます。
なぜ head_dim=256 が難しいのか
これが問題となる理由を理解するには、エピローグの実行方法を見てみましょう。メインループの後、CUTLASS の集合型エピローグは CTA タイルの4つのサブタイルを固定された順序(Tile 0 → Tile 1 → Tile 2 → Tile 3)でループ処理します。各サブタイルに対して、断片ごとに visit() を一度呼び出し、結果を保存して次に進みます。これは単一の順方向パスであり、以前のサブタイルに戻ることはできません。
RMSNorm では、ヘッド全体に対する rstd(標準化された逆平方根)が必要であり、これは rstd = rsqrt(mean(x²) + eps) という計算で求められます。その後、y = x * rstd * weight となります。これを計算するには両方の半分における二乗和が必要ですが、それらは別々の visit() コールで到着します:最初の半分は Tile 0 で、2 番目の半分は Tile 1 です。Tile 1 を見て rstd を計算できる頃には、Tile 0 はすでに訪問されて保存されています。後から戻って修正する方法はありません。このセクションの残りは、それでもどうやって融合したかを示すものであり、最初の半分をレジスタ内に保持する 2 パスの再訪スケジュールです。
再訪融合
CUTLASS のエピローグ集合体は、固定された単一パスのループ順序で動作します:各サブタイルを一度だけ訪問し、保存して次に進みます。これは私たちの問題を解決しません:Tile 0 を正規化するには Tile 1 を先に確認する必要があるため、戻る必要があります。そこで私たちは、標準的な単一の前方パスではなく、減少計算が必要とする順序(すでに訪れたタイルに戻ってくることも含む)で訪問者がサブタイルを歩くことを可能にするエピローグを作成しました。
各行グループごとのスケジュールは 3 回のタッチです:

Tile 0、パス 0:これはヘッドの最初の半分です。その二乗和を計算して次に進みますが、2 番目の半分がない限り正規化はまだできません。
タイル 1、パス 1:頭の後半が到着します。これで両方の半分が揃いましたので、この半分の二乗和を計算し、最初の半分に加算して rstd を取得します。これを正規化して、この半分を保存します。
タイル 0、パス 2(再訪):rstd が既知になったので、タイル 0 に戻ってそれをスケーリングします。これはシングルパスのエピローグでは行えない追加の手順です。
すべての処理がオンチップ内で完結します。グローバルメモリへの往復も、第 2 のカーネル実行も不要です。カスタムエピローグが 3 回のタッチスケジュールを歩き回り、ビジターがそのタッチ間で集約状態を引き継ぎ、rstd が準備できたらスケーリングを適用します。これにより、HBM に到達する前に正規化が融合されます。
ゲート付き SiLU (SwiGLU) の融合
拡散トランスフォーマー(および LLaMA などの大規模言語モデル)でよく見られる別の一般的なパターンに、ゲート付き SiLU があります。1 つの GEMM で出力 C を生成した後、レイヤーはそれを半分に分けて乗算することで完了します。
C = A @ B
output = SiLU(C[:, :N//2]) * C[:, N//2:]
これにより出力幅が半分に削減され、2 つの半分が 1 つになります。RMSNorm の場合と同じく、計算自体は軽量ですが、融合されない第 2 のカーネルでは HBM への往復を完全に支払う必要があります。CUTLASS にはこの「ペア + N/2 の集約」パターン用の組み込みエピローグがないため、これを融合するにはカスタムビジターが必要です。
課題はレイアウトにあります。出力列 n に対して、対応するペアは gate = C[:, n] と up = C[:, n + N/2] であり、これらは N の半分の距離だけ離れています。これらの 2 つの値は、異なるフラグメントやサブタイル、さらには異なる CTAs に配置される可能性が十分にあります。タイル間での同期を行うと、エピローグのパラレルモデルが破綻してしまいます。
代わりに、重みパッキング時に B の列を一度並べ替えます。GEMM はその後、2 つの分離した半分ではなく、インターリーブされたペア [gate0, up0, gate1, up1, ...] を生成します。隣接性が保証されるため、カスタム EVT ビジターが同じフラグメント内の隣接要素に対して SiLU と乗算を適用し、縮小された結果を保存します。これは 1 つのカーネルで完結し、タイル間の通信は不要です。
重みパッキング、ビジターのフック、EVT の配線に関する完全なウォークスルーは、「Crafting Efficient Kernels with Epilogue Fusion」に記載されています。
imageOG 重みは FP4 量子化では高品質な画像を提供できない
落とし穴:FP4 は画質にコストを強いる
エピローグの融合により、高速な FP4 フォワードパスが実現します。しかし、それ単独では画質が悪化する結果にもなります。標準的な FP4 推論は bf16 に比べて視覚的に劣っており、特に色再現において顕著でした。赤色が褪せ、彩度が低下し、ロゴや製品、植物など色彩に依存するプロンプトでは、bf16 の参照画像と比較して無彩色のように見えました。
これらの値ごとの数レベルは、各トークンが大きな平均値への小さな寄与に過ぎない大規模言語モデルにとっては問題ありません。しかし、拡散トランスフォーマーはより寛容ではありません。なぜなら、それは潜在空間に対して多数のステップで反復的にノイズ除去を行うためであり、各ステップの出力が次のステップの入力となるからです。あるステップでの速度がわずかに間違っていると、次のステップでは誤った潜在値となり、さらにその次のステップでは誤った入力としてフィードバックされます。このエラーは雪だるま式に膨らんでいきます。FP4 では、この累積効果が彩度の低下として現れました:モデルは正しい構成を生成していましたが、bf16 モデルと比較して色が平坦で鮮やかさに欠ける結果となりました。
ポストプロセッシングのトリック(輝度補正、色の再バランス調整など)は本質的な解決にはなりませんでした。なぜなら、エラーはデコード前の潜在値に組み込まれてしまっており、単なる表示空間の問題ではないからです。私たちはモデルの外側ではなく、内部でこれを修正する必要がありました。
何が機能しなかったか
最終的なレシピにたどり着く前に、いくつかの試行を行いました。これらは、何が重要で何がそうでないかを教えてくれました:
出力に対するポストプロセッシング。最初の直感は、生成後に色を修正することでした。デコードされた画像に対して彩度補正、輝度調整、色の再バランス調整を試みました。しかし、どれも機能しませんでした。彩度の低下は表示の問題ではなく、潜在空間の軌道における問題です。修正すべきピクセルが得られる頃には、ノイズ除去器は何回かの反復でわずかに間違ったステップを踏んでおり、色情報はすでに失われています。最終画像からそれを元に戻すことはできません。
素朴な蒸留。最も単純な方法を試しました:学生モデルを FP4 に量子化し、同じ入力に対して bf16 の教師モデルを実行して、両者の予測間の MSE を最小化します。しかし、量子化プロセスを通じて勾配は流れず、学生モデルは量子化された状態で凍結され、単に教師モデルと比較されるだけでした。損失は低下しましたが、画像の品質は向上しませんでした。これにより、我々は苦い経験から、拡散モデルの学習損失が画像の品質を予測できないことを学びました。損失値が数千ステップにわたって低下しても、出力はほとんど変化しないのです。
これらの結果から、2 つの重要な教訓を得ました:量子化誤差はトレーニング終了後ではなく、トレーニング中に解決する必要があること、そして最終的な評価は数値的ではなく視覚的に行うべきことです。
量子化意識型蒸留 (Quantization-aware distillation)
我々がこれに苦闘していた頃、LLM や VLM 向けに「量子化意識型蒸留 (QAD)」という手法が論文として発表されました(paper)。後から見れば単純なアイデアです:モデルを単に量子化して運命に任せるのではなく、完全精度の教師モデルから量子化された学生モデルへと知識を移すのです。学生モデルは、自身の量子化ノイズという制約下で、教師モデルが予測したであろう内容を予測することを学びます。教師モデルは凍結された bf16 状態のまま、学生モデルは量子化され、学生モデルの出力が教師モデルの出力に一致するようにトレーニングを行います。
その論文では、LLM に関するものであったため、ロジットに対して KL 発散を用いました。私たちはトークンを予測しているのではなく、潜在空間における速度場を予測しています。そのため損失関数を交換しました:学生モデルの速度予測と教師モデルの速度予測との間の MSE です。精神は同じですが、目的は異なります。
以前試した単純な知識蒸留との決定的な違いは、QAD では学生モデルが単に FP4 で実行されて教師と比較されるだけではない点です。学生モデルは自身の量子化を通じて学習します。勾配は直線推定器(straight-through estimator)を介して FP4 の丸め処理を通って逆伝播するため、重みは自身が生成する量子化ノイズに対して積極的に適応します。これが本質的なポイントです — 重みは、4 ビットに丸められても出力が損なわれないパラメータ空間の領域へと移動します。単純な知識蒸留では、固定された量子化状態の学生モデルを教師と比較して期待するだけですが、QAD では量子化自体を最適化の一部として組み込みます。
順伝播では、学生モデルのbf16重みと活性化値は行列積のためにFP4に量子化され、その後再び非量子化されます。この往復処理によって出力には実際の量子化ノイズが含まれます。凍結されたbf16教師モデルが目標速度を生成します。損失関数は、学生モデルのノイズ付き予測と教師モデルのクリーンな目標との間のMSE(平均二乗誤差)です。
逆伝播では、FP4の丸め処理は自動微分グラフから切り離されます。丸め関数の勾配はほぼ全域でゼロとなるため直接微分できず、Straight-Through Estimator (STE) がこれを迂回して、勾配を完全精度のまま流すようにします。順伝播では量子化ノイズが伝搬しますが、逆伝播ではこれが無視されます。学習が進むにつれて重みは、4ビットへの丸めでも出力が劣化しないパラメータ空間の領域に収束していきます。
推論時にはこれらの処理は一切不要です。学習済みの重みをエクスポートし、本番環境のパイプラインに読み込み、実際のカーネルを用いて静的なFP4量子化を適用して画像を生成します。学生モデルは、ガイダンス係数 = 1 の条件付き分岐としてのみ動作します。教師モデルもCFG(Classifier-Free Guidance)もSTEも使用しません。
imageCFGを一つの順伝播に統合
Classifier-free guidance (CFG) は、拡散モデルがプロンプトに向かって誘導する仕組みです。推論時には、各ステップごとにトランスフォーマーを 2 回実行します:1 回はプロンプトに条件付けされたもの(条件付き)、もう 1 回はテキストなしのもの(非条件付き)。これは、すべてのノイズ除去ステップで 2 倍のコストがかかります。私たちはこれを蒸留に統合して、無料で実現できることに気づきました。
トレーニング中、凍結された bf16 の教師モデルは両方のブランチを実行し、ガイド付きの CFG ベロシティを生成します。学生モデルは条件付きブランチのみであり、そのガイド付きベロシティを直接予測するように訓練されます。推論時には、 trained conditional branch だけを guidance = 1 で実行し、2 回のフォワードパスではなく 1 回で済ませます。つまり、FP4 の高速化に加えて、CFG をサービングパスから完全に削除することでトランスフォーマーのコストを半減させました。
これは QAD 論文を超える部分です。その論文は量子化精度の回復にとどまりますが、私たちは量子化精度を回復し、さらに CFG ブランチを蒸留で除去します。その結果、最終的な推論グラフはより小さく(fp4)、かつ短くなります(1 つのブランチ)。
QAD が機能した理由
QAD を実際に視覚品質を回復させるためには、コアとなるレシピBeyond いくつかの判断が必要でした。特に重要だったのは以下の点です:
損失の組み合わせ。リアルデータの品質を活用するために、flow-matching loss と蒸留損失を組み合わせて使用しています。純粋な蒸留だけでは、望む完全な視覚品質を回復するには不十分でした。
ガイダンススケジュール。教師ターゲットに対して固定された単一のガイダンスは使用しませんでした。ノイジーな中間タイムステップでは高いガイダンスを、クリーンな終端付近では低いガイダンスを使用しました。これは、モデルが初期段階で強い誘導を受け、収束するにつれてより穏やかな誘導を受けることで利益を得るためです。
QAD による品質回復と CFG の単一ブランチへの統合により、コスト乗数のうち 2 つは処理されました。残りの 1 つが最大の課題となります。
imageQAD は量子化誤差を回復し、同様の出力を提供します
タイムステップの蒸留
これで各フォワードパスは可能な限り安価になりました:FP4 での 4 ビット行列乗算、エピローグの融合によるノルムと活性化値の往復コストの排除、そして 2 つのガイダンスブランチに代わる単一のブランチです。まだ手をつけていないのは、フォワードパスの数、すなわちディノイジングステップ数であり、これが最大のレバー(調整要素)となります。
拡散トランスフォーマーは、多くの反復を通じて潜在をディノイズすることで品質を獲得し、各反復が完全なフォワードパスです。もしモデルに少数のステップで同じ作業を行わせることを教えられれば、レイテンシとスループットにおける最大の要因を縮小できます。これがタイムステップの蒸留です。また、これはパイプラインの中で最も多くの要素を持ち、文献も最も多く、画像品質を静かに損なう方法が最も多い部分でもあります。
基盤:分布マッチング蒸留
自然な最初のアイデアは回帰です:教師モデルを実行し、生徒モデルを実行して、両者の間の MSE(平均二乗誤差)を最小化します。上記の QAD 作業がまさにこれを行っており、生徒と教師が同じ経路をたどる場合には機能します。しかし、数ステップで完了する生徒は、多数のステップを要する教師と同じ経路をたどりません。生徒に中間的な潜在変数を一致させるよう求めることはできません。なぜなら、生徒はそれらのほとんどをスキップしているからです。回帰という手法は、生徒が自由に離れるべき軌道に生徒を固定してしまいます。
分布マッチング蒸留(DMD)は目標を再定義します:サンプルごとに教師と一致させるのではなく、分布同士を一致させます。特定の経路上で生徒が何を行うかは問題ではなく、生徒が生み出す画像の雲が、教師が生み出す画像の雲のように見えるかを重視します。
このメカニズムでは 2 つのスコアネットワークを保持します:1 つはリアルなスコア(凍結された教師)であり、もう 1 つは偽物のスコア(生徒の現在の分布を追跡する第 2 のネットワーク)です。生徒を更新するために、DMD は生徒サンプルを取得し、それを再ノイズ化して、両方のスコアネットワークに進行方向を問います。2 つのスコアの差は、生徒の分布を教師の分布へと移動させる勾配方向となります。このスコアの差分は、定数倍を除けば、そのノイズレベルにおける 2 つの分布間の KL 発散(Kullback-Leibler divergence)の勾配です。ペアされたターゲットも、特定の潜在変数に対する MSE も不要です。必要なのは「これらの 2 つの分布を一致させる」という指示だけです。
imageDMD2: 実際に出荷されているバージョン
DMD2 は、バニラの DMD を整理し、現代の少ステップ画像モデルが派生するレシピへと昇華させました。具体的には、回帰項を削除してデータフリーな手法とし、二時間スケール更新を採用して学生モデルの変化に伴い偽スコアを正確に保ち、GAN 損失を二次的なシャープニング項として追加し、さらに多ステップサンプリングをサポートして学生モデルが単一のステップ数に縛られないようにしています。
それぞれ異なるレバーを追う一連のファミリー
DMD2 は一連の後継作の始まりとなりました。最適な選択は、何を最適化するかによって異なります。DMDR、Flash-DMD、AdvDMD、GNDM は強化学習と報酬信号を蒸留に組み込みます。DP-DMD は多様性を維持します。AMD は安定性を向上させます。
原文を表示
imageAt @fal we serve Ideogram V4 at high speed. At 1K resolution, a generation that used to take 2.75 s now renders in 0.44 s, a 6x speedup with no visible quality loss. We got there by pushing the cost of a generated image down on every axis we could find, and it helps to see those axes as one equation: the cost of an image is how many forward passes the diffusion transformer runs, times how expensive each pass is.
Contents
FP4 and System Level Optimizations
Quantization Aware Distillation
Timestep Distillation
imageTwo fronts. Make each forward pass cheap: run the transformer in FP4, and fuse the small ops around each matmul so they don't pay a memory round-trip. Make the passes fewer: distill classifier-free guidance from two branches into one, and distill the many denoising steps into a handful. Stack all of it and you land on the same image as the full bf16 model at a fraction of the compute.
This post walks the whole path, roughly in the order we built it. We start with the FP4 forward pass and the kernel fusion that makes it fast, then the quality problem 4-bit caused and the quantization-aware distillation that fixed it, and finally the timestep distillation that collapsed the step count. FP4 is fast, but naive FP4 looked visibly worse than bf16, and even then it still ran many steps times two guidance branches. Here is how we closed all of that.
Running the transformer in FP4
The fastest inference path runs the diffusion transformer in NVFP4. FP4 is a 4-bit floating point format: one sign bit, two exponent bits, one mantissa bit, so each value can only take one of a handful of levels. In exchange you get a meaningful speedup and a much smaller memory footprint, which lets us push more concurrency and lower latency per image. On Blackwell the format is supported in hardware, so the matmuls run natively in 4-bit.
But a 4-bit matmul only pays off if the matmul is where the time actually goes, and in a diffusion transformer it often isn't the whole story. Every big GEMM is chased by a small op: RMSNorm on the attention path, gated-SiLU in the FFN. Unfused, that small op forces the GEMM's entire output out to HBM and back. The matmul got cheaper, and then a memory round-trip you didn't remove eats the win. So the first thing a fast FP4 path needs is to fuse those ops into the GEMM itself.
Fusing the epilogue so FP4 pays off
Epilogue fusion, briefly
A GEMM produces A @ B in tiles. Inside each tile, Tensor Cores stream multiply-adds into an accumulator that lives in the SM's own storage: registers on Hopper, TMEM on Blackwell. While that mainloop runs, nothing has touched HBM for the output yet; the answer is being accumulated on-chip.
But A @ B is almost never the end of the layer. In a diffusion transformer the FFN is typically a GEMM followed by a gated-SiLU (SwiGLU), and the attention path by a GEMM followed by RMSNorm. The pattern repeats across LLMs and DiTs: a big GEMM, then a small post-op. The op itself is cheap; the problem is that unfused, the GEMM writes the whole tile to HBM, a second kernel loads it back, applies the op, and writes again. A few FLOPs become a full memory round-trip plus a second kernel launch.
Epilogue fusion is applying operations after the GEMM just before the result is saved to HBM. The output goes to global memory once, already transformed no second kernel, no round-trip. On Hopper and Blackwell the epilogue can overlap with the next tile's mainloop, so for small ops the extra compute is often nearly hidden.
imageCUTLASS exposes this through Epilogue Visitor Trees (EVT). You write a visit() that runs on each accumulator fragment, and the collective epilogue handles the loads and stores. The mechanics are explained well in the Colfax EVT writeup. The short version: pointwise fusions (bias, ReLU, cast) fall out cleanly: every output element is independent, there's nothing to reduce across, and CUTLASS ships prebuilt visitors for them.
In this post we focus on fusing RMSNorm. It normalizes a vector by its own RMS: y = x · rsqrt(mean(x²) + eps) · weight. Unlike a pointwise op, it needs rstd over the whole head, a reduction. That reduction is what makes fusion hard, and to see why we need to look at how the epilogue actually sees its data.
Tiles and fragments on Blackwell
The GEMM works in CTA tiles: on Blackwell (SM100), our NVFP4 GEMM uses a 256×256 CTA tile. The epilogue doesn't process that whole tile at once; it walks it in smaller epilogue tiles of 128×128, giving 4 epilogue subtiles per CTA:
imageEach epilogue subtile is partitioned across 128 threads, so each thread holds a fragment of 128 elements (FragmentSize = 128). The visit() callback receives one fragment at a time, 128 values that this thread is responsible for. This is the unit of work the epilogue sees, and it matters for what you can fuse.
This makes head_dim=128 RMSNorm fusion trivial: a head of 128 elements is exactly one thread's fragment. When visit() is called, the thread is already holding all 128 values of one head in its own registers. To compute RMSNorm it sums the squares across those 128 values (no cross-thread shuffle, no second fragment, no extra pass), scales, and stores. The reduction fits in a single visit() call.
Ideogram v4 is not so convenient. Its attention uses head_dim=256, so a single head no longer fits in one fragment; it straddles two, and the clean single-visit() reduction breaks down.
Why head_dim=256 is hard
To see why that's a problem, look at how the epilogue runs. After the mainloop, CUTLASS's collective epilogue loops over the 4 subtiles of the CTA tile in a fixed order: Tile 0 → Tile 1 → Tile 2 → Tile 3, and for each subtile it calls visit() once per fragment, stores the result, and moves on. It is a single forward pass; there is no going back to an earlier subtile.
imageRMSNorm needs rstd over the whole head, a reduction: rstd = rsqrt(mean(x²) + eps), then y = x * rstd * weight. To compute it we need the sum of squares across both halves, but they arrive in separate visit() calls: the first half in Tile 0, the second in Tile 1. By the time we see Tile 1 and can compute rstd, Tile 0 has already been visited and stored. There's no way to go back and fix it up. The rest of this section is how we fused it anyway: a two-pass revisit schedule that keeps the first half in registers.
The revisit fusion
CUTLASS's epilogue collective works with a fixed, single-pass loop order: visit each subtile once, store, move on. That doesn't solve our problem: we need to see Tile 1 before we can normalize Tile 0, which means going back. So we wrote an epilogue that lets the visitor walk subtiles in the order the reduction needs (including coming back to a tile it already saw) instead of the stock single forward pass.
The schedule, per row-group, is three touches:

Tile 0, pass 0: this is the first half of the head. We compute its sum-of-squares and move on; we can't normalize yet without the second half.
Tile 1, pass 1: the second half of the head arrives. Now we have both halves: we compute this half's sum-of-squares, add it to the first half's, and finally get rstd. We normalize and store this half.
Tile 0, pass 2 (revisit): we go back to Tile 0 and scale it with the now-known rstd. This is the extra touch the single-pass epilogue can't do.
Everything stays on-chip: no global memory round-trip, no second kernel. A custom epilogue walks the three-touch schedule; a visitor carries the reduction state across those touches and applies the scale when rstd is ready. Together they fuse the norm before anything hits HBM.
Gated-SiLU (SwiGLU) fusion
A different common pattern in diffusion transformers (and LLMs like LLaMA) is gated-SiLU. One GEMM produces an output C, then the layer finishes by splitting it in half and multiplying:
C = A @ B
output = SiLU(C[:, :N//2]) * C[:, N//2:]
That cuts the output width in half; two halves become one. Same story as RMSNorm: the math is cheap, but an unfused second kernel pays a full HBM round-trip. CUTLASS has no built-in epilogue for this "pair + reduce N/2" pattern, so fusing it means a custom visitor.
The catch is layout. For output column n, the pair is gate = C[:, n] and up = C[:, n + N/2], separated by half of N. Those two values can easily land in different fragments, different subtiles, or even different CTAs. Synchronizing across tiles would kill the epilogue's parallel model.
imageInstead we permute the columns of B once during weight packing. The GEMM then produces interleaved pairs [gate0, up0, gate1, up1, ...] instead of two separated halves. With adjacency guaranteed, a custom EVT visitor applies SiLU and the multiply on adjacent elements in the same fragment, and stores the reduced result: one kernel, no cross-tile communication.
The full walkthrough (weight packing, the visitor hooks, and the EVT wiring) is in Crafting Efficient Kernels with Epilogue Fusion.
imageOG weights fail to deliver high quality image with FP4 quantizationThe catch: FP4 costs image quality
Fusing the epilogue gets us a fast FP4 forward pass. On its own, it also gets us a worse-looking one. Stock FP4 inference looked visibly worse than bf16, specifically on color. Reds washed out, chroma dropped, and color-heavy prompts (logos, products, plants) looked desaturated next to the bf16 reference.
Those handful of levels per value are fine for a large language model, where every token is a small contribution to a big average. A diffusion transformer is less forgiving, because it iteratively denoises a latent over many steps and each step feeds its output into the next. A slightly wrong velocity at one step becomes a wrong latent at the next, which feeds a wrong input at the step after that. The error snowballs. With FP4, this compounding showed up as desaturation: the model still produced the right composition, but colors came out flatter and less vivid than the bf16 model.
Post-processing tricks (luma correction, color rebalancing) did not materially help, because the error was baked into the latents before decoding, not just a display-space issue. We needed to fix this inside the model, not after it.
What did not work
Before landing on the final recipe, we tried a few things that taught us what matters and what does not:
Post-processing on the output. Our first instinct was to just fix the colors after generation. Saturation correction, luma adjustment, color rebalancing on the decoded image. None of it worked. The desaturation is not a display problem; it is a latent trajectory problem. By the time you have pixels to correct, the denoiser has already taken slightly wrong steps for many iterations, and the color information is just gone. You cannot un-bake it from the final image.
Naive distillation. We tried the simplest thing: quantize the student to FP4, run a bf16 teacher on the same input, and minimize MSE between their predictions. No gradient flowed through the quantization - the student was frozen in its quantized state and just compared to the teacher. Loss went down, images did not get better. That is when we learned the hard way that diffusion training loss does not predict image quality. You can watch it drop for thousands of steps and the output barely changes.
These told us two things: the quantization error had to be addressed during training (not after), and the final judgment had to be visual (not numeric).
Quantization-aware distillation
Around the time we were wrestling with this, a technique called quantization-aware distillation (QAD) was published for LLMs and VLMs(paper). The idea is simple in hindsight: instead of just quantizing a model and hoping for the best, you distill a full-precision teacher into a quantized student. The student learns to predict what the teacher would have predicted, but under the constraint of its own quantization noise. The teacher is frozen bf16, the student is quantized, and you train the student to match the teacher's output.
That paper used KL divergence on logits, because it was about LLMs. We are not predicting tokens; we are predicting a velocity field over a latent. So we swapped the loss: MSE between the student's velocity prediction and the teacher's velocity prediction. Same spirit, different objective.
The critical difference from the naive distillation we tried earlier: in QAD, the student does not just run in FP4 and get compared to a teacher. The student trains through its own quantization. Gradients flow back through the FP4 rounding via a straight-through estimator, so the weights actively adapt to the quantization noise they produce. That is the whole point - the weights move to a part of parameter space where being rounded to 4 bits does not hurt the output. Naive distillation just compares a frozen-quantization student to a teacher and hopes; QAD lets the quantization be part of the optimization.
In the forward pass, the student's bf16 weights and activations are quantized to FP4 for the matmul, then dequantized back. The output carries real quantization noise from that round-trip. The frozen bf16 teacher produces the target velocity. The loss is MSE between the student's noisy prediction and the teacher's clean target. In the backward pass, the FP4 rounding is detached from the autograd graph: a rounding function has zero gradient almost everywhere, so it cannot be differentiated directly, and the straight-through estimator simply bypasses it, letting gradients flow in full precision. The forward pass carries the quantization noise; the backward pass ignores it. Over training, the weights converge to a region of parameter space where rounding to 4 bits does not degrade the output.
At inference, none of this is needed. We export the trained weights, load them into the production pipeline, apply real static FP4 quantization with the real kernels, and generate images. The student runs as just the conditional branch with guidance = 1 - no teacher, no CFG, no STE.
imageCollapsing CFG into one forward
Classifier-free guidance (CFG) is how diffusion models steer toward the prompt. At inference, you run the transformer twice per step: once conditioned on the prompt (conditional), and once with no text (unconditional), That is a 2x cost on every denoising step. We realized we could fold this into the distillation and get it for free.
During training, the frozen bf16 teacher runs both branches and produces the guided CFG velocity. The student is only the conditional branch, and it is trained to directly predict that guided velocity. At inference, we run the trained conditional branch alone with guidance = 1 - one forward pass instead of two. So on top of the FP4 speedup, we halve the transformer cost by removing CFG entirely from the serving path.
This is the part that goes beyond the QAD paper. That paper recovers quantization accuracy and stops. We recover quantization accuracy and distill away the CFG branch, so the final inference graph is both smaller (fp4) and shorter (one branch).
What made QAD work
Getting QAD to actually recover visual quality required a few decisions beyond the core recipe. These are the ones that mattered most:
Loss combination. We use flow-matching loss in combination with distillation loss to utilize real data quality. Pure distillation alone was not enough to recover the full visual quality we wanted.
Guidance schedule. We did not use one fixed guidance for the teacher target. We used higher guidance over the noisy and mid timesteps and lower guidance near the clean end, because the model benefits from stronger steering early and gentler steering as it converges.
With QAD recovering the quality and CFG folded into a single branch, two of the three cost multipliers are handled. The last one is the biggest.
imageQAD recovers the quantization error and delivers similar outputs
Timestep distillation
We have now made each forward pass about as cheap as it gets: 4-bit matmuls in FP4, fused epilogues so the norms and activations don't pay a round-trip, and a single guidance branch instead of two. The one thing we haven't touched is how many forward passes there are, the number of denoising steps, and it is the biggest lever of all.
A diffusion transformer earns its quality by denoising a latent over many iterations, and every iteration is a full forward pass. If you can teach the model to do the same job in a handful of steps, you collapse the single largest factor in latency and throughput. That is timestep distillation. It is also the part of the pipeline with the most moving parts, the most literature, and the most ways to quietly ruin image quality.
The foundation: Distribution Matching Distillation
The natural first idea is regression: run the teacher, run the student, minimize the MSE between them. That is what the QAD work above does, and it works when the student and teacher take the same path. But a few-step student does not take the same path as a many-step teacher. You cannot ask it to hit the same intermediate latents, because it is skipping most of them. Regression pins the student to a trajectory it should be free to leave.
Distribution Matching Distillation (DMD) reframes the goal: instead of matching the teacher sample by sample, match distribution to distribution. We don't care what the student does on any single trajectory; we care that the cloud of images it produces looks like the cloud the teacher produces.
The mechanism keeps two score networks: a real score (the frozen teacher) and a fake score (a second network tracking the student's current distribution). To update the student, DMD takes a student sample, renoises it, and asks both score networks where to go. The difference between the two scores is a gradient direction that moves the student's distribution toward the teacher's. That difference-of-scores is, up to a constant, the gradient of the KL divergence between the two distributions at that noise level. No paired targets, no MSE to a specific latent, just "make these two distributions agree."
imageDMD2: the version people actually ship
DMD2 cleans up vanilla DMD into the recipe most modern few-step image models descend from: it drops the regression term (making the method data-free), uses two-timescale updates so the fake score stays accurate as the student changes, adds a GAN loss as a secondary sharpening term, and supports multi-step sampling so the student isn't locked to a single step count.
A whole family, each chasing a different lever
DMD2 kicked off a family of follow-ups. The right choice depends on what you're optimizing for: DMDR, Flash-DMD, AdvDMD, and GNDM fold RL and reward signals into distillation. DP-DMD preserves diversity. AMD improves stability.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み