本文へスキップ
郭立 (leeguoo)

Wan2.2 の全ソースコードを読み終えて:この動画生成コードはどこであなたを噛むのか

五大タスク行列、階層アーキテクチャから DiT/VAE/T5/分散/スケジューラまで、Wan2.2 のソースコードにある、文書とコードが乖離している実際の落とし穴を層ごとに分解する。

このページの目次
1.5つのタスク、5{通|とお}りの{入力|にゅうりょく}、5{通|とお}りの{落|お}とし{穴|あな}2.5{層|そう}アーキテクチャ、{何|なに}が{何|なに}に{貼|は}りつくのか3.{一次生成|いちじせいせい}では{何|なに}が{通|とお}り、どの{段階|だんかい}でこっそり{失敗|しっぱい}するのか4.五つの{平行世界|へいこうせかい}5.{優雅|ゆうが}な{降級|こうきゅう}を{書|か}いたが、{誰|だれ}も{使|つか}っていない6.VRAM と{計算力|けいさんりょく}をどう{分|わ}けるか7.shift {引数|ひきすう}が{二|に}{回|かい}、{二|ふた}つの{場所|ばしょ}で{効|き}いている8.{三|みっ}つの{解像度|かいぞうど}テーブル9.{共通|きょうつう}するパターン

Wan2.2 リポジトリを generate.py からスケジューラまで読よみ進すすめたあと、ほんとうに書かき残のこす価値かちがあるのは「このモデルをどう使つかうか」ではなく、このコードがあなたの気きづかないところでどう噛かみついてくるかだ。

Wan2.2 は Alibaba の Wan チームがオープンソース公開こうかいした動画生成どうがせいせいモデルライブラリで、generate.py という1つの入口いりぐちから、--task によって5つの推論すいろんパイプラインへ分岐ぶんきする。テキストから動画どうが、画像がぞうから動画どうが、テキスト・画像がぞう統一とういつから動画どうが、音声おんせいから動画どうが、キャラクターアニメーションだ。表面上ひょうめんじょうは「1つのモデル、5つの遊あそび方かた」に見みえるが、コードを開ひらくと、むしろ5つの並行世界へいこうせかいが、CLI という薄うすい層そうで貼はり合あわされているように見みえる。以下いかではコードを読よんだ順番じゅんばんに沿そって一通ひととおり見みていき、落おとし穴あなはその場ばで印しるしをつけ、重要じゅうようなところはそのままソースコードを貼はる。

この記事で扱あつかうのは工学上こうがくじょうの落おとし穴あなだ。まず動画生成どうがせいせいそのものの原理げんり、つまり VAE 圧縮あっしゅく、flow matching、CFG、MoE 二重専門家にじゅうせんもんかの数学すうがくと数値すうちを理解りかいしたいなら、姉妹編しまいへんのWan2.2 のソースコードで学まなぶ動画生成どうがせいせいを読よんでほしい。2本ほんを合あわせて読よむとよい。

Wan2.2 全体像

5つのタスク、5通とおりの入力にゅうりょく、5通とおりの落おとし穴あな

Wan2.2 はタスクの行列ぎょうれつだ。t2v-A14B、i2v-A14B、ti2v-5B、s2v-14B、animate-14B は、それぞれ要求ようきゅうする素材そざいがまったく違ちがう。純粋じゅんすいなテキスト、参照画像さんしょうがぞう、参照画像さんしょうがぞうに音声おんせいを加くわえたもの、さらには動画どうが1本ぽんと前処理まえしょりで生成せいせいした姿勢素材しせいそざいパッケージまである。どのタスクを使つかうのか、どんな入力にゅうりょくを用意よういするのか、その答こたえは generate.py の引数検証ひきすうけんしょうコードにある。ドキュメントには半分はんぶんしか書かかれていない。

業務理解

まず generate.py にあるデフォルト値ちのフォールバック処理しょりを見みてみよう。

python
if args.prompt is None:
    args.prompt = EXAMPLE_PROMPT[args.task]["prompt"]
if args.image is None and "image" in EXAMPLE_PROMPT[args.task]:
    args.image = EXAMPLE_PROMPT[args.task]["image"]

