
<a id="lab06-opening-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#lab06-opening-001)


# LAB 06 · 真实文生图与编辑


<a id="lab06-opening-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#lab06-opening-002)

先阅读保存结果和解释，再按本册步骤选择是否运行。[在 Deepnote 阅读与运行](https://deepnote.com/project/6f83a923-d155-47be-be58-680db701ff7f/notebook/aa278b6be88e4114bdfea0de5b8aee33) · [下载 Notebook](https://codingai-lec04.pages.dev/notebooks/lab06.ipynb)


<a id="block-05587e86d0be499caf912ebeec33f7d5-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-05587e86d0be499caf912ebeec33f7d5-001)


# LAB 06 · 真实文生图与编辑


<a id="block-05587e86d0be499caf912ebeec33f7d5-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-05587e86d0be499caf912ebeec33f7d5-002)

同一个模型，为什么每次会画出不一样的图？这册从一只窗边杯子开始，把**随机起点、提示词、CFG、更新步数和编辑范围**分开观察。


<a id="block-05587e86d0be499caf912ebeec33f7d5-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-05587e86d0be499caf912ebeec33f7d5-003)

这里保存了本次实际运行的 **23 张 512×512 原始输出**：18 张文生图、3 张 img2img、2 张 inpainting。采用普通 Stable Diffusion 1.5 预训练权重，没有训练新模型，也没有用加速蒸馏模型代替 CFG 实验。三个种子全部保留；基线图在各组中复用，没有为了图好看而更换种子。原定22张完成后，仅增加一次遮罩位置的定向跟进；原来的编辑失败也保留在图阵中。


<a id="block-05587e86d0be499caf912ebeec33f7d5-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-05587e86d0be499caf912ebeec33f7d5-004)

先看图，再选一项参数重算。下载 Notebook 内嵌保存图阵，不需要 GPU 或联网即可阅读保存输出；重新生成需要依赖、模型权重和计算资源。


<a id="block-05587e86d0be499caf912ebeec33f7d5-005"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-05587e86d0be499caf912ebeec33f7d5-005)

