<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Haruk1y Wiki Blog</title>
        <link>https://haruk1y.github.io/wiki/blog</link>
        <description>Haruk1y Wiki Blog</description>
        <lastBuildDate>Sat, 20 Jun 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Stable Video Diffusion の公式実装を読む]]></title>
            <link>https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion</link>
            <guid>https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion</guid>
            <pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Stable Video Diffusion の推論処理を、Stability AI の公式実装である Stability-AI/generative-models をもとに解説していく。]]></description>
            <content:encoded><![CDATA[<p><a href="https://stability.ai/news/stable-video-diffusion-open-ai-video-model" target="_blank" rel="noopener noreferrer" class="">Stable Video Diffusion</a> の推論処理を、Stability AI の公式実装である <a href="https://github.com/Stability-AI/generative-models" target="_blank" rel="noopener noreferrer" class="">Stability-AI/generative-models</a> をもとに解説していく。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="全体像">全体像<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#%E5%85%A8%E4%BD%93%E5%83%8F" class="hash-link" aria-label="Direct link to 全体像" title="Direct link to 全体像" translate="no">​</a></h2>
<p>推論スクリプトと YAML config を組み合わせて、<code>DiffusionEngine</code> の中に部品を組み立てていく構成になっている。
メインの実装は <code>simple_video_sample.py</code> の <code>sample()</code> にある。ここで入力画像の読み込みから出力動画の保存までを行っている。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="version-から-config-を選ぶ">version から config を選ぶ<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#version-%E3%81%8B%E3%82%89-config-%E3%82%92%E9%81%B8%E3%81%B6" class="hash-link" aria-label="Direct link to version から config を選ぶ" title="Direct link to version から config を選ぶ" translate="no">​</a></h2>
<p><code>sample()</code> の前半では、<code>version</code>と呼ばれるパラメータに応じて</p>
<ul>
<li class="">フレーム数 (出力動画の長さ)</li>
<li class="">ステップ数 (デノイジングの回数)</li>
<li class="">config ファイル</li>
</ul>
<p>を選択する。公式実装では、<code>svd</code> と <code>svd_xt</code> の2種類が用意されている。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">svd     -&gt; 14 frames, 25 steps, svd.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">svd_xt  -&gt; 25 frames, 30 steps, svd_xt.yaml</span><br></div></code></pre></div></div>
<p>その後、<code>load_model()</code> が YAML を読み、<code>instantiate_from_config()</code> でモデルを作る。<code>svd.yaml</code> のルートを見ると、中心は <a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/models/diffusion.py" target="_blank" rel="noopener noreferrer" class="">DiffusionEngine</a> で、その下に denoiser、network、conditioner、first stage、sampler などが存在する構成になっている。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">DiffusionEngine</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ├─ Denoiser</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ├─ VideoUNet</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ├─ GeneralConditioner</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ├─ AutoencodingEngine / VideoDecoder</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  └─ EulerEDMSampler</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="入力画像は2つの条件経路に分かれる">入力画像は2つの条件経路に分かれる<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#%E5%85%A5%E5%8A%9B%E7%94%BB%E5%83%8F%E3%81%AF2%E3%81%A4%E3%81%AE%E6%9D%A1%E4%BB%B6%E7%B5%8C%E8%B7%AF%E3%81%AB%E5%88%86%E3%81%8B%E3%82%8C%E3%82%8B" class="hash-link" aria-label="Direct link to 入力画像は2つの条件経路に分かれる" title="Direct link to 入力画像は2つの条件経路に分かれる" translate="no">​</a></h2>
<p>SVD を読む上で一番大事なのは、入力画像が1回だけ使われるわけではない、という点だと思う。公式実装では、画像から大きく2種類の条件を作る。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">入力画像</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ├─ ノイズなし画像 -&gt; OpenCLIP -&gt; cross-attention 条件</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  └─ 少しノイズを足した画像 -&gt; VAE encoder -&gt; concat 条件</span><br></div></code></pre></div></div>
<p>スクリプト側では <code>value_dict</code> に条件素材を詰める。中身を意味で書くとこうなる。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">cond_frames_without_noise = 元画像</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cond_frames               = 元画像 + cond_aug * noise</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fps_id                    = fps 条件</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">motion_bucket_id          = motion 条件</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cond_aug                  = 画像に足したノイズ量</span><br></div></code></pre></div></div>
<p><code>cond_aug</code> は、入力画像を VAE latent にするときのノイズ量であり、同時に条件 vector としてもモデルに渡される。つまり「どれくらい劣化した条件画像から復元する設定なのか」もモデルに知らせている。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="generalconditioner-が条件を仕分ける">GeneralConditioner が条件を仕分ける<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#generalconditioner-%E3%81%8C%E6%9D%A1%E4%BB%B6%E3%82%92%E4%BB%95%E5%88%86%E3%81%91%E3%82%8B" class="hash-link" aria-label="Direct link to GeneralConditioner が条件を仕分ける" title="Direct link to GeneralConditioner が条件を仕分ける" translate="no">​</a></h2>
<p><code>svd.yaml</code> の <code>conditioner_config</code> を見ると、SVD の条件は3種類に分かれている。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">cond_frames_without_noise</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; FrozenOpenCLIPImagePredictionEmbedder</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; crossattn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cond_frames</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; VideoPredictionEmbedderWithEncoder</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; concat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fps_id / motion_bucket_id / cond_aug</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; ConcatTimestepEmbedderND</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; vector</span><br></div></code></pre></div></div>
<p><a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/modules/encoders/modules.py" target="_blank" rel="noopener noreferrer" class="">GeneralConditioner</a> は、embedder の出力次元によって保存先を決める。2次元なら <code>vector</code>、3次元なら <code>crossattn</code>、4次元なら <code>concat</code> という分類になる。</p>
<p>ここでできる条件を diffusers 風に見ると、<code>crossattn</code> は <code>image_embeddings</code>、<code>concat</code> は <code>image_latents</code>、<code>vector</code> は <code>added_time_ids</code> に近い。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">crossattn: CLIP image embedding</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">concat:   VAE image latent</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">vector:   fps + motion_bucket_id + cond_aug</span><br></div></code></pre></div></div>
<p>この時点で、入力画像は「意味的な画像 embedding」と「空間的な latent」の両方として使われる。SVD の image-to-video らしさは、かなりここに詰まっている。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cfg-用に条件ありなしを作る">CFG 用に条件あり・なしを作る<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#cfg-%E7%94%A8%E3%81%AB%E6%9D%A1%E4%BB%B6%E3%81%82%E3%82%8A%E3%81%AA%E3%81%97%E3%82%92%E4%BD%9C%E3%82%8B" class="hash-link" aria-label="Direct link to CFG 用に条件あり・なしを作る" title="Direct link to CFG 用に条件あり・なしを作る" translate="no">​</a></h2>
<p>次に <code>get_unconditional_conditioning()</code> で、conditional な <code>c</code> と unconditional な <code>uc</code> を作る。</p>
<p>公式実装では、unconditional 側で <code>cond_frames</code> と <code>cond_frames_without_noise</code> をゼロにする。つまり CFG では、画像条件ありの予測と、画像条件を消した予測を比較する。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">uc: 画像条件をゼロにした条件</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">c:  画像条件を持った条件</span><br></div></code></pre></div></div>
<p>その後、<code>crossattn</code> と <code>concat</code> はフレーム数ぶん repeat される。公式実装では内部表現が <code>[B*T, ...]</code> に寄っているので、1枚の条件画像を T フレームぶんに複製してから、batch と time をまとめた形に並べ替える。</p>
<p>diffusers だと <code>[B, T, C, H, W]</code> で見えることが多いが、公式実装では <code>[B*T, C, H, W]</code> として扱う場面が多い。この layout の違いを頭に入れておくと読みやすい。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="生成対象は-video-latent-のランダムノイズ">生成対象は video latent のランダムノイズ<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#%E7%94%9F%E6%88%90%E5%AF%BE%E8%B1%A1%E3%81%AF-video-latent-%E3%81%AE%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%83%8E%E3%82%A4%E3%82%BA" class="hash-link" aria-label="Direct link to 生成対象は video latent のランダムノイズ" title="Direct link to 生成対象は video latent のランダムノイズ" translate="no">​</a></h2>
<p>条件ができたら、生成対象の video latent をランダムノイズから始める。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">samples_z の初期値: [T, 4, H/8, W/8] の noise</span><br></div></code></pre></div></div>
<p>ここで <code>T</code> はフレーム数、<code>4</code> は latent channel、<code>H/8</code> と <code>W/8</code> は VAE の空間圧縮後の解像度である。</p>
<p>重要なのは、入力画像の latent をそのまま動画に伸ばしているわけではないこと。最終的に denoise される主役は、T フレームぶんのランダムな video latent である。入力画像はあくまで条件として横から入る。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sampler-は-denoiser-関数を受け取る">sampler は denoiser 関数を受け取る<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#sampler-%E3%81%AF-denoiser-%E9%96%A2%E6%95%B0%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B" class="hash-link" aria-label="Direct link to sampler は denoiser 関数を受け取る" title="Direct link to sampler は denoiser 関数を受け取る" translate="no">​</a></h2>
<p>公式スクリプトでは、sampler に model をそのまま渡すのではなく、denoiser 関数を作って渡す。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">EulerEDMSampler</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Denoiser</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; VideoUNet</span><br></div></code></pre></div></div>
<p><code>additional_model_inputs</code> として <code>image_only_indicator</code> と <code>num_video_frames</code> も渡される。後者は VideoUNet 内で、batch と time の軸を戻すために必要になる。</p>
<p>サンプリング本体は <a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/modules/diffusionmodules/sampling.py" target="_blank" rel="noopener noreferrer" class="">EulerEDMSampler</a> で、sigma schedule に沿ってノイズを少しずつ落としていく。diffusers の感覚でいえば、<code>for t in timesteps</code> の loop で <code>unet</code> と <code>scheduler.step()</code> を回しているところに近い。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="image-latent-は-unet-入力-channel-に-concat-される">image latent は UNet 入力 channel に concat される<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#image-latent-%E3%81%AF-unet-%E5%85%A5%E5%8A%9B-channel-%E3%81%AB-concat-%E3%81%95%E3%82%8C%E3%82%8B" class="hash-link" aria-label="Direct link to image latent は UNet 入力 channel に concat される" title="Direct link to image latent は UNet 入力 channel に concat される" translate="no">​</a></h2>
<p>SVD の読みどころはここだと思う。<code>concat</code> 条件、つまり VAE encode された入力画像 latent は、UNet の入力 channel として結合される。</p>
<p><a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/modules/diffusionmodules/wrappers.py" target="_blank" rel="noopener noreferrer" class="">OpenAIWrapper</a> の役割を意味で書くとこうなる。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">noisy video latent: [B*T, 4, H/8, W/8]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">image latent:       [B*T, 4, H/8, W/8]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">------------------------------------------------</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VideoUNet input:    [B*T, 8, H/8, W/8]</span><br></div></code></pre></div></div>
<p>実際、<code>svd.yaml</code> では <code>VideoUNet</code> の <code>in_channels</code> が <code>8</code>、<code>out_channels</code> が <code>4</code> になっている。UNet は「今の noisy video latent」と「条件画像の latent」を見ながら、次に取り除くべきノイズを4 channel で予測する。</p>
<p>つまり入力画像は、CLIP embedding として attention に入るだけではない。latent 空間でも各フレームに並べて渡される。この2経路の条件付けが、SVD の image-to-video の核になっている。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cfg-はフレーム方向に-scale-される">CFG はフレーム方向に scale される<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#cfg-%E3%81%AF%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E6%96%B9%E5%90%91%E3%81%AB-scale-%E3%81%95%E3%82%8C%E3%82%8B" class="hash-link" aria-label="Direct link to CFG はフレーム方向に scale される" title="Direct link to CFG はフレーム方向に scale される" translate="no">​</a></h2>
<p>CFG は <a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/modules/diffusionmodules/guiders.py" target="_blank" rel="noopener noreferrer" class="">LinearPredictionGuider</a> が担当する。</p>
<p>式としてはよく見る形で、</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pred = uncond + scale * (cond - uncond)</span><br></div></code></pre></div></div>
<p>である。ただし公式実装では <code>min_scale</code> と <code>max_scale</code> を持ち、フレーム方向に scale を変化させる。<code>svd.yaml</code> では <code>min_scale: 1.0</code>、<code>max_scale: 2.5</code> になっている。</p>
<p>最初から最後まで同じ guidance scale をかけるというより、動画のフレーム軸を意識した CFG になっているのが面白い。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="videounet-は-t-フレームをまとめて-denoise-する">VideoUNet は T フレームをまとめて denoise する<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#videounet-%E3%81%AF-t-%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%82%92%E3%81%BE%E3%81%A8%E3%82%81%E3%81%A6-denoise-%E3%81%99%E3%82%8B" class="hash-link" aria-label="Direct link to VideoUNet は T フレームをまとめて denoise する" title="Direct link to VideoUNet は T フレームをまとめて denoise する" translate="no">​</a></h2>
<p><a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/modules/diffusionmodules/video_model.py" target="_blank" rel="noopener noreferrer" class="">VideoUNet</a> は、見た目は2D latent を <code>[B*T, C, H, W]</code> として受け取るが、内部では <code>num_video_frames</code> を使って time 軸を復元する。</p>
<p><code>VideoResBlock</code> は一度 <code>[B, C, T, H, W]</code> のような形に戻して、時間方向の畳み込みを入れる。<a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/modules/video_attention.py" target="_blank" rel="noopener noreferrer" class="">SpatialVideoTransformer</a> も、空間方向の処理に加えて temporal attention を持っている。</p>
<p>このため、SVD は1フレームずつ逐次的に作っているわけではない。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">T フレームぶんの latent をまとめて denoise する</span><br></div></code></pre></div></div>
<p>という理解のほうが近い。画像から「次の1枚」を予測し、それをまた入力して、という autoregressive な動画生成ではない。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="decode-で-latent-から動画フレームへ戻す">decode で latent から動画フレームへ戻す<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#decode-%E3%81%A7-latent-%E3%81%8B%E3%82%89%E5%8B%95%E7%94%BB%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%81%B8%E6%88%BB%E3%81%99" class="hash-link" aria-label="Direct link to decode で latent から動画フレームへ戻す" title="Direct link to decode で latent から動画フレームへ戻す" translate="no">​</a></h2>
<p>sampler が返す <code>samples_z</code> はまだ latent なので、最後に first stage model で decode する。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">samples_z: [T, 4, H/8, W/8]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; VideoDecoder</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">samples_x: [T, 3, H, W]</span><br></div></code></pre></div></div>
<p>SVD の config では decoder に <a href="https://github.com/Stability-AI/generative-models/blob/main/sgm/modules/autoencoding/temporal_ae.py" target="_blank" rel="noopener noreferrer" class="">VideoDecoder</a> が使われる。ここでも単なる画像 VAE decoder ではなく、時間方向を意識した decoder になっている。</p>
<p>その後、値域を <code>[0, 1]</code> に戻し、watermark と safety filter を通して、<code>imageio</code> で mp4 として保存する。diffusers 版だと pipeline は frames を返し、保存は <code>export_to_video()</code> など外側の helper に任せることが多い。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="まとめ">まとめ<a href="https://haruk1y.github.io/wiki/blog/read-stable-video-diffusion#%E3%81%BE%E3%81%A8%E3%82%81" class="hash-link" aria-label="Direct link to まとめ" title="Direct link to まとめ" translate="no">​</a></h2>
<p>公式実装を1本の流れにすると、こう読める。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">入力画像を [-1, 1] tensor にする</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; value_dict を作る</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; GeneralConditioner で条件を作る</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     ├─ OpenCLIP image embedding -&gt; crossattn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     ├─ VAE image latent -&gt; concat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     └─ fps / motion / cond_aug -&gt; vector</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; CFG 用に c / uc を作る</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; random video latent を作る</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; EulerEDMSampler で denoise</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; VideoUNet 入力で video latent と image latent を concat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; temporal block / temporal attention で T フレームをまとめて処理</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; VideoDecoder でフレームに戻す</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; mp4 に保存する</span><br></div></code></pre></div></div>
<p>自分の理解として一番大事だったのは、SVD が入力画像を2つの経路で使っていることだった。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">1. CLIP embedding として cross-attention に入れる</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. VAE latent として noisy video latent に concat する</span><br></div></code></pre></div></div>
<p>そして生成される動画は、入力画像 latent の単純な延長ではなく、T フレームぶんのランダム video latent を denoise して得られる。入力画像は、その denoise の方向を強く縛る条件として効いている。</p>
<p>この見方を持ってから diffusers の <code>StableVideoDiffusionPipeline.__call__()</code> を読むと、<code>_encode_image()</code>、<code>_encode_vae_image()</code>、<code>added_time_ids</code>、<code>prepare_latents()</code>、<code>unet()</code>、<code>scheduler.step()</code> の対応がかなり見えやすくなる。</p>]]></content:encoded>
            <category>Diffusion</category>
            <category>Video Generation</category>
            <category>Stable Video Diffusion</category>
        </item>
        <item>
            <title><![CDATA[Welcome]]></title>
            <link>https://haruk1y.github.io/wiki/blog/welcome</link>
            <guid>https://haruk1y.github.io/wiki/blog/welcome</guid>
            <pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Docusaurus blogging features are powered by the blog plugin.]]></description>
            <content:encoded><![CDATA[<p><a href="https://docusaurus.io/docs/blog" target="_blank" rel="noopener noreferrer" class="">Docusaurus blogging features</a> are powered by the <a href="https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog" target="_blank" rel="noopener noreferrer" class="">blog plugin</a>.</p>
<p>Here are a few tips you might find useful.</p>
<!-- -->
<p>Simply add Markdown files (or folders) to the <code>blog</code> directory.</p>
<p>Regular blog authors can be added to <code>authors.yml</code>.</p>
<p>The blog post date can be extracted from filenames, such as:</p>
<ul>
<li class=""><code>2019-05-30-welcome.md</code></li>
<li class=""><code>2019-05-30-welcome/index.md</code></li>
</ul>
<p>A blog post folder can be convenient to co-locate blog post images:</p>
<p><img decoding="async" loading="lazy" alt="Docusaurus Plushie" src="https://haruk1y.github.io/wiki/assets/images/docusaurus-plushie-banner-a60f7593abca1e3eef26a9afa244e4fb.jpeg" width="1500" height="500" class="img_ev3q"></p>
<p>The blog supports tags as well!</p>
<p><strong>And if you don't want a blog</strong>: just delete this directory, and use <code>blog: false</code> in your Docusaurus config.</p>]]></content:encoded>
            <category>Facebook</category>
            <category>Hello</category>
            <category>Docusaurus</category>
        </item>
        <item>
            <title><![CDATA[MDX Blog Post]]></title>
            <link>https://haruk1y.github.io/wiki/blog/mdx-blog-post</link>
            <guid>https://haruk1y.github.io/wiki/blog/mdx-blog-post</guid>
            <pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Blog posts support Docusaurus Markdown features, such as MDX.]]></description>
            <content:encoded><![CDATA[<p>Blog posts support <a href="https://docusaurus.io/docs/markdown-features" target="_blank" rel="noopener noreferrer" class="">Docusaurus Markdown features</a>, such as <a href="https://mdxjs.com/" target="_blank" rel="noopener noreferrer" class="">MDX</a>.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>Use the power of React to create interactive blog posts.</p></div></div>
<!-- -->
<p>For example, use JSX to create an interactive button:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">button onClick</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">alert</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'button clicked!'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token maybe-class-name">Click</span><span class="token plain"> me</span><span class="token operator" style="color:#393A34">!</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">button</span><span class="token operator" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<button>Click me!</button>]]></content:encoded>
            <category>Docusaurus</category>
        </item>
        <item>
            <title><![CDATA[Long Blog Post]]></title>
            <link>https://haruk1y.github.io/wiki/blog/long-blog-post</link>
            <guid>https://haruk1y.github.io/wiki/blog/long-blog-post</guid>
            <pubDate>Wed, 29 May 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[This is the summary of a very long blog post,]]></description>
            <content:encoded><![CDATA[<p>This is the summary of a very long blog post,</p>
<p>Use a <code>{/*</code> <code>truncate</code> <code>*/}</code> comment to limit blog post size in the list view.</p>
<!-- -->
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>]]></content:encoded>
            <category>Hello</category>
            <category>Docusaurus</category>
        </item>
        <item>
            <title><![CDATA[First Blog Post]]></title>
            <link>https://haruk1y.github.io/wiki/blog/first-blog-post</link>
            <guid>https://haruk1y.github.io/wiki/blog/first-blog-post</guid>
            <pubDate>Tue, 28 May 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Lorem ipsum dolor sit amet...]]></description>
            <content:encoded><![CDATA[<p>Lorem ipsum dolor sit amet...</p>
<!-- -->
<p>...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet</p>]]></content:encoded>
            <category>Hola</category>
            <category>Docusaurus</category>
        </item>
    </channel>
</rss>