--prompt を渡わたさなくても、コードはエラーを出ださない。ハードコードされた例文れいぶん、サングラスをかけた白猫しろねこがサーフィンする内容ないようを黙だまって適用てきようする。s2v-14B と i2v-A14B は、さらに同おなじ例れいを共有きょうゆうしている。prompt を渡わたさずにこの2つのタスクをそれぞれ走はしらせると、出でてくるものの雰囲気ふんいきが同おなじになる。どちらのタスクも公式こうしき demo 素材そざいを使つかって空回からまわししているからだ。--image、--audio、--tts_* にも似にたようなフォールバックがある。初心者しょしんしゃは初回しょかいに demo が通とおったあと、自分じぶんの設定せっていが効きいていると勘違かんちがいしがちで、これが最大さいだいの誤解源ごかいげんだ。

--size の意味いみはタスクごとに一致いっちしない。t2v/ti2v では解像度かいぞうどだが、i2v/s2v では実際じっさいには目標もくひょうの画素面積がそめんせきで、最終画面さいしゅうがめんの縦横比たてよこひは入力画像にゅうりょくがぞうに追従ついじゅうする。1280*720 と入いれても、出力しゅつりょくがそのサイズになるとは限かぎらない。frame_num は 4n+1 を満みたす必要ひつようがある。3つのタスクでデフォルト値ちもそれぞれ違ちがう(t2v/i2v は 81、ti2v-5B は 121、animate-14B は 77)。背後はいごには VAE の時系列圧縮率じけいれつあっしゅくりつの違ちがいがあり、この数字すうじは気軽きがるに変かえてはいけない。Wan-Animate は2段階だんかいの流ながれだ。まず独立どくりつした preprocess_data.py を実行じっこうして pose/mask/reference 素材そざいパッケージを作つくり、それからこのディレクトリを generate.py に渡わたす。前処理まえしょりを飛とばして、元もとの動画どうがパスをそのまま渡わたして走はしらせるのが、このコードで最もっともよくある失敗しっぱいパターンだ。

5層そうアーキテクチャ、何なにが何なにに貼はりつくのか

wan/ パッケージを分解ぶんかいすると、configs はどの重おもみ・どの形状けいじょうでモデルを走はしらせるかを決きめる。modules は純粋じゅんすいなネットワーク定義ていぎで、「タスク」という概念がいねんを知しらない。distributed は pipeline 用ようの並列へいれつツールボックス。utils はサンプリング数学すうがくと雑多ざったなもの。それらを貼はり合あわせているのが、text2video.py、image2video.py などの pipeline クラスだ。

アーキテクチャ全体像

この層そうには、いくつか歴史的れきしてきな負債ふさいが残のこっている。wan/distributed/__init__.py は空からファイルで、すべての pipeline はそこを迂回うかいして子こモジュールから直接ちょくせつ import しており、パッケージ名めいは namespace の場所取ばしょとりとしての役割やくわりしか残のこっていない。wan/utils/__init__.py の __all__ には HuggingfaceTokenizer と書かかれているが、このクラスは実際じっさいには wan/modules/tokenizers.py から export されているため、__all__ に従したがって from wan.utils import HuggingfaceTokenizer と書かくと、そのまま ImportError になる。期限切きげんぎれの宣言せんげんが片付かたづけられずに残のこっているわけだ。

さらに import wan という1行ぎょうの代償だいしょうもある。これは5つの pipeline とそれぞれの依存いぞんを一気いっきに起動きどうする。t2v-A14B だけを走はしらせたい場合ばあいでも、speech2video.py の TTS/CosyVoice の重おもい依存いぞんまで連つれてくる。どれか1つのタスクモジュールの import が失敗しっぱいすると、無関係むかんけいな4つのタスクまで巻まき添ぞえで落おちる。

一次生成いちじせいせいでは何なにが通とおり、どの段階だんかいでこっそり失敗しっぱいするのか

t2v-A14B(MoE 双専門家そうせんもんか)を主線しゅせんにすると、generate.py が引数ひきすうを解析かいせき → pipeline クラスを選択せんたく → テキスト符号化ふごうか → 拡散かくさんサンプリング → VAE デコード → mp4 として保存ほぞん、という流ながれになる。