[回到 N34 的 CFG](https://codingai-lec04.pages.dev/classroom/N34.html?view=notes) · [回到 N41 的编辑](https://codingai-lec04.pages.dev/classroom/N41.html?view=notes) · [网页实验](https://codingai-lec04.pages.dev/course/experiments/image-generation.html)


<a id="block-831aab349a644cacad673be33bafa310-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-831aab349a644cacad673be33bafa310-001)


## 固定什么，改变什么


<a id="block-831aab349a644cacad673be33bafa310-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-831aab349a644cacad673be33bafa310-002)

基线提示词：`A red ceramic mug on a wooden table beside a window, soft daylight, still life photograph`。


<a id="block-831aab349a644cacad673be33bafa310-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-831aab349a644cacad673be33bafa310-003)


| 对照 | 唯一改变项 | 其余固定 |
| --- | --- | --- |
| 随机起点 | seed = 11 / 23 / 37 | 红杯提示、CFG 4、20 步 |
| 文本条件 | red → blue | 同一种子的原始 latent、CFG 4、20 步 |
| CFG | 1 / 4 / 8 | 同一种子的原始 latent、红杯提示、20 步 |
| 步数 | 10 / 20 / 40 | 同一种子的原始 latent、红杯提示、CFG 4 |
| img2img | strength = 0.25 / 0.55 / 0.85 | 同一原图、蓝杯提示、seed 11、CFG 4、名义20步 |


<a id="block-831aab349a644cacad673be33bafa310-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-831aab349a644cacad673be33bafa310-004)

文生图不只重设随机种子，还保存并复用每个种子的**同一初始 latent**。DDIM 的 `eta=0` 固定，改变步数会改变使用的时间网格。img2img 的 strength 同时改变加入的噪声量与实际去噪区间，这正是该参数的实现含义；不能把它解释成只给图像加了一个后处理滤镜。


<a id="block-979749e2d4204fd6a27e836881fa4e09-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-979749e2d4204fd6a27e836881fa4e09-001)


## 1 · 条件相同，答案仍不唯一


<a id="block-979749e2d4204fd6a27e836881fa4e09-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-979749e2d4204fd6a27e836881fa4e09-002)

三张图只改变 seed。先指出共有的条件：杯子、木桌、窗边、日光；再找没有被条件唯一决定的构图、杯形、把手和光影。三张样本只能提供具体观察，不能估计整个模型的多样性。


<a id="block-979749e2d4204fd6a27e836881fa4e09-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-979749e2d4204fd6a27e836881fa4e09-003)

![seeds](https://codingai-lec04.pages.dev/course/experiments/image-lab/figures/seeds.png)


<a id="block-f7e74b555b21473baa736745ad422b13-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-f7e74b555b21473baa736745ad422b13-001)


## 2 · 同一起点，只把 red 改为 blue


<a id="block-f7e74b555b21473baa736745ad422b13-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-f7e74b555b21473baa736745ad422b13-002)

每一行复用同一份初始 latent，左侧是 red，右侧是 blue。颜色是我们改变的文本条件，但生成过程仍可能改变光线、边缘或局部形状。检查哪几项保持近似、哪几项跟着改变，不要把提示词差异理解为逐像素编辑指令。


<a id="block-f7e74b555b21473baa736745ad422b13-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-f7e74b555b21473baa736745ad422b13-003)

本组中，三张blue条件图都出现了蓝色杯子，但seed11同时改变了杯子的位置，seed37的杯子占画面比例也明显变大。相同噪声提供一个可比起点，并不锁死构图。


<a id="block-f7e74b555b21473baa736745ad422b13-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-f7e74b555b21473baa736745ad422b13-004)

![prompts](https://codingai-lec04.pages.dev/course/experiments/image-lab/figures/prompts.png)


<a id="block-16f70debaa8f4a2c8cb0cb3d614bfa8a-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-16f70debaa8f4a2c8cb0cb3d614bfa8a-001)


## 3 · CFG 调整条件方向的力度


<a id="block-16f70debaa8f4a2c8cb0cb3d614bfa8a-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-16f70debaa8f4a2c8cb0cb3d614bfa8a-002)

每一行保持同一份起始噪声，从左到右只改 `guidance_scale=1,4,8`。先比较杯子是否出现、颜色与材质是否贴近提示，再比较纹理、明暗与局部瑕疵；不要预设“越大越好”。


<a id="block-16f70debaa8f4a2c8cb0cb3d614bfa8a-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-16f70debaa8f4a2c8cb0cb3d614bfa8a-003)

本组的红色在较大CFG下通常更明显，但seed37在CFG4时是一只红杯，CFG8时旁边又出现大型红色容器。颜色条件更突出，并不等于物体数量、结构和构图同时更符合预期。


<a id="block-16f70debaa8f4a2c8cb0cb3d614bfa8a-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-16f70debaa8f4a2c8cb0cb3d614bfa8a-004)

本册固定 **Diffusers 0.35.1 的普通 StableDiffusionPipeline**。其实现为：


<a id="block-16f70debaa8f4a2c8cb0cb3d614bfa8a-005"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-16f70debaa8f4a2c8cb0cb3d614bfa8a-005)

`prediction = empty_text_prediction + s * (text_prediction - empty_text_prediction)`


<a id="block-16f70debaa8f4a2c8cb0cb3d614bfa8a-006"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-16f70debaa8f4a2c8cb0cb3d614bfa8a-006)

在同一噪声状态和时间点，用相同网络分别预测“空文本”和“给定文本”，再放大两者之差。**s=1 是文本条件预测本身，并非无条件生成**；这个实现只有 `s>1` 才执行双分支 CFG。`negative_prompt=""` 固定为空文本，本册没有将 `s=0` 当作无条件基线。机制见 [v0.35.1 源码 L764 与 L1054](https://github.com/huggingface/diffusers/blob/v0.35.1/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py)。


<a id="block-16f70debaa8f4a2c8cb0cb3d614bfa8a-007"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-16f70debaa8f4a2c8cb0cb3d614bfa8a-007)

![cfg](https://codingai-lec04.pages.dev/course/experiments/image-lab/figures/cfg.png)


<a id="block-7b0cffaa232946ad999d35b9c796c698-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-7b0cffaa232946ad999d35b9c796c698-001)


## 4 · 多做更新，会改变什么


<a id="block-7b0cffaa232946ad999d35b9c796c698-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-7b0cffaa232946ad999d35b9c796c698-002)

每一行保持种子、初始 latent、提示词和 CFG 不变，只改 10、20、40 步。比较结构与纹理是否稳定、哪里出现新的变化。更多步意味着更多网络调用，并不保证每一个局部都更好；这些图也不能证明一个调度器优于另一个，因为调度器没有被比较。


<a id="block-7b0cffaa232946ad999d35b9c796c698-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-7b0cffaa232946ad999d35b9c796c698-003)

看seed37：40步在杯子之外又出现了红色容器和书。它不是20步图的单纯“精细版”。一次完整采样会沿自己的时间网格推进，改变步数可以改变最后落到的答案。


<a id="block-7b0cffaa232946ad999d35b9c796c698-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-7b0cffaa232946ad999d35b9c796c698-004)

![steps](https://codingai-lec04.pages.dev/course/experiments/image-lab/figures/steps.png)


<a id="block-9ad622e667c14cf1a1b7850dc9790542-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-9ad622e667c14cf1a1b7850dc9790542-001)


## 5 · 从同一原图开始，增加改动空间


<a id="block-9ad622e667c14cf1a1b7850dc9790542-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-9ad622e667c14cf1a1b7850dc9790542-002)

左侧固定使用 seed 11 的红杯基线。后三张使用同一个蓝杯提示、同一个重置种子，只改变 img2img 的 strength。名义步数20，对应本实现实际更新区间为5、11、17步。


<a id="block-9ad622e667c14cf1a1b7850dc9790542-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-9ad622e667c14cf1a1b7850dc9790542-003)

低 strength 从接近原图的状态开始，通常更容易留下布局，但也可能不充分服从“蓝色”这一新条件；高 strength 允许更大变化。用本组图检查这两个倾向，避免把它说成必然规律。img2img 不显式保护某一区域；这与下一组的遮罩约束不同。


<a id="block-9ad622e667c14cf1a1b7850dc9790542-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-9ad622e667c14cf1a1b7850dc9790542-004)

**本组实际结果没有可靠完成“红杯变蓝”**：0.25与0.55大体保留了红杯，0.85改变了窗格，并增加了一个小杯与杯碟。变化更大并不等于指定编辑成功。三张均照实保留；下一次可以固定strength，只改CFG或提示措辞中的一项来追问原因。


<a id="block-9ad622e667c14cf1a1b7850dc9790542-005"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-9ad622e667c14cf1a1b7850dc9790542-005)

![img2img](https://codingai-lec04.pages.dev/course/experiments/image-lab/figures/img2img.png)


<a id="block-c24cad48e6da4f2986b60e361a9da848-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-c24cad48e6da4f2986b60e361a9da848-001)


## 6 · 遮罩告诉模型期望修改哪里


<a id="block-c24cad48e6da4f2986b60e361a9da848-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-c24cad48e6da4f2986b60e361a9da848-002)

白色矩形是期望编辑的区域，黑色部分作为上下文参考；这并不保证黑色区域的每个像素完全不变。这个固定几何遮罩并不是自动分割出的杯子：它覆盖画面中央，因此**没有完整包住左侧原杯**。将这张遮罩与输出一起看，区分“指定区域生成”和“自动理解并替换整个对象”。


<a id="block-c24cad48e6da4f2986b60e361a9da848-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-c24cad48e6da4f2986b60e361a9da848-003)

本例使用 Stable Diffusion 1.5 的专用 inpainting 权重、蓝杯提示、seed 11、CFG 4、20 步。展示的是模型原始输出，**没有将遮罩外像素再贴回原图**。遮罩外变化也保留并单独计算；这不是 RePaint 的算法复现。


<a id="block-c24cad48e6da4f2986b60e361a9da848-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-c24cad48e6da4f2986b60e361a9da848-004)

上排是原定中央遮罩：实际输出主要重建了窗格，左侧杯子仍为红色。看到这个失败后，下排增加一次定向跟进：仅将矩形改为完整包住杯子的范围，其余参数保持相同。两张输出都展示，以便判断遮罩是否真正覆盖了想改的对象；这不是把第二张伪装成预先成功的案例。


<a id="block-c24cad48e6da4f2986b60e361a9da848-005"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-c24cad48e6da4f2986b60e361a9da848-005)

跟进结果确实重画了杯子：红杯变成了形状不同的白杯，**仍然没有满足提示中的蓝色**。这里分别看到了区域修改生效与颜色条件未满足；“模型运行成功”“目标区域发生变化”和“编辑目标达成”是三个不同判断。


<a id="block-c24cad48e6da4f2986b60e361a9da848-006"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-c24cad48e6da4f2986b60e361a9da848-006)

![inpainting](https://codingai-lec04.pages.dev/course/experiments/image-lab/figures/inpainting.png)


<a id="block-0adab4bb11b9497692005e4af6daa142-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-0adab4bb11b9497692005e4af6daa142-001)


## 把观察变成一个可检验的判断


<a id="block-0adab4bb11b9497692005e4af6daa142-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-0adab4bb11b9497692005e4af6daa142-002)

选一组图，写下三句话：这次只改了什么；你在图中实际看到什么；哪一个更强的结论还不能据此得出。比如“这个种子下 CFG 8 的红色更明显”并不能推出“提高 CFG 总能提高质量”。


<a id="block-0adab4bb11b9497692005e4af6daa142-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-0adab4bb11b9497692005e4af6daa142-003)

若要继续，先固定一张基线，只改一个参数并保存结果。给出原图、参数和输出，比单独发一张“好看”图片更容易让同学或 AI 帮你判断。


<a id="block-8b48b9a25b3b4b0bb5f420b032f2d43c-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-8b48b9a25b3b4b0bb5f420b032f2d43c-001)


## 运行来源与重算边界


<a id="block-8b48b9a25b3b4b0bb5f420b032f2d43c-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-8b48b9a25b3b4b0bb5f420b032f2d43c-002)

这23张图实际运行于 **本地执行环境的 CPU**（8核配额、8GiB内存、AMD EPYC 9V74，bfloat16），不是Deepnote GPU运行。保存结果时间为 `2026-09-29T00:01:01.907763+00:00` 至 `2026-09-29T00:25:23.473353+00:00`；逐图用时、参数、初始latent哈希、图片SHA256和安全检查结果见 `results/manifest.json`。总推理时间约 **21.5 分钟**，不含首次模型下载；其他设备的耗时可能不同。


<a id="block-8b48b9a25b3b4b0bb5f420b032f2d43c-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-8b48b9a25b3b4b0bb5f420b032f2d43c-003)


| 项目 | 固定值 |
| --- | --- |
| 文生图 / img2img | `stable-diffusion-v1-5/stable-diffusion-v1-5` |
| 权重 revision | `451f4fe16113bff5a5d2269ed5ad43b0592e9a14` |
| Inpainting | `stable-diffusion-v1-5/stable-diffusion-inpainting` |
| Inpainting revision | `8a4288a76071f7280aedbdb3253bdb9e9d5d84bb` |
| 主要依赖 | PyTorch 2.8.0+cpu / Diffusers 0.35.1 / Transformers 4.55.4 |
| 数值与采样 | CPU bfloat16 / DDIM / eta=0 / 512×512 |


<a id="block-8b48b9a25b3b4b0bb5f420b032f2d43c-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-8b48b9a25b3b4b0bb5f420b032f2d43c-004)

Deepnote 原有环境探测run `3f9e0c0d-2c7b-4ca8-b6d0-8a03f35aa48e` 显示2核CPU配额、5GiB内存、CUDA不可用。该run只证明探测成功，不能当作生成成功。这里将本地生成结果同步为可阅读教材，默认不在云端重复消耗推理时间。


<a id="block-8b48b9a25b3b4b0bb5f420b032f2d43c-005"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-8b48b9a25b3b4b0bb5f420b032f2d43c-005)

精确种子不保证跨设备、数值精度或软件版本逐像素一致。重新计算时记录实际环境；不要将不同精度生成的图与本组混成一个“单变量”对照。


<a id="block-8b48b9a25b3b4b0bb5f420b032f2d43c-006"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-8b48b9a25b3b4b0bb5f420b032f2d43c-006)

