<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Diffusion on Tarragon</title><link>https://tarrragon.github.io/blog/tags/diffusion/</link><description>Recent content in Diffusion on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 12 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/diffusion/index.xml" rel="self" type="application/rss+xml"/><item><title>Hands-on：安裝 ComfyUI + SDXL base</title><link>https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/comfyui-setup/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/comfyui-setup/</guid><description>&lt;p>本篇紀錄裝 ComfyUI 跟 Stable Diffusion XL base 模型、在 Apple Silicon Mac 上跑通最小 text-to-image 流程。ComfyUI 是 2026 年 Apple Silicon 跑 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion&lt;/a> 最主流的選擇——節點式工作流（拖拉節點連線、像 visual programming、每個節點負責一段運算）、跨平台、Python 環境、容易客製化。Draw Things（Mac 原生 GUI）更簡單、但 ComfyUI 接 workflow 跟 custom node 的能力強很多。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>驗證日期&lt;/strong>：2026-05-12
&lt;strong>ComfyUI&lt;/strong>：main branch、shallow clone
&lt;strong>示範模型&lt;/strong>：Stable Diffusion XL base 1.0（6.5 GB、&lt;code>stabilityai/stable-diffusion-xl-base-1.0&lt;/code>）
&lt;strong>Python&lt;/strong>：3.14（venv 隔離、不污染系統）&lt;/p>&lt;/blockquote>
&lt;h2 id="前置設定">前置設定&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>項目&lt;/th>
 &lt;th>檢查指令&lt;/th>
 &lt;th>預期&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Git&lt;/td>
 &lt;td>&lt;code>which git&lt;/code>&lt;/td>
 &lt;td>&lt;code>/usr/bin/git&lt;/code> 或 brew 版&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Python 3.10+&lt;/td>
 &lt;td>&lt;code>python3 --version&lt;/code>&lt;/td>
 &lt;td>3.10 ~ 3.14 都可、本 demo 用 3.14&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>磁碟空間&lt;/td>
 &lt;td>&lt;code>df -h ~&lt;/code>&lt;/td>
 &lt;td>至少 15 GB（runtime 3 GB + SDXL 6.5 GB + cache）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/unified-memory/" data-link-title="Unified Memory Architecture" data-link-desc="Apple Silicon 讓 CPU / GPU / NE 共用同一塊記憶體：跑大模型的優勢來源">統一記憶體&lt;/a>&lt;/td>
 &lt;td>&lt;code>system_profiler SPHardwareDataType | grep Memory&lt;/code>&lt;/td>
 &lt;td>至少 16 GB、推薦 32 GB+&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>ComfyUI 在 Apple Silicon 跑 Diffusion 用 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/gpu-compute-backend/" data-link-title="GPU Compute Backend" data-link-desc="GPU 加速計算的底層 API 介面（CUDA / ROCm / Vulkan / Metal / SYCL）、決定推論軟體能否用 GPU 跑得快">MPS（Metal Performance Shaders）backend&lt;/a>、不需要 NVIDIA CUDA。但跑 SDXL 至少要 12 GB 統一記憶體留給 model + activation、16 GB Mac 跟其他 app 一起會吃緊。&lt;/p>