核心フロー

引数ひきすうチェックの粒度りゅうどは均一きんいつではない。i2v-A14B は --image を渡わたし忘わすれるとそのまま assert で落おちる。s2v-14B の --audio 不足ふそく、animate-14B の video/pose/mask 不足ふそくには対応たいおうする断言だんげんがなく、エラーは実行時じっこうじまで引ひき延のばされてから露呈ろていする。ドキュメントには frame_num が 4n+1 を満みたす必要ひつようがあると書かかれているが、コードにはその assert がない。誤あやまったフレーム数すうを渡わたすと、DiT/VAE 内部ないぶで形状けいじょうが合あわなくなってから爆発ばくはつし、エラー位置いちは原因げんいんからかなり遠とおい。

サンプリングの主しゅループで誤解ごかいされやすい点てんがある。CFG(条件じょうけんあり + 無条件むじょうけんで DiT をそれぞれ1回かいずつ走はしらせる)に MoE 双専門家そうせんもんかの切きり替かえが重かさなると、各 timestep は「専門家せんもんかを1つ選えらび、前向まえむき計算けいさんを2回かい走はしらせる」になる。2つの専門家せんもんかが同おなじステップに登場とうじょうすることはない。切きり替かえの根拠こんきょは t.item() >= boundary * num_train_timesteps で、スケジューラが吐はいた timestep の値あたいをそのまま比較ひかくしている。この両者りょうしゃは独立どくりつに維持いじされる状態じょうたいだ。スケジューラを替かえたり、shift が変かわって timestep の値域ちいきがずれたりすると、専門家せんもんかの切きり替かえ位置いちもつられて偏かたよる。エラーは出でず、品質低下ひんしつていかを目視もくしで見るみるしかない。

次つぎに落おとし込こみを見みる。wan/utils/utils.py の save_video の末尾まつびはこうだ。

python
        # write video
        writer = imageio.get_writer(
            cache_file, fps=fps, codec='libx264', quality=8)
        for frame in tensor.numpy():
            writer.append_data(frame)
        writer.close()
    except Exception as e:
        logging.info(f'save_video failed, error: {e}')

エンコードに失敗しっぱいしても logging.info を1行ぎょう出だすだけで、raise しない。主処理しゅしょりは保存ほぞんに失敗しっぱいしたことを知しらないまま、merge_video_audio へ進すすみ、ログの最後さいごには Finished と出でるが、ディスクには mp4 がない。merge_video_audio で ffmpeg が落おちた場合ばあいも同おなじく握にぎりつぶされる。前段ぜんだんの処理しょりがすべて正ただしくても、最後さいごの一歩いっぽで死しに、しかも黙だまったままだ。

五つの平行世界へいこうせかい

5つのタスククラスは互たがいに基底きていクラスを共有きょうゆうしていない。_configure_model という共通きょうつうの骨格こっかくが5つのファイルにそれぞれコピペされている: eval + 勾配こうばい凍結とうけつ → 系列けいれつ並列へいれつのとき attention にモンキーパッチ → dist.barrier() → FSDP 分割ぶんかつまたは GPU へ移動いどう。

五大タスククラス実装比較

5つもコピペすれば、細部さいぶはすでにズレている。S2V と Animate のバージョンには model.use_context_parallel = True が1行ぎょう多おおく、ほかの3つにはない。意図的いとてきなのか漏もれなのか判断はんだんしづらい。CFG のデフォルト開閉かいへいも統一とういつされていない: 4つのタスクはデフォルトでオン、Animate はデフォルトでオフ(guide_scale=1 は CFG を実行じっこうしないのと同おなじ)。調整ちょうせいを比較ひかくするときに「Animate のほうが速はやい」を bug として調査ちょうさすると、徒労とろうに終おわる。それは意図的いとてきに計算量けいさんりょうを節約せつやくする設計せっけいで、docstring の「表情ひょうじょう制御せいぎょのみに使つかい、ほとんどの場合ばあい不要ふよう」という一文いちぶんが理由りゆうを説明せつめいしているが、コードはそこを読よみに行いけとは教おしえてくれない。