来源：[SD1.5 模型卡](https://huggingface.co/stable-diffusion-v1-5/stable-diffusion-v1-5) · [inpainting 模型卡](https://huggingface.co/stable-diffusion-v1-5/stable-diffusion-inpainting) · [Diffusers img2img 说明](https://huggingface.co/docs/diffusers/v0.35.1/en/api/pipelines/stable_diffusion/img2img)。


<a id="block-ea3a99e38eec40fd87e7cf263c943c41-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-ea3a99e38eec40fd87e7cf263c943c41-001)


## 可选重算：先选一张


<a id="block-ea3a99e38eec40fd87e7cf263c943c41-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-ea3a99e38eec40fd87e7cf263c943c41-002)

阅读保存图不需要安装推理依赖。若要重算，在已具备计算资源的Python环境中安装下面的固定依赖，选择 `TASK`，再把 `RUN_RECOMPUTE` 改为 `True`。代码只生成所选任务的一张图，不会默认再跑完整23张。


<a id="block-ea3a99e38eec40fd87e7cf263c943c41-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-ea3a99e38eec40fd87e7cf263c943c41-003)

安装示例：`pip install torch==2.8.0 diffusers==0.35.1 transformers==4.55.4 accelerate==1.10.1 huggingface-hub==0.34.4 safetensors==0.6.2`。PyTorch安装包应与自己的设备匹配；CPU结果是本册的基线。初次运行会下载模型权重并写入缓存，输出保存在 `student-results/`。这不需要付费图像API，但会占用所在环境的存储和计算资源。


<a id="block-ea3a99e38eec40fd87e7cf263c943c41-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-ea3a99e38eec40fd87e7cf263c943c41-004)

`TASK` 可选 `txt2img`、`img2img` 或 `inpainting`。文生图中固定其他参数，只改 `SEED`、`PROMPT`、`CFG`、`STEPS` 中的一项；img2img 先只改 `STRENGTH`。编辑任务使用保存的 seed 11 原图，自动核对其 SHA256；遮罩仍为同一几何矩形。不要同时改多项再将变化归因于其中一个。


<a id="block-6e64785c187c42ddbed67f4f0ef8bedd-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-6e64785c187c42ddbed67f4f0ef8bedd-001)

查看可执行代码
```
RUN_RECOMPUTE = False
TASK = "txt2img"  # txt2img | img2img | inpainting
SEED = 11
PROMPT = "A red ceramic mug on a wooden table beside a window, soft daylight, still life photograph"
CFG = 4.0
STEPS = 20
STRENGTH = 0.55  # used only by img2img; edit tasks use the blue-mug prompt below
MASK_RECT = (24,272,208,472)  # covers the mug; original center mask was (160,160,352,400)

if RUN_RECOMPUTE:
    import json, time, platform, hashlib, io, urllib.request
    from pathlib import Path
    from datetime import datetime, timezone
    import torch, diffusers, transformers
    from diffusers import StableDiffusionPipeline, StableDiffusionImg2ImgPipeline, StableDiffusionInpaintPipeline, DDIMScheduler
    from PIL import Image, ImageDraw
    from IPython.display import display
    target = Path("student-results"); target.mkdir(exist_ok=True)
    torch.set_num_threads(2)  # respect the inspected Deepnote CPU quota
    tasks = {"txt2img":StableDiffusionPipeline,"img2img":StableDiffusionImg2ImgPipeline,"inpainting":StableDiffusionInpaintPipeline}
    assert TASK in tasks
    model = "stable-diffusion-v1-5/stable-diffusion-inpainting" if TASK=="inpainting" else "stable-diffusion-v1-5/stable-diffusion-v1-5"
    revision = "8a4288a76071f7280aedbdb3253bdb9e9d5d84bb" if TASK=="inpainting" else "451f4fe16113bff5a5d2269ed5ad43b0592e9a14"
    pipe = tasks[TASK].from_pretrained(
        model, revision=revision,
        variant="fp16", torch_dtype=torch.bfloat16, use_safetensors=True,
        low_cpu_mem_usage=True,
    ).to("cpu")
    pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config)
    inputs={"negative_prompt":"","num_inference_steps":STEPS,"guidance_scale":CFG,"eta":0.0,
            "generator":torch.Generator("cpu").manual_seed(SEED)}
    source_hash=None
    if TASK=="txt2img":
        initial=torch.randn((1,4,64,64),generator=torch.Generator("cpu").manual_seed(SEED),dtype=torch.float32).to(torch.bfloat16)
        inputs.update(prompt=PROMPT,latents=initial.clone(),height=512,width=512)
    else:
        url="https://codingai-lec04.pages.dev/course/experiments/image-lab/component-trace/component-trace-final.png"
        filename="txt-baseline-seed-11.png"
        cwd=Path.cwd()
        # Saved images are readable offline; recomputing still needs dependencies, weights, and compute.
        local_candidates=[
            cwd / "results" / filename,
            cwd / "textbook/course/experiments/image-lab/results" / filename,
            cwd / "course/experiments/image-lab/results" / filename,
            cwd.parent / "textbook/course/experiments/image-lab/results" / filename,
            cwd.parent / "course/experiments/image-lab/results" / filename,
        ]
        local_source=next((candidate for candidate in local_candidates if candidate.exists()),None)
        # A local read or checksum error must surface, never fall back to the network.
        raw=local_source.read_bytes() if local_source is not None else urllib.request.urlopen(url,timeout=30).read()
        source_hash=hashlib.sha256(raw).hexdigest()
        if source_hash!="525551fbaae476dabd9ff73777114231896dccfabfb3d85be50c22889710ce9c":
            raise ValueError(f"Original image differs from the saved experiment: {local_source if local_source is not None else url}")
        source=Image.open(io.BytesIO(raw)).convert("RGB")
        inputs.update(prompt=PROMPT.replace("red","blue"),image=source)
        if TASK=="img2img":inputs["strength"]=STRENGTH
        else:
            mask=Image.new("L",(512,512),0)
            ImageDraw.Draw(mask).rectangle(MASK_RECT,fill=255)
            inputs.update(mask_image=mask,height=512,width=512,strength=1.0)
    started = datetime.now(timezone.utc).isoformat(); t0=time.perf_counter()
    path=target / f"{TASK}-seed-{SEED}-cfg-{CFG}-steps-{STEPS}-{time.time_ns()}.png"
    parameters={"task":TASK,"model":model,"revision":revision,"seed":SEED,"prompt":inputs["prompt"],"negative_prompt":"","cfg":CFG,"steps":STEPS,
                "strength":inputs.get("strength"),"source_sha256":source_hash,
                "started_at":started,"status":"running","width":512,"height":512,
                "effective_steps":int(STEPS*STRENGTH) if TASK=="img2img" else STEPS,
                "scheduler":type(pipe.scheduler).__name__,"scheduler_config":dict(pipe.scheduler.config),"eta":0.0,
                "torch":torch.__version__,"diffusers":diffusers.__version__,
                "transformers":transformers.__version__,"device":"cpu","dtype":"bfloat16"}
    if TASK=="txt2img":
        latent_path=path.with_suffix(".latent.pt");torch.save(initial,latent_path)
        parameters["initial_latent_sha256"]=hashlib.sha256(latent_path.read_bytes()).hexdigest()
        parameters["initial_latent_tensor_sha256"]=hashlib.sha256(initial.view(torch.uint8).numpy().tobytes()).hexdigest()
    if TASK=="inpainting":
        mask_path=path.with_suffix(".mask.png");mask.save(mask_path)
        parameters.update(mask_rectangle=list(MASK_RECT),mask_sha256=hashlib.sha256(mask_path.read_bytes()).hexdigest(),post_composited=False)
    path.with_suffix(".json").write_text(json.dumps(parameters,ensure_ascii=False,indent=2))
    try:
        result=pipe(**inputs)
        result.images[0].save(path)
        parameters.update(status="success",seconds=time.perf_counter()-t0,
                          safety_checker=result.nsfw_content_detected,
                          sha256=hashlib.sha256(path.read_bytes()).hexdigest())
    except Exception as exc:
        parameters.update(status="failed",seconds=time.perf_counter()-t0,error_type=type(exc).__name__,error=str(exc))
        path.with_suffix(".json").write_text(json.dumps(parameters,ensure_ascii=False,indent=2))
        raise
    path.with_suffix(".json").write_text(json.dumps(parameters,ensure_ascii=False,indent=2))
    display(result.images[0]); print(parameters)
else:
    print("保存图可直接阅读。RUN_RECOMPUTE=False，未启动模型推理。")

```

<a id="block-6e64785c187c42ddbed67f4f0ef8bedd-output-0-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-6e64785c187c42ddbed67f4f0ef8bedd-output-0-001)

查看保存的计算输出
```
保存图可直接阅读。RUN_RECOMPUTE=False，未启动模型推理。

```

<a id="block-e116afd609064b529127aed2c8c8c013-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-e116afd609064b529127aed2c8c8c013-001)

查看可执行代码
```
# 环境核验：不运行生成模型，不读取密钥或环境变量。
import json, os, platform, importlib.util
from pathlib import Path
report={"python":platform.python_version(),"visible_cpu_count":os.cpu_count()}
for filename in ["/sys/fs/cgroup/cpu.max","/sys/fs/cgroup/memory.max"]:
    p=Path(filename)
    if p.exists():report[filename]=p.read_text().strip()
if importlib.util.find_spec("torch"):
    import torch
    report.update(torch_version=torch.__version__,cuda_available=torch.cuda.is_available())
else:
    report.update(torch_version="not installed",cuda_available="not checked")
print(json.dumps(report,indent=2))

```

<a id="block-e116afd609064b529127aed2c8c8c013-output-0-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-e116afd609064b529127aed2c8c8c013-output-0-001)

查看保存的计算输出
```
{
  "python": "3.13.12",
  "visible_cpu_count": 16,
  "/sys/fs/cgroup/cpu.max": "200000 100000",
  "/sys/fs/cgroup/memory.max": "5368709120",
  "torch_version": "2.12.0+cu130",
  "cuda_available": false
}

```

<a id="block-aede6bebe0974b0e8085bac28ea371d2-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-001)


## 7 · 跟着一次真实生成走过组件


<a id="block-aede6bebe0974b0e8085bac28ea371d2-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-002)

这张红杯图使用本册已有的 SD1.5 固定权重、seed 11 保存的**同一份初始 latent**、空负提示、CFG 4、DDIM 20 步与 eta=0。此次只增加观测钩子，仍由原版 Diffusers 0.35.1 管线完成生成；钩子没有替换任何模型返回值。CPU bfloat16 实际推理用时约 80.8 秒，与历史 seed 11 基线的 PNG 逐字节一致。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-003)