&lt;h2 id="clone-comfyui">Clone ComfyUI&lt;/h2>
&lt;p>放在 &lt;code>~/Projects/&lt;/code> 下、跟其他 dev project 同層：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/Projects
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">git clone --depth &lt;span class="m">1&lt;/span> https://github.com/comfyanonymous/ComfyUI.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ComfyUI&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>--depth 1&lt;/code> 只拉最新 commit、不拉全部歷史、省幾百 MB。要追歷史 / submit PR 才需要 full clone。&lt;/p>
&lt;p>ComfyUI 目錄結構（核心部分）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">ComfyUI/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── main.py # 啟動 entry point
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── server.py # HTTP server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── nodes.py # 內建節點實作
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">├── custom_nodes/ # 第三方 / 客製節點放這
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">├── models/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">│ ├── checkpoints/ # SD / SDXL 主 model 檔放這
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ ├── loras/ # LoRA 微調權重
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ ├── vae/ # VAE 模型
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ ├── controlnet/ # ControlNet 模型
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ └── ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">├── output/ # 生成的圖
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">├── input/ # 拖進 ComfyUI 的圖片
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">└── requirements.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="建-venv--裝-dependencies">建 venv + 裝 dependencies&lt;/h2>
&lt;p>ComfyUI requirements 含 PyTorch、numpy、PIL、safetensors、einops 等、套件多、版本敏感。用 venv 隔離：&lt;/p></description><content:encoded><![CDATA[<p>本篇紀錄裝 ComfyUI 跟 Stable Diffusion XL base 模型、在 Apple Silicon Mac 上跑通最小 text-to-image 流程。ComfyUI 是 2026 年 Apple Silicon 跑 <a href="/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion</a> 最主流的選擇——節點式工作流（拖拉節點連線、像 visual programming、每個節點負責一段運算）、跨平台、Python 環境、容易客製化。Draw Things（Mac 原生 GUI）更簡單、但 ComfyUI 接 workflow 跟 custom node 的能力強很多。</p>
<blockquote>
<p><strong>驗證日期</strong>：2026-05-12
<strong>ComfyUI</strong>：main branch、shallow clone
<strong>示範模型</strong>：Stable Diffusion XL base 1.0（6.5 GB、<code>stabilityai/stable-diffusion-xl-base-1.0</code>）
<strong>Python</strong>：3.14（venv 隔離、不污染系統）</p></blockquote>
<h2 id="前置設定">前置設定</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>檢查指令</th>
          <th>預期</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Git</td>
          <td><code>which git</code></td>
          <td><code>/usr/bin/git</code> 或 brew 版</td>
      </tr>
      <tr>
          <td>Python 3.10+</td>
          <td><code>python3 --version</code></td>
          <td>3.10 ~ 3.14 都可、本 demo 用 3.14</td>
      </tr>
      <tr>
          <td>磁碟空間</td>
          <td><code>df -h ~</code></td>
          <td>至少 15 GB（runtime 3 GB + SDXL 6.5 GB + cache）</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/unified-memory/" data-link-title="Unified Memory Architecture" data-link-desc="Apple Silicon 讓 CPU / GPU / NE 共用同一塊記憶體：跑大模型的優勢來源">統一記憶體</a></td>
          <td><code>system_profiler SPHardwareDataType | grep Memory</code></td>
          <td>至少 16 GB、推薦 32 GB+</td>
      </tr>
  </tbody>
</table>
<p>ComfyUI 在 Apple Silicon 跑 Diffusion 用 <a href="/blog/llm/knowledge-cards/gpu-compute-backend/" data-link-title="GPU Compute Backend" data-link-desc="GPU 加速計算的底層 API 介面（CUDA / ROCm / Vulkan / Metal / SYCL）、決定推論軟體能否用 GPU 跑得快">MPS（Metal Performance Shaders）backend</a>、不需要 NVIDIA CUDA。但跑 SDXL 至少要 12 GB 統一記憶體留給 model + activation、16 GB Mac 跟其他 app 一起會吃緊。</p>
<h2 id="clone-comfyui">Clone ComfyUI</h2>
<p>放在 <code>~/Projects/</code> 下、跟其他 dev project 同層：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> ~/Projects
</span></span><span class="line"><span class="ln">2</span><span class="cl">git clone --depth <span class="m">1</span> https://github.com/comfyanonymous/ComfyUI.git
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">cd</span> ComfyUI</span></span></code></pre></div><p><code>--depth 1</code> 只拉最新 commit、不拉全部歷史、省幾百 MB。要追歷史 / submit PR 才需要 full clone。</p>
<p>ComfyUI 目錄結構（核心部分）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">ComfyUI/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── main.py              # 啟動 entry point
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── server.py            # HTTP server
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── nodes.py             # 內建節點實作
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── custom_nodes/        # 第三方 / 客製節點放這
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── models/
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│   ├── checkpoints/     # SD / SDXL 主 model 檔放這
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   ├── loras/           # LoRA 微調權重
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│   ├── vae/             # VAE 模型
</span></span><span class="line"><span class="ln">10</span><span class="cl">│   ├── controlnet/      # ControlNet 模型
</span></span><span class="line"><span class="ln">11</span><span class="cl">│   └── ...
</span></span><span class="line"><span class="ln">12</span><span class="cl">├── output/              # 生成的圖
</span></span><span class="line"><span class="ln">13</span><span class="cl">├── input/               # 拖進 ComfyUI 的圖片
</span></span><span class="line"><span class="ln">14</span><span class="cl">└── requirements.txt</span></span></code></pre></div><h2 id="建-venv--裝-dependencies">建 venv + 裝 dependencies</h2>
<p>ComfyUI requirements 含 PyTorch、numpy、PIL、safetensors、einops 等、套件多、版本敏感。用 venv 隔離：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> ~/Projects/ComfyUI
</span></span><span class="line"><span class="ln">2</span><span class="cl">python3 -m venv venv
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">source</span> venv/bin/activate
</span></span><span class="line"><span class="ln">4</span><span class="cl">python --version  <span class="c1"># 確認在 venv 內</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">pip install --upgrade pip</span></span></code></pre></div><p>裝 dependencies：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pip install -r requirements.txt</span></span></code></pre></div><p>實測時間：10-15 分鐘（torch + 各種 dep）、首次跑會編譯部分 C extension。完成後預期看到：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Successfully installed Mako-... MarkupSafe-... Pillow-... PyOpenGL-... ...
</span></span><span class="line"><span class="ln">2</span><span class="cl">  torch-... torchvision-... torchaudio-... ...
</span></span><span class="line"><span class="ln">3</span><span class="cl">  safetensors-... transformers-... ...</span></span></code></pre></div><p>驗證 PyTorch + MPS：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python -c <span class="s2">&#34;import torch; print(&#39;torch:&#39;, torch.__version__, &#39;mps:&#39;, torch.backends.mps.is_available())&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># torch: 2.x.x mps: True</span></span></span></code></pre></div><p><code>mps: True</code> 表示 Apple Silicon GPU 加速可用。</p>
<h2 id="下載-sdxl-base-模型">下載 SDXL base 模型</h2>
<p>SDXL base 約 6.5 GB、是 Stable Diffusion XL 的基礎 model。從 Hugging Face 拉到 ComfyUI 的 <code>models/checkpoints/</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir -p ~/Projects/ComfyUI/models/checkpoints
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">cd</span> ~/Projects/ComfyUI/models/checkpoints
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># -L 跟 redirect、--continue-at - 支援中斷後重續、避免 6.5 GB 重下</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">curl -L --continue-at - -o sd_xl_base_1.0.safetensors <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors?download=true&#34;</span></span></span></code></pre></div><p>下載時間視網速、10-30 分鐘 broadband 都正常。網路中斷時重跑同一個指令、<code>--continue-at -</code> 會從中斷處續傳、不用重下 6.5 GB。完成後：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ls -lh sd_xl_base_1.0.safetensors
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 6.5 GB</span></span></span></code></pre></div><p>可選的進階模型：</p>
<table>
  <thead>
      <tr>
          <th>Model</th>
          <th>大小</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SDXL base 1.0</td>
          <td>6.5 GB</td>
          <td>基礎、本 demo 用</td>
      </tr>
      <tr>
          <td>SDXL refiner 1.0</td>
          <td>6.1 GB</td>
          <td>跟 base 配對、提升細節</td>
      </tr>
      <tr>
          <td>SD 1.5</td>
          <td>4.0 GB</td>
          <td>較小、生態最成熟（很多 LoRA）</td>
      </tr>
      <tr>
          <td>Flux.1 schnell</td>
          <td>12 GB</td>
          <td>2024+ 最強開源 SD 級</td>
      </tr>
      <tr>
          <td>Flux.1 dev</td>
          <td>24 GB</td>
          <td>Flux 完整版、品質最佳</td>
      </tr>
  </tbody>
</table>
<p>SDXL 6.5 GB 是「能驗證 + 不過大」的甜蜜點。再小可以選 SD 1.5（4 GB）、跑 Flux 要 24 GB 磁碟 + 16 GB+ 統一記憶體。</p>
<h2 id="啟動-comfyui-server">啟動 ComfyUI Server</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> ~/Projects/ComfyUI
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">source</span> venv/bin/activate
</span></span><span class="line"><span class="ln">3</span><span class="cl">python main.py</span></span></code></pre></div><p>預期輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[Prompt Server] Starting ComfyUI...
</span></span><span class="line"><span class="ln">2</span><span class="cl">Total VRAM 32768 MB, total RAM 32768 MB
</span></span><span class="line"><span class="ln">3</span><span class="cl">pytorch version: 2.x.x
</span></span><span class="line"><span class="ln">4</span><span class="cl">Set vram state to: SHARED
</span></span><span class="line"><span class="ln">5</span><span class="cl">Device: mps
</span></span><span class="line"><span class="ln">6</span><span class="cl">Using sub quadratic attention for cross-attention
</span></span><span class="line"><span class="ln">7</span><span class="cl">...
</span></span><span class="line"><span class="ln">8</span><span class="cl">Starting server
</span></span><span class="line"><span class="ln">9</span><span class="cl">To see the GUI go to: http://127.0.0.1:8188</span></span></code></pre></div><p>Apple Silicon 統一記憶體被 PyTorch 報成 VRAM 是預期、不是 bug：mps backend 把整個統一記憶體當成「GPU 可見記憶體」、所以 32GB Mac 顯示 <code>Total VRAM 32768 MB</code>。實際使用上 ComfyUI、其他 app 跟系統共用同一塊。</p>
<p>關鍵驗證：</p>
<ul>
<li><code>Device: mps</code> → Apple Silicon GPU 啟用</li>
<li><code>Starting server</code> + <code>http://127.0.0.1:8188</code> → server 跑了</li>
</ul>
<p>開瀏覽器到 <code>http://127.0.0.1:8188</code>、看到節點式 UI 就成功。第一次開啟會載入預設 workflow（一個簡單 text-to-image）。</p>
<p>要對外暴露（讓 LAN 內其他機器連）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python main.py --listen 0.0.0.0 --port <span class="m">8188</span></span></span></code></pre></div><p>跟 <a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 隱私資料流</a> 提的一樣、<code>0.0.0.0</code> 等於暴露給整個區網、家用 OK 公共網路要小心。</p>
<h2 id="跑第一張圖">跑第一張圖</h2>
<p>ComfyUI 預設 workflow 是 text-to-image：</p>
<ol>
<li><strong>CheckpointLoader 節點</strong>：選 <code>sd_xl_base_1.0.safetensors</code>。</li>
<li><strong>CLIPTextEncode（Prompt）節點</strong>：輸入 prompt、例如 <code>a photograph of a cat sitting on a wooden chair, natural lighting</code>。</li>
<li><strong>CLIPTextEncode（Negative）節點</strong>：輸入 negative prompt、例如 <code>blurry, low quality, artifacts</code>。</li>
<li><strong>EmptyLatentImage 節點</strong>：設定 1024×1024（SDXL 最佳尺寸）。</li>
<li><strong>KSampler 節點</strong>：steps=20、cfg=7、sampler=<code>euler</code> 或 <code>dpmpp_2m</code>。</li>
<li><strong>VAEDecode 節點</strong>：把 latent 轉成 RGB image。</li>
<li><strong>SaveImage 節點</strong>：存到 <code>output/</code>。</li>
</ol>
<p>點右側 panel 的 <code>Queue Prompt</code>、開始生成。</p>
<p>實測時間（M4 Pro 32GB、SDXL base、1024×1024、MPS backend）：</p>
<table>
  <thead>
      <tr>
          <th>Steps</th>
          <th>第一張（含 model 載入）</th>
          <th>後續同 model</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>15</td>
          <td>約 100-110 秒</td>
          <td>約 30-40 秒</td>
          <td>本驗證實測 106s（含載入）</td>
      </tr>
      <tr>
          <td>20</td>
          <td>約 130-150 秒</td>
          <td>約 40-60 秒</td>
          <td>ComfyUI 預設值</td>
      </tr>
      <tr>
          <td>30</td>
          <td>約 200 秒</td>
          <td>約 80 秒</td>
          <td>品質更高、邊際效益小</td>
      </tr>
  </tbody>
</table>
<p>16GB Mac 跑 SDXL：每張 60-180 秒、可能會降頻。</p>
<p>生成完成後在 <code>output/</code> 看到 PNG 檔（如 <code>comfyui-test_00001_.png</code>）。</p>
<h2 id="用-rest-api-直接生成不開瀏覽器">用 REST API 直接生成（不開瀏覽器）</h2>
<p>GUI 適合互動探索、自動化要走 REST API。完整 script 在 <code>scripts/comfyui-test/generate.py</code>、實際驗證指令：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> ~/Projects/blog
</span></span><span class="line"><span class="ln">2</span><span class="cl">python3 scripts/comfyui-test/generate.py --steps <span class="m">15</span></span></span></code></pre></div><p>腳本流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">build_workflow</span><span class="p">(</span><span class="n">prompt_text</span><span class="p">,</span> <span class="n">neg_text</span><span class="p">,</span> <span class="n">steps</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="s2">&#34;3&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;seed&#34;</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span> <span class="s2">&#34;steps&#34;</span><span class="p">:</span> <span class="n">steps</span><span class="p">,</span> <span class="s2">&#34;cfg&#34;</span><span class="p">:</span> <span class="mf">7.0</span><span class="p">,</span> <span class="s2">&#34;sampler_name&#34;</span><span class="p">:</span> <span class="s2">&#34;euler&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">                         <span class="s2">&#34;scheduler&#34;</span><span class="p">:</span> <span class="s2">&#34;normal&#34;</span><span class="p">,</span> <span class="s2">&#34;denoise&#34;</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">                         <span class="s2">&#34;model&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;4&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="s2">&#34;positive&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;6&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">                         <span class="s2">&#34;negative&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;7&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="s2">&#34;latent_image&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;5&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">              <span class="s2">&#34;class_type&#34;</span><span class="p">:</span> <span class="s2">&#34;KSampler&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="s2">&#34;4&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;ckpt_name&#34;</span><span class="p">:</span> <span class="s2">&#34;sd_xl_base_1.0.safetensors&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">              <span class="s2">&#34;class_type&#34;</span><span class="p">:</span> <span class="s2">&#34;CheckpointLoaderSimple&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;5&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;width&#34;</span><span class="p">:</span> <span class="mi">1024</span><span class="p">,</span> <span class="s2">&#34;height&#34;</span><span class="p">:</span> <span class="mi">1024</span><span class="p">,</span> <span class="s2">&#34;batch_size&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">              <span class="s2">&#34;class_type&#34;</span><span class="p">:</span> <span class="s2">&#34;EmptyLatentImage&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;6&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="n">prompt_text</span><span class="p">,</span> <span class="s2">&#34;clip&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;4&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">              <span class="s2">&#34;class_type&#34;</span><span class="p">:</span> <span class="s2">&#34;CLIPTextEncode&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;7&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="n">neg_text</span><span class="p">,</span> <span class="s2">&#34;clip&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;4&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">              <span class="s2">&#34;class_type&#34;</span><span class="p">:</span> <span class="s2">&#34;CLIPTextEncode&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;8&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;samples&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;3&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="s2">&#34;vae&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;4&#34;</span><span class="p">,</span> <span class="mi">2</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">              <span class="s2">&#34;class_type&#34;</span><span class="p">:</span> <span class="s2">&#34;VAEDecode&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;9&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;filename_prefix&#34;</span><span class="p">:</span> <span class="s2">&#34;comfyui-test&#34;</span><span class="p">,</span> <span class="s2">&#34;images&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;8&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">              <span class="s2">&#34;class_type&#34;</span><span class="p">:</span> <span class="s2">&#34;SaveImage&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="p">}</span></span></span></code></pre></div><p><strong>workflow JSON 結構解釋</strong>：</p>
<ul>
<li><strong>每個 key（&ldquo;3&rdquo;、&ldquo;4&rdquo;、…）是節點 ID</strong>。任意整數字串、只要在 workflow 內唯一即可。</li>
<li><strong><code>class_type</code></strong>：節點類型（KSampler、CheckpointLoaderSimple、CLIPTextEncode 等）、ComfyUI 內建。</li>
<li><strong><code>inputs</code></strong>：節點參數。標量值（如 <code>1024</code>、<code>&quot;euler&quot;</code>）直接寫；連到別的節點輸出用 <code>[node_id, output_index]</code> 形式。</li>
<li><strong><code>[&quot;4&quot;, 0]</code></strong> 表示「節點 4 的第 0 個 output」。CheckpointLoaderSimple 有三個 output：<code>model</code>（0）、<code>clip</code>（1）、<code>vae</code>（2）、所以 <code>[&quot;4&quot;, 0]</code> 是 model、<code>[&quot;4&quot;, 1]</code> 是 clip、<code>[&quot;4&quot;, 2]</code> 是 vae。</li>
</ul>
<p><strong>每個節點做什麼</strong>：</p>
<ul>
<li><strong>4 CheckpointLoaderSimple</strong>：載 SDXL safetensors、輸出 model / clip / vae 三個東西。是整條 graph 的根。</li>
<li><strong>5 EmptyLatentImage</strong>：建一張 1024×1024 的空白 latent tensor（不是 RGB 圖、是 4-channel latent space tensor）。SDXL 的 「畫布」。</li>
<li><strong>6 CLIPTextEncode (positive)</strong>：把 prompt 文字用 CLIP text encoder 轉成 conditioning vector。</li>
<li><strong>7 CLIPTextEncode (negative)</strong>：同上、但是 negative prompt（要 avoid 的特徵）。</li>
<li><strong>3 KSampler</strong>：核心 denoising loop。15-30 個 step、把 latent 從噪聲變成跟 conditioning 對齊的 latent。</li>
<li><strong>8 VAEDecode</strong>：把 latent 用 VAE 解碼成 RGB 圖（1024×1024×3）。</li>
<li><strong>9 SaveImage</strong>：寫 PNG 到 <code>output/</code> 目錄、檔名 prefix <code>comfyui-test</code>。</li>
</ul>
<p><strong>為什麼 graph 結構這樣</strong>：</p>
<ul>
<li><strong>為什麼 model / clip / vae 從同一個 checkpoint 拿</strong>：SDXL 設計上三個元件互相 train、必須同源。從不同 checkpoint 拿會造成生成品質崩。</li>
<li><strong>為什麼 EmptyLatentImage 不直接接 KSampler、要設 batch_size</strong>：保留 batch 維度、未來要 batch generation（一次生 4 張）改 <code>batch_size: 4</code> 就好、其他節點不用改。</li>
<li><strong>為什麼 sampler 用 <code>euler</code>、scheduler 用 <code>normal</code></strong>：最簡單的組合、SDXL base 上品質可預測。其他選項（<code>dpmpp_2m</code>、<code>karras</code> scheduler 等）品質可能更好但效果各模型不同。</li>
<li><strong>為什麼 cfg=7.0</strong>：classifier-free guidance scale。SDXL 的標準預設、太低（&lt; 3）模型忽略 prompt、太高（&gt; 12）過 saturated。</li>
<li><strong>為什麼 seed=42</strong>：固定 seed 讓結果可重現。每次跑同 prompt 同 seed 同 model 結果完全一樣——是調 prompt / 比較 model 的必要條件。</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">workflow</span> <span class="o">=</span> <span class="n">build_workflow</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">prompt</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">neg</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">steps</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">client_id</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">resp</span> <span class="o">=</span> <span class="n">http_post_json</span><span class="p">(</span><span class="s2">&#34;/prompt&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;prompt&#34;</span><span class="p">:</span> <span class="n">workflow</span><span class="p">,</span> <span class="s2">&#34;client_id&#34;</span><span class="p">:</span> <span class="n">client_id</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">prompt_id</span> <span class="o">=</span> <span class="n">resp</span><span class="p">[</span><span class="s2">&#34;prompt_id&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">history</span> <span class="o">=</span> <span class="n">http_get_json</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;/history/</span><span class="si">{</span><span class="n">prompt_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">if</span> <span class="n">prompt_id</span> <span class="ow">in</span> <span class="n">history</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">outputs</span> <span class="o">=</span> <span class="n">history</span><span class="p">[</span><span class="n">prompt_id</span><span class="p">]</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;outputs&#34;</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">img</span> <span class="o">=</span> <span class="n">outputs</span><span class="p">[</span><span class="s2">&#34;9&#34;</span><span class="p">][</span><span class="s2">&#34;images&#34;</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">qs</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">parse</span><span class="o">.</span><span class="n">urlencode</span><span class="p">({</span><span class="s2">&#34;filename&#34;</span><span class="p">:</span> <span class="n">img</span><span class="p">[</span><span class="s2">&#34;filename&#34;</span><span class="p">],</span> <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;output&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">blob</span> <span class="o">=</span> <span class="n">http_get_bytes</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;/view?</span><span class="si">{</span><span class="n">qs</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">Path</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">out</span><span class="p">)</span><span class="o">.</span><span class="n">write_bytes</span><span class="p">(</span><span class="n">blob</span><span class="p">)</span></span></span></code></pre></div><p><strong>每段做什麼</strong>：</p>
<ol>
<li><strong><code>client_id = str(uuid.uuid4())</code></strong>：每個 client 識別碼。ComfyUI 用 client_id 把 progress events 路由給正確 WebSocket subscriber。本 demo 用 polling、client_id 隨意產生即可。</li>
<li><strong><code>POST /prompt</code></strong>：送 workflow + client_id、server 回 <code>prompt_id</code>（這次 job 的 UUID）。Server 把 workflow 丟進 internal queue、立刻 return、不會等 generation。</li>
<li><strong><code>while True: time.sleep(2); GET /history/{prompt_id}</code></strong>：polling 等 job 完成。完成的 job 才會出現在 <code>/history</code> 裡（執行中 / queued 都不算）。</li>
<li><strong><code>if prompt_id in history</code></strong>：完成判讀——history 內出現該 prompt_id 表示 generation 結束。</li>
<li><strong><code>outputs[&quot;9&quot;][&quot;images&quot;][0]</code></strong>：節點 9 (SaveImage) 的輸出、含 <code>filename</code>、<code>subfolder</code>、<code>type</code> 等資訊。</li>
<li><strong><code>/view?filename=...&amp;type=output</code></strong>：拿生成的 PNG bytes。<code>type=output</code> 是 ComfyUI 的內部 dir 標記（區分 output / input / temp）。</li>
</ol>
<p><strong>為什麼這樣設計</strong>：</p>
<ul>
<li><strong>為什麼 polling 而不是 WebSocket</strong>：WebSocket 要 subscribe events、處理 connection lifecycle、邏輯複雜。Polling 兩行解決、對教學 demo 夠用。Production 自動化系統建議用 WebSocket、知道每個 progress event。</li>
<li><strong>為什麼 <code>time.sleep(2)</code></strong>：太短（&lt; 1s）對 server 造成不必要 polling；太長（&gt; 5s）感知延遲明顯。2 秒是 demo 友善平衡。</li>
<li><strong>為什麼用 prompt_id 而不是 client_id 查 history</strong>：一個 client 可能送多個 job、prompt_id 唯一識別 job。client_id 主要用 WebSocket 訂閱、不是 history query 主鍵。</li>
<li><strong>為什麼 <code>Path(args.out).write_bytes(blob)</code></strong>：PNG 是 binary、用 <code>write_bytes</code> 直接寫；改用 <code>open(...).write()</code> 的 text mode 會在編碼轉換時破壞檔案內容。</li>
</ul>
<p><strong>實測</strong>：M4 Pro 32GB、prompt 「a photograph of an orange cat sitting on a wooden chair, soft natural lighting, detailed fur」、15 steps、cfg=7、euler+normal sampler、seed=42 → 106 秒生成 1024×1024 PNG、1.65 MB。</p>
<h2 id="comfyui-的-rest-api-形狀無-openai-相容層">ComfyUI 的 REST API 形狀（無 OpenAI 相容層）</h2>
<p>ComfyUI 沒提供 OpenAI 相容 API、它的 API 是自己的 REST + WebSocket：</p>
<ul>
<li><code>POST /prompt</code>：丟一個 workflow JSON、回傳 job id。</li>
<li><code>GET /history/{prompt_id}</code>：查看生成結果。</li>
<li><code>GET /view?filename=X</code>：拿生成的圖。</li>
<li>WebSocket：訂閱 job progress events。</li>
</ul>
<p>API 形狀跟 Diffusion 任務匹配、跟 LLM 的 <code>/chat/completions</code> 完全不同——這正是 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 章節</a> 提到「Diffusion 跟 Transformer 工具鏈互不通用」的具體展現。Ollama / LM Studio 對接 Continue.dev 的 OpenAI 相容路徑、跟 ComfyUI 接 SDXL 是完全平行的兩條路。</p>
<h2 id="常用-custom-nodes">常用 Custom Nodes</h2>
<p>ComfyUI 的核心功能來自 custom nodes、社群維護。最常用：</p>
<table>
  <thead>
      <tr>
          <th>Custom Node</th>
          <th>功能</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ComfyUI-Manager</td>
          <td>管理其他 custom node、安裝 / 更新</td>
      </tr>
      <tr>
          <td>ComfyUI-Impact-Pack</td>
          <td>物件偵測、masking、inpainting</td>
      </tr>
      <tr>
          <td>ComfyUI-AnimateDiff</td>
          <td>影片動畫生成</td>
      </tr>
      <tr>
          <td>ComfyUI-ControlNet-Aux</td>
          <td>ControlNet preprocessor</td>
      </tr>
      <tr>
          <td>ComfyUI-IPAdapter-plus</td>
          <td>圖像 reference embedding</td>
      </tr>
  </tbody>
</table>
<p>安裝方式（透過 ComfyUI-Manager）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> ~/Projects/ComfyUI/custom_nodes
</span></span><span class="line"><span class="ln">2</span><span class="cl">git clone https://github.com/ltdrdata/ComfyUI-Manager.git
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 重啟 ComfyUI、UI 多一個 Manager 按鈕、之後用 Manager 裝其他 node</span></span></span></code></pre></div><h2 id="常見坑">常見坑</h2>
<h3 id="python-版本太新torch-沒-wheel">Python 版本太新、torch 沒 wheel</h3>
<p>PyTorch 對最新 Python（3.13、3.14）的 wheel 發布有 lag、可能 <code>pip install -r requirements.txt</code> 跑 build from source 慢 + 失敗。退到 Python 3.11 / 3.12：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">brew install python@3.11
</span></span><span class="line"><span class="ln">2</span><span class="cl">python3.11 -m venv venv
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">source</span> venv/bin/activate
</span></span><span class="line"><span class="ln">4</span><span class="cl">pip install -r requirements.txt</span></span></code></pre></div><h3 id="mps-false跑在-cpu-上"><code>mps: False</code>、跑在 CPU 上</h3>
<p>確認 PyTorch 是 Apple Silicon 版本（不是 x86_64 emulation）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python -c <span class="s2">&#34;import platform; print(platform.machine())&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># arm64 ← 正確；x86_64 ← 走 Rosetta、要重裝</span></span></span></code></pre></div><p>如果是 x86_64、表示 venv 用了 Intel Python。重建 venv：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">deactivate
</span></span><span class="line"><span class="ln">2</span><span class="cl">rm -rf venv
</span></span><span class="line"><span class="ln">3</span><span class="cl">arch -arm64 python3 -m venv venv</span></span></code></pre></div><h3 id="記憶體不夠推論時-crash">記憶體不夠、推論時 crash</h3>
<p>SDXL 在 16 GB Mac 上吃緊、可能 swap 或 crash。緩解：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 降解析度</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">python main.py --normalvram   <span class="c1"># 預設、~12 GB</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">python main.py --lowvram      <span class="c1"># 較省、~8 GB、慢</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">python main.py --novram       <span class="c1"># 極省、~4 GB、極慢、實用上界</span></span></span></code></pre></div><p>或換 SD 1.5（4 GB checkpoint）、記憶體需求 &lt; SDXL 的一半。</p>
<h3 id="workflow-json-載入失敗">Workflow JSON 載入失敗</h3>
<p>ComfyUI workflow 是 JSON 描述節點 + 連線。如果是別人分享的 workflow、可能用了你沒裝的 custom node。錯誤訊息會列出缺哪些 node、用 ComfyUI-Manager 補裝。</p>
<h3 id="port-8188-被佔">Port 8188 被佔</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">lsof -i :8188
</span></span><span class="line"><span class="ln">2</span><span class="cl">python main.py --port <span class="m">8189</span>  <span class="c1"># 改 port</span></span></span></code></pre></div><h2 id="跟-llm-stack-並存">跟 LLM stack 並存</h2>
<p>ComfyUI 用 port 8188、跟 Ollama (11434) / LM Studio (1234) 完全不撞、可同時跑。實務配置：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>Port</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ollama</td>
          <td>11434</td>
          <td>寫 code、對話</td>
      </tr>
      <tr>
          <td>ComfyUI</td>
          <td>8188</td>
          <td>產圖</td>
      </tr>
      <tr>
          <td>LM Studio</td>
          <td>1234</td>
          <td>探索新 LLM</td>
      </tr>
      <tr>
          <td>Open WebUI</td>
          <td>3000</td>
          <td>ChatGPT 風格瀏覽器介面</td>
      </tr>
  </tbody>
</table>
<p>各服務獨立、不干擾、可以一台 Mac 跑全部（看記憶體預算）。</p>
<h2 id="何時這篇會過時">何時這篇會過時</h2>
<ul>
<li>ComfyUI 主分支 API 短期內穩定（大量社群依賴）。</li>
<li>SDXL base 1.0 不會消失、但會被新版本（SDXL 1.1、Flux 等）取代——「下載 .safetensors 放 models/checkpoints/」流程不變。</li>
<li>MPS backend 持續優化、效能會提升、但介面不變。</li>
<li>Python 版本相容性會持續演化、<code>pip install -r requirements.txt</code> 偶爾要降版 Python。</li>
</ul>
<p>讀的時候若 pip install 失敗、看 ComfyUI GitHub issues 跟 PyTorch release notes 對應的 Python 版本。</p>
<p>跟其他 hands-on 章節的關係：完整 hands-on 系列見 <a href="/blog/llm/01-local-llm-services/hands-on/" data-link-title="Hands-on：本地 AI 工具實作筆記" data-link-desc="Ollama / ComfyUI / Whisper / Piper TTS：實際安裝、驗證、跑通的紀錄。隨工具版本演化、跟 1.x 原理章節互補。">Hands-on 章節索引</a>、跨服務的 lifecycle / 記憶體管理見 <a href="/blog/llm/01-local-llm-services/hands-on/resource-management/" data-link-title="Hands-on：LLM 運行中 &#43; 結束的資源管理" data-link-desc="RAM / 磁碟 / port 三個 dimension 的觀察跟釋放、Ollama keep_alive 跟 ComfyUI 兩種 lifecycle 對比、實測釋放數字">Resource management</a>、ComfyUI 跟 Ollama 同台跑的記憶體預算規劃見 <a href="/blog/llm/00-foundations/hardware-memory-budget/" data-link-title="0.5 Apple Silicon 記憶體預算" data-link-desc="記憶體決定能跑什麼，Q4 量化下的可運作模型對照與系統保留">0.5 Apple Silicon 記憶體預算</a>。</p>
]]></content:encoded></item><item><title>1.6 延伸方向：Web UI、coding agent、產圖</title><link>https://tarrragon.github.io/blog/llm/01-local-llm-services/extension-paths/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/01-local-llm-services/extension-paths/</guid><description>&lt;p>模組一前五章覆蓋了「Ollama + Continue.dev」這條最短路徑。日常路徑跑穩後，你可能會想往以下方向延伸：加裝 ChatGPT 風格的 Web UI、跑 coding agent、嘗試產圖。本章把這些延伸方向逐一列出、給優先順序、講清楚哪些是「換工具」、哪些是「換領域」。&lt;/p>
&lt;p>關鍵原則：&lt;strong>先把寫 code 跑穩、再考慮延伸&lt;/strong>。同時推進三條延伸通常會讓每條都停在半生不熟階段、累積成果有限。本章建議的順序是先 Web UI、再 coding agent、最後產圖；如果你只想嘗試一個、依自己最常用的場景挑。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後，你應該能：&lt;/p>
&lt;ol>
&lt;li>列出三條延伸方向的代表工具與基本定位。&lt;/li>
&lt;li>知道每個方向跟寫 code 主路徑的關係。&lt;/li>
&lt;li>判斷自己現階段該不該往延伸方向走。&lt;/li>
&lt;li>對「產圖」這條歧路建立正確認知（不是換 model 就好）。&lt;/li>
&lt;/ol>
&lt;h2 id="延伸方向一chatgpt-風格-web-uiopen-webui">延伸方向一：ChatGPT 風格 Web UI（Open WebUI）&lt;/h2>
&lt;p>&lt;strong>定位&lt;/strong>：在瀏覽器跑一個類 ChatGPT 介面，連到本地 LLM 或雲端 LLM。屬於&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/three-layer-architecture/" data-link-title="0.2 介面 / 伺服器 / 模型三層架構" data-link-desc="把任何本地 LLM 工具放回正確的層級，用三層心智模型看懂工具關係">三層架構&lt;/a>的介面層，跟 Continue.dev 同層、解決不同情境（瀏覽器 vs IDE）。&lt;/p>
&lt;p>&lt;strong>典型使用情境&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>不在寫 code 但想跟 LLM 對話（解釋技術概念、寫文章草稿）。&lt;/li>
&lt;li>跟同事 / 家人分享 LLM 使用，他們不會用 VS Code。&lt;/li>
&lt;li>從手機 / iPad 連回家裡 Mac 跑的 Ollama。&lt;/li>
&lt;li>多輪深度對話、希望有歷史紀錄保存。&lt;/li>
&lt;/ol>
&lt;h3 id="主流選擇open-webui">主流選擇：Open WebUI&lt;/h3>
&lt;p>Open WebUI 是 open source 的 ChatGPT-clone，連 Ollama 與 OpenAI 相容 API。安裝最快路徑是 Docker：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">docker run -d --name open-webui -p 3000:8080 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> -e &lt;span class="nv">OLLAMA_BASE_URL&lt;/span>&lt;span class="o">=&lt;/span>http://host.docker.internal:11434 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> -v open-webui:/app/backend/data &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --restart always &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> ghcr.io/open-webui/open-webui:main&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>host.docker.internal&lt;/code> 是 Docker Desktop 提供的 DNS 名稱、container 內透過它連到宿主機（macOS 本身）跑的 Ollama；Linux Docker 沒這個別名、要改用 &lt;code>--add-host=host.docker.internal:host-gateway&lt;/code> 或直接填宿主 IP。啟動後開 &lt;code>http://localhost:3000&lt;/code>、註冊本地帳號（資料只存本機 SQLite）、就有完整 ChatGPT 介面：&lt;/p>
&lt;ul>
&lt;li>對話歷史保存（本地 SQLite）&lt;/li>
&lt;li>多 model 切換、可同時對比兩個 model 回答&lt;/li>
&lt;li>系統 prompt 自訂、prompt template 管理&lt;/li>
&lt;li>上傳檔案分析（PDF、txt 等）&lt;/li>
&lt;li>圖片支援（如果本地 model 是多模態）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>陷阱&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>沒裝 Docker 的話要先學 Docker，是不小的前置學習。&lt;/li>
&lt;li>Open WebUI 預設不需要驗證，跑在 &lt;code>0.0.0.0&lt;/code> 會暴露在 LAN 上。要從外網用記得加 reverse proxy + auth。&lt;/li>
&lt;li>對話紀錄存在 Docker volume，刪 container 要小心保留 volume，否則歷史會消失。&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>何時做這個延伸&lt;/strong>：日常 Continue.dev + Ollama 跑穩、用了至少一週、確認本地 LLM 對你有用，再加 Open WebUI 擴展使用情境。&lt;/p>
&lt;h2 id="延伸方向二coding-agentaidercline-等">延伸方向二：Coding Agent（aider、Cline 等）&lt;/h2>
&lt;p>&lt;strong>定位&lt;/strong>：比 Continue.dev 更主動的 LLM 寫 code 工具。Continue.dev 是「你提問、LLM 答」的對話模式；coding &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">agent&lt;/a> 是「你給目標、LLM 自己分多步驟改 code、跑測試、修錯誤」的代理模式。詳細的 agent loop 結構、失敗模式、人類審查協作見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構原理&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>模組一前五章覆蓋了「Ollama + Continue.dev」這條最短路徑。日常路徑跑穩後，你可能會想往以下方向延伸：加裝 ChatGPT 風格的 Web UI、跑 coding agent、嘗試產圖。本章把這些延伸方向逐一列出、給優先順序、講清楚哪些是「換工具」、哪些是「換領域」。</p>
<p>關鍵原則：<strong>先把寫 code 跑穩、再考慮延伸</strong>。同時推進三條延伸通常會讓每條都停在半生不熟階段、累積成果有限。本章建議的順序是先 Web UI、再 coding agent、最後產圖；如果你只想嘗試一個、依自己最常用的場景挑。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後，你應該能：</p>
<ol>
<li>列出三條延伸方向的代表工具與基本定位。</li>
<li>知道每個方向跟寫 code 主路徑的關係。</li>
<li>判斷自己現階段該不該往延伸方向走。</li>
<li>對「產圖」這條歧路建立正確認知（不是換 model 就好）。</li>
</ol>
<h2 id="延伸方向一chatgpt-風格-web-uiopen-webui">延伸方向一：ChatGPT 風格 Web UI（Open WebUI）</h2>
<p><strong>定位</strong>：在瀏覽器跑一個類 ChatGPT 介面，連到本地 LLM 或雲端 LLM。屬於<a href="/blog/llm/00-foundations/three-layer-architecture/" data-link-title="0.2 介面 / 伺服器 / 模型三層架構" data-link-desc="把任何本地 LLM 工具放回正確的層級，用三層心智模型看懂工具關係">三層架構</a>的介面層，跟 Continue.dev 同層、解決不同情境（瀏覽器 vs IDE）。</p>
<p><strong>典型使用情境</strong>：</p>
<ol>
<li>不在寫 code 但想跟 LLM 對話（解釋技術概念、寫文章草稿）。</li>
<li>跟同事 / 家人分享 LLM 使用，他們不會用 VS Code。</li>
<li>從手機 / iPad 連回家裡 Mac 跑的 Ollama。</li>
<li>多輪深度對話、希望有歷史紀錄保存。</li>
</ol>
<h3 id="主流選擇open-webui">主流選擇：Open WebUI</h3>
<p>Open WebUI 是 open source 的 ChatGPT-clone，連 Ollama 與 OpenAI 相容 API。安裝最快路徑是 Docker：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">docker run -d --name open-webui -p 3000:8080 <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  -e <span class="nv">OLLAMA_BASE_URL</span><span class="o">=</span>http://host.docker.internal:11434 <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  -v open-webui:/app/backend/data <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --restart always <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  ghcr.io/open-webui/open-webui:main</span></span></code></pre></div><p><code>host.docker.internal</code> 是 Docker Desktop 提供的 DNS 名稱、container 內透過它連到宿主機（macOS 本身）跑的 Ollama；Linux Docker 沒這個別名、要改用 <code>--add-host=host.docker.internal:host-gateway</code> 或直接填宿主 IP。啟動後開 <code>http://localhost:3000</code>、註冊本地帳號（資料只存本機 SQLite）、就有完整 ChatGPT 介面：</p>
<ul>
<li>對話歷史保存（本地 SQLite）</li>
<li>多 model 切換、可同時對比兩個 model 回答</li>
<li>系統 prompt 自訂、prompt template 管理</li>
<li>上傳檔案分析（PDF、txt 等）</li>
<li>圖片支援（如果本地 model 是多模態）</li>
</ul>
<p><strong>陷阱</strong>：</p>
<ol>
<li>沒裝 Docker 的話要先學 Docker，是不小的前置學習。</li>
<li>Open WebUI 預設不需要驗證，跑在 <code>0.0.0.0</code> 會暴露在 LAN 上。要從外網用記得加 reverse proxy + auth。</li>
<li>對話紀錄存在 Docker volume，刪 container 要小心保留 volume，否則歷史會消失。</li>
</ol>
<p><strong>何時做這個延伸</strong>：日常 Continue.dev + Ollama 跑穩、用了至少一週、確認本地 LLM 對你有用，再加 Open WebUI 擴展使用情境。</p>
<h2 id="延伸方向二coding-agentaidercline-等">延伸方向二：Coding Agent（aider、Cline 等）</h2>
<p><strong>定位</strong>：比 Continue.dev 更主動的 LLM 寫 code 工具。Continue.dev 是「你提問、LLM 答」的對話模式；coding <a href="/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">agent</a> 是「你給目標、LLM 自己分多步驟改 code、跑測試、修錯誤」的代理模式。詳細的 agent loop 結構、失敗模式、人類審查協作見 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構原理</a>。</p>
<p><strong>主流選擇</strong>：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>介面</th>
          <th>定位</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>aider</td>
          <td>CLI</td>
          <td>git-aware、把 LLM 改的 diff 直接 commit、支援 multi-file edit</td>
      </tr>
      <tr>
          <td>Cline</td>
          <td>VS Code 擴充</td>
          <td>在 VS Code 內跑 agent、可執行 shell command</td>
      </tr>
      <tr>
          <td>Cursor Agent</td>
          <td>Cursor 內建</td>
          <td>Cursor 訂閱戶可用、雲端綁定</td>
      </tr>
  </tbody>
</table>
<p>選擇三個工具的延伸判讀：</p>
<ul>
<li><strong>aider</strong>：當主要工作流是「在 terminal + git 內完成」、想讓 LLM 把 diff 直接 commit 進 history、aider 的 CLI-first + git-aware 設計最對位。失敗模式：跨多檔修改超過 5 個檔時、aider 的 prompt 規劃容易斷裂；改回 Continue.dev 手動逐檔修可能更穩。</li>
<li><strong>Cline</strong>：當你已在 VS Code 內工作、想要 agent 能跑 shell command（執行測試、跑 build 看錯誤）並 loop 修錯時、Cline 比 aider 更貼近「IDE 內 agent」。失敗模式：本地模型在「規劃 → 執行 shell → 解讀錯誤 → 改 code」這個 loop 上接受度不穩、常需要人工接管。</li>
<li><strong>Cursor Agent</strong>：當你已是 Cursor 訂閱戶、agent 預設綁雲端旗艦（成功率最高、但 prompt / code 會送到 Cursor 雲端）。NDA / 合規場景不適用、本地 LLM 接入也是次要 surface。</li>
</ul>
<p><strong>為什麼是 advanced</strong>：coding agent 需要本地模型能「跟著規劃跑多步驟、用 tools、不偏離目標」。這部分是本地 LLM 的弱項（見 <a href="/blog/llm/01-local-llm-services/expectation-management/" data-link-title="1.5 期望管理：本地 LLM 的擅長領域與分工" data-link-desc="本地 LLM 是免費的初階 pair programmer：辨識它的擅長領域、跟雲端旗艦做結構性分工">1.5 期望管理</a>）；現階段本地模型跑 coding agent 的成功率明顯低於雲端旗艦。</p>
<p><strong>用 aider 跑本地 LLM 的最小範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 裝 aider</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">pip install aider-chat
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 在 git repo 內啟動，用本地 Ollama</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">aider --model ollama/gemma4:31b-coding-mtp-bf16 <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --ollama-base-url http://localhost:11434</span></span></code></pre></div><p>aider 會把當前 repo 的相關檔案打進 prompt、把 LLM 生成的 diff apply 到本機、自動 commit。簡單任務（單檔重構、加 test）成功率還行；複雜任務（跨檔案、需要規劃）失敗率高。</p>
<p><strong>陷阱</strong>：</p>
<ol>
<li>本地 LLM 跑 aider 比跑 Continue.dev 慢得多、因為每輪 agent loop 都要重新處理長 context。</li>
<li>coding agent 對 long context 敏感、本地 <a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a> 痛點被放大。Agent loop 每輪都會 mutate prompt（前一輪結果加入下一輪的 context）、<a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a> 命中率低、每輪都要重新做完整 prefill。</li>
<li>失敗時 agent 可能 commit 不可用的 code、要記得 <code>git diff</code> 審過再 push。</li>
</ol>
<p><strong>何時做這個延伸</strong>：本地模型在 Continue.dev 對話模式下表現穩定、且你想看看「multi-step 自動化」能幫到什麼程度。對多數讀者、這條延伸在 2026 年 5 月時是「值得試一週、但不一定留下」。</p>
<p><strong>何時該停</strong>：以下訊號出現時、agent 路線在你的工作流暫時不成立、回到 Continue.dev 對話模式：</p>
<ul>
<li>連續 5 個 multi-step 任務都需要人工接管 / 中途介入修錯</li>
<li>TTFT 持續 &gt; 30 秒、agent loop 的「等待 → 接管」節奏比手寫快不了多少</li>
<li>agent commit 進 git history 的 diff 通過率 &lt; 50%、審查與 revert 的成本超過自己寫</li>
<li>簡單任務（單檔重構、加 test）本地 agent 也常失敗、表示模型 capacity 對 agent 規劃不足</li>
</ul>
<h2 id="延伸方向三產圖stable-diffusionflux-等">延伸方向三：產圖（Stable Diffusion、Flux 等）</h2>
<p>產圖是另一個專業領域、工具鏈跟概念體系另起一套、跟 LLM 寫 code 沒有共用的伺服器層或 model layer。產圖用的是 <strong>Diffusion 架構</strong>、跟寫 code 用的 <strong>Transformer 架構</strong>是兩個獨立的神經網路類型。</p>
<p>四個維度上產圖跟寫 code 的工作流互不相通：</p>
<ol>
<li><strong>工具鏈各自獨立</strong>：Ollama 服務 <a href="/blog/llm/knowledge-cards/transformer/" data-link-title="Transformer" data-link-desc="寫 code 用的 LLM 神經網路架構：基於 attention 機制、自回歸生成 token">Transformer</a> LLM、Draw Things / ComfyUI 服務 <a href="/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion</a> 模型、兩條路線的伺服器與生態互不通用。</li>
<li><strong>prompt 風格不同</strong>：寫 code 是 instruction 形式、產圖是 descriptive prompt + negative prompt + sampler 參數。</li>
<li><strong>學習成本各自獨立</strong>：產圖有自己的 LoRA、ControlNet、IP-Adapter、refiner 等概念體系、學起來等於進入新領域。</li>
<li><strong>硬體最適規格不同</strong>：寫 code 看記憶體預算（<a href="/blog/llm/knowledge-cards/unified-memory/" data-link-title="Unified Memory Architecture" data-link-desc="Apple Silicon 讓 CPU / GPU / NE 共用同一塊記憶體：跑大模型的優勢來源">跑大模型</a>）、產圖看 GPU 算力與 VRAM 頻寬。</li>
</ol>
<p>本章只給入口資訊、不展開教學。</p>
<p><strong>主流工具</strong>：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>定位</th>
          <th>適合誰</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Draw Things</td>
          <td>Mac 原生 app，GUI 友善，免費</td>
          <td>macOS 使用者入門首選</td>
      </tr>
      <tr>
          <td>ComfyUI</td>
          <td>節點式工作流，跨平台，需要 Python 環境</td>
          <td>想客製化流程、進階使用者</td>
      </tr>
      <tr>
          <td>AUTOMATIC1111</td>
          <td>Web UI，跨平台，需要 Python</td>
          <td>Linux / NVIDIA 玩家為主</td>
      </tr>
      <tr>
          <td>Diffusers</td>
          <td>Hugging Face 的 Python library</td>
          <td>開發者、要嵌入產品</td>
      </tr>
  </tbody>
</table>
<p><strong>主流模型</strong>：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>風格特色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Stable Diffusion 3.5</td>
          <td>通用、社群成熟、生態最大</td>
      </tr>
      <tr>
          <td>Flux</td>
          <td>質感高、prompt 跟隨度高</td>
      </tr>
      <tr>
          <td>SDXL</td>
          <td>SD 1.5 的進階版，仍有大量 LoRA</td>
      </tr>
  </tbody>
</table>
<p><strong>Apple Silicon Mac 跑產圖的現實</strong>：</p>
<ol>
<li>24GB+ Mac 可以順暢跑 SDXL / Flux。記憶體需求其實比 LLM 低（一張圖 ~ 8GB），但對 GPU 算力敏感。</li>
<li>M4 Max 跑 Flux 生 1024x1024 圖約 15 ~ 30 秒一張，可接受。</li>
<li>Draw Things 在 Mac App Store 可下載，是最簡單的入門路徑。</li>
</ol>
<p><strong>本指南的立場</strong>：先把寫 code 跑穩、再考慮產圖。產圖屬於獨立的學習主題、另外找專門教材會學得更有效率。</p>
<h2 id="給讀者的延伸順序">給讀者的延伸順序</h2>
<p>如果你想嘗試延伸方向，建議的順序：</p>
<ol>
<li><strong>先用一個月本地 LLM 寫 code</strong>。確認 Ollama + Continue.dev 對你有用、習慣了切換。</li>
<li><strong>第一個延伸：Open WebUI</strong>。加裝最低成本（只多裝 Docker），擴展使用情境到非 VS Code 場景。</li>
<li><strong>第二個延伸：aider 或 Cline</strong>。試 coding agent，評估本地模型能 handle 多複雜的多步驟任務。</li>
<li><strong>第三個延伸：產圖</strong>。完全獨立的學習投入，跟前面工具鏈無關。</li>
</ol>
<p>依序進階。先讓基底穩、再疊加延伸、學習曲線最平滑。</p>
<h2 id="不在本章範圍內的延伸">不在本章範圍內的延伸</h2>
<p>下列延伸方向值得知道存在，但不在本指南內展開：</p>
<table>
  <thead>
      <tr>
          <th>方向</th>
          <th>為什麼不展開</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a>（檢索增強生成）</td>
          <td>需要 vector database、文件 chunking、embedding 設計、見 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a></td>
      </tr>
      <tr>
          <td>Fine-tuning</td>
          <td>訓練流程跟跑現成模型是不同工程；資源、資料、評估都複雜</td>
      </tr>
      <tr>
          <td>Multi-modal（語音、影片）</td>
          <td>工具鏈跟生態完全獨立</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP</a>（Model Context Protocol）伺服器整合</td>
          <td>是工具串接協定、見 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a></td>
      </tr>
      <tr>
          <td>部署到雲端 GPU / Linux server</td>
          <td>本指南範圍只在 Apple Silicon Mac</td>
      </tr>
  </tbody>
</table>
<p>需要這些方向時請另尋專門資源；硬塞進來會稀釋本指南「Mac 本地寫 code」這條最短路徑。</p>
<h2 id="下一步">下一步</h2>
<p>實作範例（含 ComfyUI / Whisper / Piper TTS / RAG / MCP）見 <a href="/blog/llm/01-local-llm-services/hands-on/" data-link-title="Hands-on：本地 AI 工具實作筆記" data-link-desc="Ollama / ComfyUI / Whisper / Piper TTS：實際安裝、驗證、跑通的紀錄。隨工具版本演化、跟 1.x 原理章節互補。">Hands-on 章節</a>。</p>
<p>讀到這裡、本指南的核心內容就完了。下一步是回到 <a href="/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零</a> 或 <a href="/blog/llm/01-local-llm-services/" data-link-title="模組一：本地 LLM 服務的安裝與應用" data-link-desc="Ollama、LM Studio、llama.cpp 的安裝與差異、VS Code &#43; Continue.dev 整合、模型選型與期望管理">模組一</a> 任一章節做深度閱讀、或實際打開終端機跑第一個 <code>ollama run</code>、把概念變成肌肉記憶。</p>
]]></content:encoded></item></channel></rss>