TI2V は本質的ほんしつてきにはディスパッチャだ: 画像がぞうを渡わたすと内部ないぶの i2v 分岐ぶんきへ、渡わたさないと t2v へ進すすみ、1つのモデルを共有きょうゆうする。ここにはデフォルト値ちの罠わながある: WanTI2V.generate() のシグネチャでは frame_num=81 なのに、転送先てんそうさきの t2v()/i2v() のシグネチャでは frame_num=121。このクラスをそのまま Python ライブラリとして使つかい、フレーム数すうを明示めいじしないと、出でてくるのは81フレームになる。CLI ユーザーはここを踏ふまない。generate.py が必かならず設定せっていの121を明示的めいじてきに渡わたし、このデフォルト値ちを迂回うかいするからだ。

S2V は唯一ゆいいつ、自己回帰じこかいき分段ぶんだんするタスクで、音声おんせい長ちょうから num_repeat を計算けいさんし、ループで断片だんぺんを生成せいせいし、前まえの断片だんぺんの末尾まつび latent で接続せつぞくする。get_gen_size には「前回ぜんかいの生成せいせい結果けっかを続つづけて書かく」ためのインターフェイス(pre_video_path 引数ひきすう)が残のこっているが、主しゅフローが呼よぶときは常つねに None を渡わたす。インターフェイスは書かかれているが、道みちは通とおっていない。コードを読よむと、その能力のうりょくを過大評価かだいひょうかしやすい。

優雅ゆうがな降級こうきゅうを書かいたが、誰だれも使つかっていない

DiT 主幹しゅかん(WanModel)は、反復はんぷくする Attention Block の積つみ重かさねでできている。RoPE 付つき Self-Attention、テキスト context に接続せつぞくする Cross-Attention、FFN、そして AdaLN の六路ろくろ変調へんちょうで駆動くどうする。

核心モデルモジュール

wan/modules/attention.py には、兜底とうていつきの分配ぶんぱい関数かんすう attention() がある。FlashAttention が入はいっていれば転送てんそうし、入はいっていなければ PyTorch の scaled_dot_product_attention に退避たいひする。だが model.py の 9 行ぎょうはこう導入どうにゅうしている。

python
from .attention import flash_attention

主幹しゅかんは最下層さいかそうの flash_attention() を直接ちょくせつ呼よんでおり、その内部ないぶでは flash-attn が入はいっていない場合ばあい、assert FLASH_ATTN_2_AVAILABLE(attention.py:112)を硬かたく実行じっこうして、そのまま崩くずれる。兜底とうていは書かかれているが、主幹しゅかんは使つかっていない。attention カーネルはさらに q.device.type == 'cuda' も要求ようきゅうするため、CPU や Mac の MPS では初回しょかい呼よび出だしで assert に失敗しっぱいし、案内あんないもない。

VAE の落おとし穴あなはもう一層いっそう深ふかく隠かくれている。Wan2.1 版ばん(16 チャンネル)と Wan2.2 版ばん(48 チャンネル、patchify と双経路そうけいろダウンサンプリングが追加ついか)は構造こうぞうが相互そうごに非互換ひごかんで、2 系統けいとうの正規化せいきか scale は次元じげんが異ことなる。checkpoint と VAE 版ばんを取とり違ちがえると、出でる誤りあやまりはただの tensor shape mismatch で、根因こんいんは見みえない。Wan2.2 VAE の encode/decode はさらに try/except TypeError で不正ふせいな入力にゅうりょくを兜底とうていし、型かたを間違まちがえて渡わたすと例外れいがいを飲のみ込こみ、ログを出だして None を返かえす。呼よび出だし側がわはずっと離はなれた場所ばしょでようやく None のせいで爆発ばくはつする。Wan2.1 版ばんにはこの try/except がない。同おなじリポジトリで、2 版ばんの VAE は容錯ようさくの気性きしょうが違ちがう。

T5 側がわでは、umT5 は層そうごとに独立どくりつした相対そうたい位置いちエンコーディングを使つかい、24 層そうそれぞれが bias を計算けいさんする。設定せっていでは shared_pos=False で、bug ではなく umT5 の設計せっけいだが、この設定せってい行ぎょうを見みないと共有きょうゆうだと思おもいやすい。より実際的じっさいてきな危険きけんは、t5.py と 2 版ばんの VAE が checkpoint の読よみ込こみにすべて裸はだかの torch.load を使つかっており、weights_only=True を付つけていないことだ。出所でどころの分わからない重おもみファイルを読よみ込こむとき、逆ぎゃくシリアライズで任意にんいコードが実行じっこうされる危険きけんがある。