![同一次推理的两支预测、CFG、scheduler更新与最终图](https://codingai-lec04.pages.dev/course/experiments/image-lab/component-trace/component-trace-first-step.png)


<a id="block-aede6bebe0974b0e8085bac28ea371d2-004"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-004)

先读上排：**三块都是同一个时间点、同一份 latent 的噪声预测**。空文本分支与红杯提示分支由同一 U-Net 产生，不是两张已经生成完的照片。右上是两支预测的组合。再读左下两块：scheduler 接收组合后的预测，才把 latent 更新到下一状态。最后的杯子图经过完整 20 步与 VAE 解码才出现。热图固定展示第 0 通道；它不是“模型脑中正在画的照片”。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-005"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-005)


### 文本如何进入：token 不直接变成像素


<a id="block-aede6bebe0974b0e8085bac28ea371d2-006"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-006)

真实调用先把提示分词并补齐到 `1 × 77` 的 token ID 张量；这条提示连首尾标记共有 20 个位置，其余是补齐位置。开头读作 `<|startoftext|> · a</w> · red</w> · ceramic</w> · mug</w> · on</w> · a</w> · wooden</w> · table</w> · beside</w> · a</w> · window</w>`。文本编码器输出 `1 × 77 × 768` 的表示：1 条提示、77 个位置、每个位置 768 个数。空文本也经过编码，**空文本表示并不是全零向量**。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-007"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-007)

两支文本表示堆成 `2 × 77 × 768` 输入 U-Net；相同 latent 复制成两份，实际输入形状是 `2 × 4 × 64 × 64`。首维 2 表示空文本与有文本两支，不是两次独立随机起点。模型使用这些文本表示影响当前噪声预测，提示没有被当成绘图命令逐词粘贴到画面上。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-008"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-008)


### 同一步怎样组合：看真实返回的一个位置


<a id="block-aede6bebe0974b0e8085bac28ea371d2-009"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-009)

第一步的时间标号为 `951`。在预先固定的坐标 `[batch=0, channel=0, y=16, x=16]`，真实值如下：


<a id="block-aede6bebe0974b0e8085bac28ea371d2-010"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-010)


| 值 | 实际返回 |
| --- | --- |
| 更新前 latent | 0.151367 |
| 空文本噪声预测 | 0.171875 |
| 提示条件噪声预测 | 0.166016 |
| CFG 4 组合后的预测 | 0.148438 |
| scheduler 更新后的 latent | 0.152344 |


<a id="block-aede6bebe0974b0e8085bac28ea371d2-011"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-011)

组合规则是 `empty + 4 × (prompt − empty)`。保存的全部 20 步都核对了它与管线真正交给 scheduler 的张量逐项一致。显示值来自 bfloat16；如果先在普通十进制计算器上无限精度算完再舍入，末位可能不同，因为实际张量在运算过程中就有舍入。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-012"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-012)