VRAM と計算力けいさんりょくをどう分わけるか

複数ふくすう GPU 戦略せんりゃくには 2 つの軸じくがある。FSDP は VRAM を担当たんとうし(パラメータの shard)、Ulysses の系列けいれつ並列へいれつは計算力けいさんりょくを担当たんとうする(activation の系列けいれつ次元を別々べつべつのカードに切きる)。どちらも torch.distributed の大域たいいき world を使つかい、独立どくりつした data parallel 次元はない。1 回かいの起動きどうで使つかうすべての GPU は、すべて同おなじ並列へいれつグループに属ぞくするか、そうでなければ単一たんいつカードになる。

複数カード分散推論戦略

Ulysses の考かんがえ方かたは 2 回かいの変換へんかんだ。q/k/v を heads 次元に沿そって scatter し、系列けいれつ次元に沿そって gather する。すると各かくカードは完全かんぜんな系列けいれつを持もって heads の一部いちぶを計算けいさんし、ローカルで FlashAttention を走はしらせたあと、all-to-all で元もとに戻もどす。この中なかには等長とうちょうという仮定かていが埋うまっている。sp_dit_forward は torch.chunk で系列けいれつを切きり、pad_freqs は「各かくカードの切片せっぺんが同おなじ長ながさ」という前提ぜんていで位置いちエンコーディングを計算けいさんする。torch.chunk では割わり切きれないと最後さいごの 1 塊かたまりがより短みじかくなる。系列けいれつ長ちょうが ulysses_size の整数せいすう倍ばいでないと、RoPE 周波数しゅうはすうと token が対応たいおうしなくなり、静しずかに誤あやまって、品質ひんしつ低下ていかとしてしか表あらわれない。

FSDP の省しょう VRAMの恩恵おんけいも、最初さいしょから得えられるわけではない。モデルはいったん丸まるごと GPU に載のせられ、そのあと shard_model に渡わたされて shard される。meta device 初期化しょきかは通とおっていない。shard の前まえに、各かくカードがまず完全かんぜんなモデルを載のせられなければならず、VRAMのきついカードは起動きどうした瞬間しゅんかんに OOM になる。また free_model は FSDP の private 属性ぞくせい _handle.flat_param.data に触さわっており、torch のアップグレード後ごにはこの行ぎょうがいつ壊こわれてもおかしくない。

shift 引数ひきすうが二に回かい、二ふたつの場所ばしょで効きいている

スケジューラ(FlowUniPCMultistepScheduler / FlowDPMSolverMultistepScheduler)は、diffusers のスケジューラを flow-matching 版ばんに改造かいぞうしたもので、ファイル冒頭ぼうとうには "Copied from diffusers" と書かかれている。

スケジューラと Prompt 拡張

text2video.py で UniPC を構築こうちくするコード:

python
sample_scheduler = FlowUniPCMultistepScheduler(
    num_train_timesteps=self.num_train_timesteps,
    shift=1,
    use_dynamic_shifting=False)
sample_scheduler.set_timesteps(
    sampling_steps, device=self.device, shift=shift)

コンストラクタの shift=1 は固定値こていちで、本当ほんとうの --sample_shift は set_timesteps の中なかで効きく。DPM++ 側がわはまた経路けいろが違ちがう。呼よび出だす前まえに get_sampling_sigmas(steps, shift) で sigma 列れつを手計算てけいさんしてから渡わたす。shift のデフォルト値ちを調整ちょうせいしたり、第三だいさんの solver を追加ついかしたりしたいとき、片方かたほうを真似まねして書かくともう片方かたほうを漏もらしやすい。字面じづらどおりにコンストラクタ引数ひきすうを変かえても、効果こうかはない。