**“上排几张看起来差不多，是不是 guidance 没作用？”** 首步仍接近高噪声状态，大的共同噪声结构遮住了较小的条件差异。下面直接把真实返回值相减：左图是两支之差，中图是 CFG 相对空文本分支的改变量，右图是 scheduler 前后之差。各图有自己的色标，不能根据颜色深浅跨图比较大小；圆圈就是上表的坐标。读这个差异，再回到最后的红杯图，才能把一步运算与完整生成过程分开。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-013"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-013)

![真实预测与状态的差分，标出算例坐标](https://codingai-lec04.pages.dev/course/experiments/image-lab/component-trace/component-trace-differences.png)


<a id="block-aede6bebe0974b0e8085bac28ea371d2-014"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-014)

**为什么提示已经写清楚，还要 guidance？** 文本分支已经使用提示，但模型的单次条件预测仍兼顾从数据学到的其他常见结构。CFG 用“有提示与空提示的差”加强这一次采样的条件影响。它没有新的真值传感器，也不逐条检查“是否恰好一只杯子”。回看本册 CFG 图阵：更强的红色与额外出现一个容器可以同时发生；颜色条件更突出，不代表所有要求同时更正确。本实现 `s=1` 正好取条件分支的预测，不能称为无条件图像。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-015"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-015)


### scheduler 做什么：预测器给方向，规则决定怎样更新


<a id="block-aede6bebe0974b0e8085bac28ea371d2-016"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-016)

U-Net 输出噪声预测，DDIM scheduler 根据当前时间、噪声日程与当前 latent 计算下一状态。第一步的 latent 均值从 `0.0065875` 变成 `0.00340301`，这次更新的 L2 范数是 `1.6986`。这些量说明更新确实发生，不能直接解释为图像“质量提高了多少”。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-017"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-017)

本次时间网格为 `951, 901, 851, 801, 751, 701, 651, 601, 551, 501, 451, 401, 351, 301, 251, 201, 151, 101, 51, 1`。20 步不是连续调用同一个 t，也不是每一步都重新抽一张独立噪声。重放保存的首步预测与输入，官方 DDIM 实现得到的下一状态与捕获值逐项相同。改变步数会改变时间网格，因此 40 步输出不一定只是 20 步图的精修。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-018"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-018)


### VAE 何时使用：普通文生图没有输入照片


<a id="block-aede6bebe0974b0e8085bac28ea371d2-019"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-019)

起点是 `1 × 4 × 64 × 64` 的随机 latent，20 步后仍是 `1 × 4 × 64 × 64`。管线先按模型规定除以缩放因子 `0.18215`，再送入 VAE decoder，实际返回 `1 × 3 × 512 × 512` 的 RGB 张量，经图像后处理保存成 512 × 512 图片。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-020"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-020)

此次真实追踪中 **VAE encode 调用数为 0**。原因很简单：没有一张输入照片需要压缩；文生图直接在随机 latent 上开始去噪。img2img 才需要把已有图片编码到 latent，再按 strength 加噪与去噪。用这里的预训练 VAE 做“原图→压缩→重建”可以观察压缩器，但那不等于 LAB07 中从数据训练一个图像 VAE 并从其先验采样。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-021"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-021)


### 自检：把四个动作放回各自组件


<a id="block-aede6bebe0974b0e8085bac28ea371d2-022"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-022)


- “把 red 改成 blue”首先改变什么？**token 与文本表示，继而影响 U-Net 的条件预测。**它不保证其余像素固定。

- “CFG 从 4 改成 8”直接改动什么？**同一步两支预测的组合系数。**没有重新训练权重。

- “20 步改成 40 步”会不会只增加相同计算的次数？**不会；scheduler 的时间网格也改变。**

- 为什么文生图没有 VAE encode，却有 VAE decode？**随机 latent 是起点，最后需要把 latent 转回像素。**


<a id="block-aede6bebe0974b0e8085bac28ea371d2-023"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-023)