Prompt 拡写かくしゃには二ふたつのバックエンド、DashScope オンライン API とローカル Qwen があり、rank 0 で一度いちどだけ実行じっこうしてからブロードキャストし、複数ふくすうカードでの重複ちょうふく呼よび出だしを避さけている。ローカル Qwen は呼よび出だしのたびにモデル全体ぜんたいを CPU↔GPU 間かんで往復おうふくさせる。メインモデルの显存けんそんを空あけるためのトレードオフだが、その代償だいしょうとして毎回まいかいの拡写かくしゃに目立めだつ遅延ちえんがある。DEFAULT_SYS_PROMPTS 辞書じしょは三みっつのタスクでネスト構造こうぞうがそれぞれ異ことなり、decide_system_prompt は文字列もじれつマッチの固定こていブランチに頼たよっている。新あたらしいタスクを追加ついかするには新あらたなブランチを手書てがきする必要ひつようがあり、ネストを書かき間違まちがえると実行時じっこうじに KeyError になる。

三みっつの解像度かいぞうどテーブル

設定せっていレイヤー最大さいだいの命名めいめいトラップ: t5_checkpoint/vae_checkpoint が組くみ立たてるのはファイルパスだが、low_noise_checkpoint/high_noise_checkpoint は from_pretrained に渡わたす subfolder のサブディレクトリ名めいだ。同じ xxx_checkpoint という命名めいめいで、用途ようとは二ふたつ。モデルディレクトリを手動しゅどうで整理せいりしたり、サブディレクトリ名めいを変かえたりすると、エラーは transformers レイヤーの「config.json が見みつからない」になり、フィールドを間違まちがえたとは教おしえてくれない。

タスク設定とモデルダウンロード

解像度かいぞうどのホワイトリストは SIZE_CONFIGS、MAX_AREA_CONFIGS、SUPPORTED_SIZES という三みっつの独立どくりつ保守ほしゅテーブルに分わかれている。ti2v-5B は 704*1280 を使つかい、ほかのタスクでは 720*1280 がよく使つかわれる。この二ふたつの数字すうじは見みた目めがかなり似にていて、README やコミュニティの投稿とうこうでもよく混同こんどうされる。渡わたし間違まちがえても「解像度かいぞうどはサポートされていません」とは出でず、三みっつのテーブルで key が見みつからないというエラーになる。新あたらしい解像度かいぞうどを追加ついかするには三みっつのテーブルを同時どうじに変かえる必要ひつようがあり、一ひとつでも漏もれても静的せいてきチェックは何なにも警告けいこくしてくれない。

共通きょうつうするパターン

九ここのつの分析ぶんせきを並ならべてみると、パターンが浮うかび上あがる。このコードの落おとし穴あなの多おおくはロジックエラーではなく、安全あんぜんなフォールバックは書かかれているのに、主経路しゅけいろで使つかわれていないことだ。attention() の降格こうかく、_validate_args のばらついたアサーション、frame_num のドキュメント要件ようけんとコード検証けんしょうのずれ、shift の二ふたつの有効化ゆうこうかルートは、すべて同おなじパターンの変種へんしゅだ。ドキュメント、デフォルト値ち、実際じっさいのコードパスの三者さんしゃが切きり離はなされ、チェーンの深ふかいところへ進すすむほど、代償だいしょうは見みえにくくなる。CLI のエラーが、ランタイムエラーへ劣化れっかし、さらにエラーは出でないが品質ひんしつだけがこっそり悪わるくなる状態じょうたいへ劣化れっかしていく。

このコードを引ひき継ついで二次開発にじかいはつするなら、効きく経験則けいけんそくは一ひとつ。docstring やフィールド名めいの字面じづらを信しんじず、コードの中なかで実際じっさいに効きいているパスを信しんじること。同おなじフィールド、同おなじ関数かんすうでもタスクによって意味いみが違ちがうところは、個別こべつに確認かくにんし、タスク間かんで共通きょうつうだと仮定かていしない。

同じカテゴリの記事 · AI & Agent

← 前の記事
coding agent に互いに @ できる部屋を与える:AgentParty とは何か、どこで使うのか、原理は何か
次の記事 →
動画生成の勘定を明らかにする:Wan2.2 ソースコード精読

コメント

コメントは即時公開されますが、ポリシー違反時は非表示になる場合があります。

最大 1000 文字。