本段的图和数值是保存结果，网页中的查看与展开不会现场生成。真正重算用同目录 `run_component_trace.py`；原始张量、逐步摘要与核验结果分别保存在 `component-tensors.pt`、`trace.json` 与 `verification.json`。这次运行没有重训 SD1.5，也没有重跑整套历史图阵。


<a id="block-aede6bebe0974b0e8085bac28ea371d2-024"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-aede6bebe0974b0e8085bac28ea371d2-024)

实现依据：[Diffusers 0.35.1 StableDiffusionPipeline](https://github.com/huggingface/diffusers/blob/v0.35.1/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py) · [同版本 DDIMScheduler](https://github.com/huggingface/diffusers/blob/v0.35.1/src/diffusers/schedulers/scheduling_ddim.py)。


<a id="block-825f96db6f8e4ebb8c8ee676fa0a14e1-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-825f96db6f8e4ebb8c8ee676fa0a14e1-001)


### 可选重算与核验


<a id="block-825f96db6f8e4ebb8c8ee676fa0a14e1-002"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-825f96db6f8e4ebb8c8ee676fa0a14e1-002)

默认阅读保存结果即可。若要生成新的组件轨迹，在具备原 LAB06 固定依赖与约 3 GB 权重缓存的 Python 环境中下载 `run_component_trace.py`，把原 LAB06 的 `initial-latent-seed-11.pt` 和基线图放进 `source-results/`。运行 `python run_component_trace.py --source-results source-results --output my-component-trace`。首次执行下载同一权重，随后只做一次 20 步推理。脚本遇到已有 trace.json 会停止，以免覆盖观察。


<a id="block-825f96db6f8e4ebb8c8ee676fa0a14e1-003"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-825f96db6f8e4ebb8c8ee676fa0a14e1-003)

再运行 `python verify_component_trace.py my-component-trace`；核验只重放保存张量的 CFG 与 scheduler 运算，不再调用生成模型。下载链接：[推理脚本](https://codingai-lec04.pages.dev/course/experiments/image-lab/component-trace/run_component_trace.py) · [核验脚本](https://codingai-lec04.pages.dev/course/experiments/image-lab/component-trace/verify_component_trace.py) · [真实轨迹 JSON](https://codingai-lec04.pages.dev/course/experiments/image-lab/component-trace/trace.json) · [核验结果](https://codingai-lec04.pages.dev/course/experiments/image-lab/component-trace/verification.json)


<a id="block-0997d48863a5475a813950692e4e276b-001"></a>
[区块原文](https://codingai-lec04.pages.dev/course/notebooks/aa278b6be88e4114bdfea0de5b8aee33.html#block-0997d48863a5475a813950692e4e276b-001)

查看可执行代码
```
RUN_COMPONENT_TRACE = False  # 阅读保存结果不需要开启
if RUN_COMPONENT_TRACE:
    import hashlib, json, subprocess, sys, urllib.request
    from pathlib import Path
    from IPython.display import display, Image
    target = Path("student-component-trace")
    target.mkdir(exist_ok=True)
    source = target / "source-results"
    source.mkdir(exist_ok=True)
    base = "https://codingai-lec04.pages.dev/course/experiments/image-lab/"
    files = [['component-trace/run_component_trace.py', 'run_component_trace.py', 'f240d4e340275d799f304ac8623a4ff78136641987f13b1b5f8c9111b4944aec'], ['component-trace/verify_component_trace.py', 'verify_component_trace.py', '0e0b005dfc1981013c774935f8ad437a49b11cf895b5af5a53672a47703bbcb1'], ['results/initial-latent-seed-11.pt', 'source-results/initial-latent-seed-11.pt', '5dcb0b9f4d7b65d2458eae198fedc392f08d61d32defd303f465b779fd0945e1'], ['results/txt-baseline-seed-11.png', 'source-results/txt-baseline-seed-11.png', '525551fbaae476dabd9ff73777114231896dccfabfb3d85be50c22889710ce9c']]
    for url_suffix, relative_path, expected_sha256 in files:
        path = target / relative_path
        if not path.exists():
            path.write_bytes(urllib.request.urlopen(base + url_suffix, timeout=60).read())
        assert hashlib.sha256(path.read_bytes()).hexdigest() == expected_sha256, path
    # 使用上文原 LAB06 固定依赖；首次下载权重需要网络与存储。
    subprocess.run([sys.executable, str(target/"run_component_trace.py"),
                    "--source-results", str(source), "--output", str(target/"results"),
                    "--threads", "2"], check=True)
    subprocess.run([sys.executable, str(target/"verify_component_trace.py"),
                    str(target/"results")], check=True)
    trace = json.loads((target/"results/trace.json").read_text())
    print("Actual status:", trace["status"], "Real scheduler updates:", len(trace["steps"]))
    display(Image(filename=str(target/"results/component-trace-final.png")))
else:
    print("保存的真实组件轨迹可直接阅读；本单元尚未启动重算。")

```
