

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>電腦相關應用 &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/category/computer/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Tue, 13 Oct 2026 15:23:04 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>電腦相關應用 &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Qwen-Image-2.1-Turbo 加速版的教學：文字生圖與局部修改</title>
		<link>https://stackoverflow.max-everyday.com/2026/10/qwen-image-2-1-turbo/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/10/qwen-image-2-1-turbo/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 12 Oct 2026 09:23:32 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8786</guid>

					<description><![CDATA[Qwen-Image-2.1-Turbo 釋出八...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Qwen-Image-2.1-Turbo 釋出八步生成與圖片編輯權重。本文整理官方環境要求、預存取樣時程、商品圖提示詞與逐張驗收方法，並說明研究授權、CUDA 範例及商用前要確認的條件。</p>



<h3 class="wp-block-heading">核心架構與技術特點</h3>



<p class="wp-block-paragraph">這個模型基於七十億參數的視覺生成架構，採用三十二層的 Single-Stream DiT 設計。與需要二十五到四十步推理的標準基礎模型不同，Turbo 版本透過特殊的加速機制與預存取樣時程，將去噪步驟大幅縮短至八步。它的預設無分類器引導係數固定為一，省去了額外的前向計算過程，因此能以極快的速度完成推理。</p>



<h3 class="wp-block-heading">主要功能</h3>



<p class="wp-block-paragraph">模型支援文字生成圖片，能夠產出高達 2K 解析度的畫面。除了文生圖之外，它也具備強大的圖片編輯能力。使用者可以透過自然語言指令、參考圖、塗繪標註或蒙版，對既有圖像進行局部修改、更換背景、增加配件或調整場景，同時保留主體與商品的身份特徵。此外，它也支援原生透明度處理。</p>



<h3 class="wp-block-heading">應用場景</h3>



<p class="wp-block-paragraph">由於其八步快速出圖的特性，這個模型非常適合需要頻繁反覆修改、快速產出草稿或進行電商商品圖批次處理的工作流程。開發者通常會透過 Hugging Face 的 Diffusers 套件以及 ComfyUI 等介面來載入與部署這款模型。</p>



<h3 class="wp-block-heading">兩者的核心差異</h3>



<p class="wp-block-paragraph">Qwen/Qwen-Image-2.1-Turbo 是 Qwen/Qwen-Image-2.1 的蒸餾加速版本。兩者在底層使用了相同的七十億參數 Single-Stream DiT 視覺生成架構，但推理機制與速度上有顯著不同：<sup></sup><sup></sup><sup></sup><sup></sup></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>比較項目</strong></td><td><strong>Qwen/Qwen-Image-2.1（標準原版）</strong></td><td><strong>Qwen/Qwen-Image-2.1-Turbo（加速版）</strong></td></tr></thead><tbody><tr><td>推理步數<sup></sup><sup></sup><sup></sup><sup></sup></td><td>需要 25 至 40 步<sup></sup><sup></sup><sup></sup><sup></sup></td><td>僅需 8 步（內建預設排程）<sup></sup><sup></sup><sup></sup><sup></sup></td></tr><tr><td>無分類器引導（CFG）<sup></sup><sup></sup><sup></sup><sup></sup></td><td>需要設定 CFG &gt; 1（需雙倍前向計算）<sup></sup><sup></sup><sup></sup><sup></sup></td><td>預設 CFG = 1（嵌入權重，無<sup></sup>額外計算）<sup></sup><sup></sup><sup></sup></td></tr><tr><td>生成速度<sup></sup><sup></sup><sup></sup></td><td>較慢，但控制精細度高<sup></sup><sup></sup><sup></sup></td><td>快約<sup></sup> 5 至 8 倍，非常適合快速預覽與大量生成<sup></sup><sup></sup></td></tr><tr><td>提示詞遵循能力<sup></sup><sup></sup></td><td>極佳，適合<sup></sup>長文字或複雜多指令提示<sup></sup></td><td>稍遜於原版，極複雜指令可能有些微<sup></sup>調偏差</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">Turbo 可以完全覆蓋原版的所有功能嗎</h3>



<p class="wp-block-paragraph">答案是：在功能支援上可以完全覆蓋，但在極限品質與控制力上無法完全取代原版。</p>



<p class="wp-block-paragraph"><strong>功能範疇完全重疊：</strong></p>



<p class="wp-block-paragraph">Turbo 版本完全繼承了原版的所有核心功能，包括文字生成圖片、透明背景 RGBA 圖層生成、多圖參考編輯、塗繪與蒙版局部修改、以及人物與商品特徵保留。在 Diffusers 程式庫中，兩者也是使用同一個管道載入。</p>



<p class="wp-block-paragraph"><strong>無法完全取代原版的特定場景：</strong></p>



<ul class="wp-block-list">
<li>極複雜提示詞：Turbo 版本透過蒸餾技術將引導資訊固化在權重中。當提示詞包含非常長且繁複的多層次指令時，標準原版在 40 步與高 CFG 下的細節表現仍會比 Turbo 更精準。</li>



<li>自由調整參數：原版允許使用者自由調整係數與微調取樣步數，以在創造力與文字貼合度之間找到最佳平衡。Turbo 版本的排程與係數已被鎖定與最佳化，調參彈性較低。</li>
</ul>



<h3 class="wp-block-heading">使用建議</h3>



<ul class="wp-block-list">
<li>如果需要高效率生產、電商批次處理、即時互動生成或快速草稿設計，應優先選擇 Turbo。</li>



<li>如果需要處理非常精細的藝術設計、極度複雜的邏輯指令，或是追求極致細節的最終成品，建議保留原版。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Q: 顯示卡 RTX 4050 Laptop 6GB VRAM 可以跑的動?</h2>



<p class="wp-block-paragraph">以預設的標準精度（BF16 或 FP16）來看，Qwen-Image-2.1 系列模型大約需要十六到二十九 GB 以上的顯示記憶體，因此 RTX 4050 Laptop 的六 GB 顯示記憶體無法直接流暢運行原生日誌與完整精度。</p>



<p class="wp-block-paragraph">不過，如果你透過以下幾種優化方式，這張顯示卡還是有機會可以嘗試本地端運行：</p>



<ul class="wp-block-list">
<li>降低精度與量化：透過 GGUF 量化版本或 INT4、INT8 壓縮權重，將模型體積縮小，讓記憶體需求降至六 GB 甚至更低，但這會犧牲部分生成畫質。</li>



<li>善用系統記憶體（RAM）：如果筆電的共用記憶體與主記憶體足夠大，部分運行框架支援將部分運算卸載到 CPU 或系統記憶體，只是生成速度會變得很慢。</li>



<li>雲端算力替代：如果本地運行經常遇到顯示記憶體不足（Out of Memory）的錯誤，建議改用 Google Colab 或雲端 GPU 服務來執行。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Q: 如何找到降低精度與量化的版本?</h2>



<p class="wp-block-paragraph">在 Hugging Face 或開源社群尋找像 Qwen-Image-2.1 系列這種大型 DiT 模型的量化（Quantized）或降低精度版本時，主要有以下幾條途徑與搜尋方法：</p>



<h3 class="wp-block-heading">一、 直接在 Hugging Face 搜尋對應的量化庫</h3>



<p class="wp-block-paragraph">量化版本通常不會放在官方原版的 repo 中，而是由社群創作者（如 Unsloth、city96、leejet 等）轉換後發布。你可以直接在 Hugging Face 的搜尋列輸入以下關鍵字：</p>



<ul class="wp-block-list">
<li><strong>GGUF 版本（最適合 6GB VRAM 使用）：</strong>
<ul class="wp-block-list">
<li>搜尋：<code>Qwen-Image-2.1 GGUF</code> 或 <code>Qwen-Image-2.1-Turbo GGUF</code></li>



<li>核心量化層級選擇：
<ul class="wp-block-list">
<li><code>Q4_K_M</code>（約 4.2 GB）：最推薦 RTX 4050 6GB 使用，模型可以直接完全載入顯示記憶體。</li>



<li><code>Q5_0</code> / <code>Q5_K_M</code>（約 5.0 &#8211; 5.3 GB）：細節稍微提升，但可能會用到少許系統記憶體（RAM）做緩衝。</li>
</ul>
</li>
</ul>
</li>



<li><strong>INT8 / NF4 版本（Comfy-Org 官方或社群微調）：</strong>
<ul class="wp-block-list">
<li>搜尋：<code>Qwen-Image-2.1-Turbo-INT8</code> 或 <code>qwen_image_2.1_turbo_int8_convrot</code></li>



<li>這是直接將權重壓成 INT8 格式的 safetensors 檔案，適合傳統 ComfyUI 流程。</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">二、 搭配 ComfyUI 運行 GGUF 量化版（最建議的做法）</h3>



<p class="wp-block-paragraph">若要在 RTX 4050 上順利運行 GGUF 模型，單靠預設的 ComfyUI 節點可能會遇到「不支援架構」的錯誤。推薦使用支援該模型架構的擴充元件與組件：</p>



<ol start="1" class="wp-block-list">
<li><strong>安裝自訂節點：</strong>使用 <code>leejet</code> 維護的 <code>ComfyUI-GGUF</code> 套件（比舊版 city96 能更完善支援 Qwen 2.1 架構）。</li>



<li><strong>下載配套檔案：</strong>
<ul class="wp-block-list">
<li><strong>UNet / Diffusion 模型：</strong> 放至 <code>ComfyUI/models/unet/</code>，選擇下載好的 <code>.gguf</code> 檔案（例如 <code>qwen-image-2.1-Q4_K_M.gguf</code>）。</li>



<li><strong>Text Encoder（文本編碼器）：</strong>Qwen-Image 2.1 搭配的是 8B 的文本編碼器。為了省記憶體，同樣建議尋找量化過的文字編碼器，如 <code>qwen3vl_8b_w4a8.safetensors</code> 或 <code>qwen3vl_8b_int8</code> 並放入 <code>models/text_encoders/</code>。</li>
</ul>
</li>



<li><strong>替換節點：</strong>在 ComfyUI 工作流中，將原本的 <code>Load Diffusion Model</code> 替換為 <code>Unet Loader (GGUF)</code> 即可載入。</li>
</ol>



<h3 class="wp-block-heading">三、 使用整合型工具（如 Ollama / LM Studio 等，若支援）</h3>



<p class="wp-block-paragraph">如果不想手動設定 ComfyUI 工作流，也可以關注像是 <strong>Draw Things</strong>（Mac/iOS 支援，部分 Windows 框架亦有移植）或 <strong>Civitai</strong> 網站上是否有創作者上傳整理好的單一打包模型，通常搜尋欄位輸入 <code>Qwen-Image-2.1 Turbo Quantized</code> 即可找到相關資源。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><a href="https://huggingface.co/unsloth/Qwen-Image-2.1-Turbo-GGUF">https://huggingface.co/unsloth/Qwen-Image-2.1-Turbo-GGUF</a></h2>



<p class="wp-block-paragraph">which version best for my RTX 4050 Laptop 6GB RAM?</p>



<p class="wp-block-paragraph">▋ 最推薦的版本：Q4_K_M (4.2 GB)<sup></sup></p>



<p class="wp-block-paragraph">對於 RTX 4050 Laptop 6GB 顯示記憶體來說，<strong>Q4_K_M 是兼顧品質與運行成功率的最佳首選</strong>。</p>



<p class="wp-block-paragraph">▋ 各版本在 6GB 顯示記憶體下的實測建議與分析</p>



<p class="wp-block-paragraph">在生成圖片時，顯示記憶體（VRAM）除了要存放主要的 DiT 模型權重之外，還必須留出空間給：</p>



<ol start="1" class="wp-block-list">
<li><strong>Text Encoder（文本編碼器，如 Qwen3-VL 8B）：</strong> 需額外佔用約 0.7 &#8211; 2 GB 顯示記憶體。</li>



<li><strong>VAE 解碼器與 CUDA 計算緩衝區（Compute Buffers）：</strong> 約需 1.0 &#8211; 1.5 GB。</li>
</ol>



<p class="wp-block-paragraph">因此，模型的檔案大小不能剛好等於 6 GB，必須留出至少 1.5 &#8211; 2 GB 的動態開銷空間。</p>



<ul class="wp-block-list">
<li><strong>Q4_K_M (4.2 GB) —— 最平衡首選</strong>
<ul class="wp-block-list">
<li><strong>分析：</strong>模型本身佔用約 4.2 GB。搭配 ComfyUI 的模型卸載（CPU Offloading）機制，可以在 6GB VRAM 內剛好塞下，避免發生 OOM（Out of Memory）記憶體溢位崩潰。</li>



<li><strong>畫質損耗：</strong> Unsloth 的 Dynamic 4-bit 量化對畫面細節與文字遵循能力保留得相當完整，幾乎感受不出明顯品質下降。</li>
</ul>
</li>



<li><strong>Q4_K_S (3.91 GB) —— 備用方案（記憶體不夠時使用）</strong>
<ul class="wp-block-list">
<li><strong>分析：</strong> 檔案更小，如果你的系統背景程式佔用了較多 VRAM（例如開著 Chrome 或多個螢幕），導致 Q4_K_M 報錯，請改用此版本。</li>
</ul>
</li>



<li><strong>Q5_K_S / Q5_K_M (4.5 GB &#8211; 5.39 GB) —— 邊緣臨界，易爆 VRAM</strong>
<ul class="wp-block-list">
<li><strong>分析：</strong>4.5 GB 以上的檔案加上 VAE 與編碼器開銷後，非常容易超過 6GB 限制。若要硬跑，系統會被迫將部分模型卸載到電腦的主記憶體（RAM），導致生成速度變得很慢。</li>
</ul>
</li>



<li><strong>Q6_K / Q6_K_XL (6.27 GB &#8211; 6.72 GB) —— 完全不建議</strong>
<ul class="wp-block-list">
<li><strong>分析：</strong>單模型檔案大小就已超過你的顯示記憶體上限。</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">▋ 6GB VRAM 順暢運行的關鍵設定</p>



<ol start="1" class="wp-block-list">
<li><strong>圖形解析度：</strong>請先從 <strong>1024&#215;1024</strong> 開始生成。切勿一開始就設為 2048&#215;2048。</li>



<li><strong>Text Encoder (文本編碼器)：</strong>建議下載量化過的文字編碼器（如 <code>qwen3vl_8b_w4a8</code> 或 <code>int8</code>），或者在 ComfyUI 中開啟 <strong>CPU Text Encoder</strong> 模式，把文字處理丟給電腦主記憶體。</li>



<li><strong>VAE 設定：</strong>在 ComfyUI 的 VAE Loader 設定中開啟 <strong>Tiled VAE</strong>（區塊解碼），可以大幅節省生成最後階段的 VRAM 峰值用量。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Q: 如何串接 AI agent CLI 生圖</h2>



<p class="wp-block-paragraph">要把文字互動介面與本地端生圖模型串接起來，主要有兩個常見且可行的解法。</p>



<p class="wp-block-paragraph">第一種是透過 ComfyUI 內建的 API 伺服器來串接。ComfyUI 啟動後會在背景提供一套完整的網頁伺服器與 API 功能。你可以在終端機裡寫一個簡單的 Python 程式或是用 curl 指令，把提示詞打包成 JSON 格式發送到 ComfyUI 的連接埠，讓它在背景自動生成圖片並回傳檔案路徑。驗證這個步驟是否成功的方法是，打開瀏覽器確認網頁伺服器有正常回應。</p>



<p class="wp-block-paragraph">第二種是自己寫一個輕量化的轉接層。有些 agent cli 工具預設只能對接類似 OpenAI 的文字或圖片生成 API。你可以用 Python 的 FastAPI 寫一個簡單的伺服器，對外提供標準的 API 介面，當收到終端機傳來的生圖請求時，程式再自動去呼叫底層的生圖工具。驗證這個步驟是否成功的方法是，用終端機發送一筆測試請求，確認伺服器能夠正確接收並處理。</p>



<h2 class="wp-block-heading">give me solution for my RTX 4050 Laptop 6GB VRAM</h2>



<p class="wp-block-paragraph">你的顯示記憶體只有 6GB ，直接執行官方的原始程式碼一定會立刻跳出記憶體不足的錯誤。為了讓程式碼能在你的電腦上順利跑完，我們必須修改幾個關鍵設定，包含大幅降低圖片解析度以及開啟 CPU 記憶體卸載功能。</p>



<p class="wp-block-paragraph">這是一份修改過後的 Python 程式碼，可以讓你在 RTX 4050 上面嘗試執行：</p>



<p class="wp-block-paragraph">Python</p>



<pre class="wp-block-code"><code>import torch
from diffusers import QwenImage21Pipeline

pipe = QwenImage21Pipeline.from_pretrained(
    "Qwen/Qwen-Image-2.1-Turbo",
    torch_dtype=torch.float16,
)

pipe.enable_model_cpu_offload()

prompt = "A vertically oriented educational chemistry study poster fills the frame, designed like a hand-drawn classroom notes page with a clean white background, thick dark navy-blue outer border, and thin inner navy rectangular border lines. The overall aesthetic is colorful, high-contrast, and hand-lettered in a signature Topper style using marker and watercolor effects. At the top left, a small header in black handwriting reads \"Topper’s Notes\", accented by short decorative navy strokes radiating from it. Centered across the top is the main lesson heading in large stylized black-and-yellow bold lettering: \"Science – Chapter 1:\" followed below by the much larger title \"Chemical Reactions and Equations\". The title has energetic yellow highlighter strokes underneath and small sparkle doodles nearby. At the top right, inside a small outlined page-number box, the label reads \"Page X\". Below the header, a wide rounded rectangle framed in dark navy contains a pink brush-stroke highlight banner with the subheading \"IMPORTANT CHEMICAL EQUATION:\" in black. Inside the same equation panel, the reaction is written very large as \"Fe + CuSO₄ → FeSO₄ + Cu\". The symbol \"Fe\" is highlighted in bright orange with an underline, \"CuSO₄\" is vivid blue and underlined, \"FeSO₄\" is bright green and underlined, and \"Cu\" is metallic bronze and underlined. Beneath the left side of the equation is a lavender rounded label connected by a leader line to the iron component; it says \"Reactants\" and below it \"(before the reaction)\", with \"Reactants\" colored purple and underlined. Beneath the right side is a matching pale-green rounded label connected by a leader line to the products; it says \"Products\" and below it \"(after the reaction)\", with \"Products\" colored green and underlined. Along the lower part of this equation box are stylized ball-and-stick molecule models and atomic orbital drawings: a silver-gray sphere labeled \"Fe\" sits left of center, another cluster shows gray, red, and white spheres representing sulfate groups around blue and green atoms, and a copper-colored sphere appears near the right. Small atom-and-orbit icons sit along the bottom edge of the panel in blue, green, and brown outline styles. The middle portion is enclosed by a large dashed green rounded rectangle titled \"REACTIVITY CONCEPT\" in green uppercase letters with decorative rays. On the left side of this section, a stepped vertical reactivity-series ladder is drawn with alternating colored horizontal blocks. From top to bottom it lists \"K\", \"Na\", \"Ca\", \"Mg\", \"Al\", \"Zn\", \"Fe\" with a green oval callout reading \"(More reactive)\", then continues downward with \"Cu\", \"Ag\", \"Hg\", \"Pt\", \"Au\" with an orange oval callout reading \"(Less reactive)\". Each step is marked by a dark upward-pointing triangular arrow, showing decreasing reactivity downward. In the central-right area, full body text as follows: \"Iron (Fe) is higher in the reactivity series than Copper (Cu). This makes Iron more reactive.\" Below this definition is a visual arrow sequence of overlapping circles: orange \"Fe\" overlaps blue \"Cu\", followed by a red arrow pointing toward the phrase \"→ More displacement ↓ less reactivity\". Under it, a prominent statement in dark handwriting reads \"More reactive element displaces less reactive element\", emphasized with a bright pink marker underline. On the right, a shield-like badge with a navy outline and light interior contains the bold contrasting font label \"DISPLACEMENT REACTION\". Below the badge, full body text as follows: \"A more reactive element displaces a less reactive element from its compound.\" The badge includes an internal miniature illustration of circular electron shells and colored particles, reinforcing the chemistry theme. Directly below the reactivity concept box, a smaller rounded rectangular table titled \"PROPERTY COMPARISON\" compares Iron and Copper. The table has three columns: \"IRON (Fe)\", \"COPPER (Cu)\", and a central property column. Row one reads \"Reactivity series position\" with Iron shown as \"Higher (more reactive)\" and Copper shown as \"Lower (less reactive)\". Row two reads \"Displaces hydrogen from acids\" with Iron marked \"Yes\" and Copper marked \"No\". Row three reads \"Displaced by other metals\" with Iron shown as \"Displaced by Zn, Mg, Al, etc.\" and Copper shown as \"Displaced by Fe, Zn, Mg, etc.\". Row four reads \"Oxidation tendency\" with Iron shown as \"Gets oxidized easily\" and Copper shown as \"Does not get oxidized easily\". Row five reads \"Useful property\" with Iron described as \"Used where strength &amp; corrosion resistance needed\" and Copper described as \"Used where conductivity &amp; corrosion resistance needed\". The table uses thin navy grid lines, orange tinting for the iron column, pale blue tinting for the copper column, black condensed handwriting, and green checkmarks next to positive traits and red crosses next to limitations. The lower half introduces a large illustrated experiment section with a cream highlight behind the title \"BEFORE/AFTER IRON NAIL EXPERIMENT\" in bold dark navy uppercase letters. The section is split visually into a left \"Before\" state and a right \"After\" state. On the left, a beaker filled with bright blue solution contains a shiny silver iron nail standing diagonally. The beaker label reads \"CuSO₄(aq)\" in blue, and the bottom label near the nail reads \"Fe(s)\". A rounded annotation box beside the beaker says \"Initial State: Blue Solution &amp; Silver Nail\". Two glass test tubes stand to the right of this beaker: the upper test tube is filled with blue solution and labeled \"CuSO₄(aq)\", while the lower one contains a pale green liquid. Red and blue directional arrows connect these visual elements to explanations: one red arrow points toward the nail with the text \"Copper gets deposited\", and one blue arrow points toward the liquid with the text \"Solution turns pale green\". On the right side of the experiment section, another beaker shows the after state with the iron nail now coated in brown copper deposits. Its solution is faded to a pale greenish-blue, and the beaker label reads \"FeSO₄(aq)\" in green. A nearby rounded annotation box says \"Final State: Pale Green Solution &amp; Brown Coated Nail\". To the far right are additional test tubes and ion symbols: a test tube containing blue liquid labeled \"CuSO₄(aq)\" with the note \"Blue colour due to Cu²⁺ ions\", and a lower test tube containing pale green liquid labeled \"FeSO₄(aq)\" with the note \"Pale green due to Fe²⁺ ions\". Beside them are ion diagrams labeled \"Cu²⁺(aq)\" and \"Fe²⁺(aq)\", shown as colored charged particles encircled by simple electron-shell rings; the copper ion uses blue particles and the iron ion uses green particles. Small sparkles, arrows, and dashed guide lines emphasize the transformation from blue copper sulfate solution to pale green iron sulfate solution. Near the bottom, a horizontal summary strip has a blue star icon followed by the heading \"SUMMARY\". Full body text as follows: \"In this reaction, Iron (Fe) displaces Copper (Cu) from Copper Sulphate (CuSO₄) solution to form Iron(II) Sulphate (FeSO₄) solution and Copper (Cu).\" To the lower left, a bordered key-points box titled \"KEY POINTS\" lists three starred bullet statements: \"Iron is above copper in the reactivity series.\", \"Fe displaces Cu from CuSO₄ solution.\", and \"Result: Blue solution turns pale green; copper metal deposits on iron nail.\" At the very bottom, inside the footer border, spaced text reads \"CHEMISTRY • CHANGE IS EVERYWHERE • TOPPER’S NOTES\", flanked by small leaf-like ornaments and thin horizontal divider lines. The entire page uses dark navy borders and headings, bright highlighter strokes, watercolor shading, realistic glassware illustrations, and playful scientific doodles to present a chemistry lesson clearly and attractively."

image = pipe(
    prompt=prompt,
    width=512,
    height=768,
    use_kv_cache=True,
    generator=torch.Generator("cpu").manual_seed(42),
).images&#91;0]

image.save("t2i.png")</code></pre>



<p class="wp-block-paragraph">這段程式碼做了三個重要的調整。第一是把圖片解析度從原本極大的尺寸縮小成 512 乘以 768 像素，因為原本的尺寸需要十六 GB 以上的顯示記憶體才能運作。第二是加入了記憶體卸載指令，當顯示記憶體快滿時，會自動把運算暫存搬到電腦的主記憶體。第三是把資料型態改成了 float16 ，這樣可以省下一半的記憶體空間。</p>



<p class="wp-block-paragraph">因為這張顯示卡的記憶體容量有限，啟動記憶體卸載之後，生圖的速度會變得非常慢，需要耐心等待程式執行完畢。如果執行時還是出現記憶體不足的錯誤，可以把解析度再調得更小一點。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Q: 上述範例 512&#215;768 會占用多少記憶體, 我如果下載 5-bit mode: Q5_K_S (4.5 GB), 會造成 OOM ?</h2>



<p class="wp-block-paragraph">在 <code>512x768</code> 解析度下，圖片本身的潛在空間與運算暫存區大約會佔用 <code>1 GB</code> 到 <code>1.5 GB</code> 的顯示記憶體。</p>



<p class="wp-block-paragraph">如果下載 <code>5-bit</code> 的 <code>Q5_K_S</code> 版本（檔案大小約 <code>4.5 GB</code>），在 <code>RTX 4050</code> 的 <code>6 GB</code> 顯示記憶體上極有可能會發生 <code>OOM</code> 錯誤，或是被迫將大量運算卸載到系統記憶體，導致生圖速度變得極慢。</p>



<p class="wp-block-paragraph">原因在於 <code>6 GB</code> 顯示記憶體扣掉 <code>4.5 GB</code> 的模型檔案後，只剩下大約 <code>1.5 GB</code> 的空間。然而生圖過程還需要載入文字編碼器與解碼器，這些元件加起來很容易超過 <code>6 GB</code> 的極限。</p>



<p class="wp-block-paragraph">建議還是選擇檔案更小的 <code>4-bit</code> 版本，例如 <code>Q4_K_M</code> 或 <code>Q4_K_S</code>，才能在 <code>6 GB</code> 顯示記憶體上比較穩定地運行。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">改寫範例scrpt 為 resetful api + fastapi, input prompt + output target filepath</h2>



<p class="wp-block-paragraph">I will now generate the FastAPI script for running the Qwen-Image-2.1-Turbo model as a RESTful service.</p>



<p class="wp-block-paragraph">I have created the FastAPI script <code>qwen_api_server.py</code>.</p>



<pre class="wp-block-code"><code>from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
import torch
from diffusers import QwenImage21Pipeline
import os

app = FastAPI(
    title="Qwen-Image-2.1-Turbo REST API",
    description="A lightweight FastAPI service to generate images via Qwen-Image-2.1-Turbo and save them to a specified target file path.",
    version="1.0.0"
)

# Global pipeline variable to hold the model in memory
pipe = None

@app.on_event("startup")
def load_model():
    global pipe
    print("Loading Qwen-Image-2.1-Turbo model into GPU memory...")
    try:
        pipe = QwenImage21Pipeline.from_pretrained(
            "Qwen/Qwen-Image-2.1-Turbo",
            torch_dtype=torch.bfloat16,
        ).to("cuda")
        # Uncomment the line below if you run into 6GB VRAM OOM issues:
        # pipe.enable_model_cpu_offload()
        print("Model loaded successfully!")
    except Exception as e:
        print(f"Failed to load model: {e}")
        raise e

class GenerateRequest(BaseModel):
    prompt: str = Field(..., description="The text description for image generation.")
    output_path: str = Field(..., description="The target file path where the generated image will be saved.")
    width: int = Field(512, description="Image width (recommended lower for 6GB VRAM, e.g., 512 or 768).")
    height: int = Field(768, description="Image height (recommended lower for 6GB VRAM, e.g., 768 or 1024).")
    seed: int = Field(42, description="Random seed for reproducibility.")

@app.post("/generate", status_code=status.HTTP_200_OK)
def generate_image(req: GenerateRequest):
    global pipe
    if pipe is None:
        raise HTTPException(status_code=500, detail="Model is not loaded yet.")
    
    try:
        # Ensure output directory exists if a subdirectory is provided
        output_dir = os.path.dirname(req.output_path)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)

        # Run inference
        image = pipe(
            prompt=req.prompt,
            width=req.width,
            height=req.height,
            use_kv_cache=True,
            generator=torch.Generator("cpu").manual_seed(req.seed),
        ).images&#91;0]

        # Save to target filepath
        image.save(req.output_path)

        return {
            "status": "success",
            "message": f"Image successfully generated and saved.",
            "output_path": req.output_path
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Image generation failed: {str(e)}")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("qwen_api_server:app", host="127.0.0.1", port=8000, reload=False)</code></pre>



<h3 class="wp-block-heading">How to use it:</h3>



<ol class="wp-block-list">
<li>Run the server using: <code>python qwen_api_server.py</code></li>



<li>You can send a POST request from your CLI agent (like <code>pi agent</code>, <code>agy cli</code>, or <code>curl</code>) to <code>[http://127.0.0.1:8000/generate](http://127.0.0.1:8000/generate)</code> with a JSON body containing your <code>prompt</code> and <code>output_path</code>.</li>
</ol>



<p class="wp-block-paragraph">Let me know if you want an example <code>curl</code> command or client script to test this API!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">FastAPI script for image editing using the Qwen-Image-2.1-Turbo pipeline, incorporating memory optimizations for your RTX 4050 6GB VRAM.</p>



<pre class="wp-block-code"><code>from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
import torch
from diffusers import QwenImage21Pipeline
from diffusers.utils import load_image
import os

app = FastAPI(
    title="Qwen-Image-2.1-Turbo Image Editing REST API",
    description="A FastAPI service for image-to-image / image editing using Qwen-Image-2.1-Turbo, optimized for 6GB VRAM.",
    version="1.0.0"
)

pipe = None

@app.on_event("startup")
def load_model():
    global pipe
    print("Loading Qwen-Image-2.1-Turbo pipeline for image editing into GPU memory...")
    try:
        pipe = QwenImage21Pipeline.from_pretrained(
            "Qwen/Qwen-Image-2.1-Turbo",
            torch_dtype=torch.float16, # Using float16 to conserve VRAM
        )
        
        pipe.enable_model_cpu_offload()
        print("Model and CPU offload successfully configured!")
    except Exception as e:
        print(f"Failed to load model: {e}")
        raise e

class EditRequest(BaseModel):
    prompt: str = Field(..., description="The editing text prompt describing the transformation.")
    input_image_path_or_url: str = Field(..., description="Local file path or URL of the source image/sketch.")
    output_path: str = Field(..., description="The target file path where the edited image will be saved.")
    width: int = Field(1024, description="Image width (scaled down to 1024 or 512 for 6GB VRAM safety).")
    height: int = Field(1024, description="Image height (scaled down to 1024 or 512 for 6GB VRAM safety).")
    seed: int = Field(20261009, description="Random seed for reproducibility.")

@app.post("/edit-image", status_code=status.HTTP_200_OK)
def edit_image(req: EditRequest):
    global pipe
    if pipe is None:
        raise HTTPException(status_code=500, detail="Model is not loaded yet.")
    
    try:
        if req.input_image_path_or_url.startswith("http://") or req.input_image_path_or_url.startswith("https://"):
            input_image = load_image(req.input_image_path_or_url).convert("RGBA")
        else:
            if not os.path.exists(req.input_image_path_or_url):
                raise HTTPException(status_code=400, detail=f"Input image path not found: {req.input_image_path_or_url}")
            input_image = load_image(req.input_image_path_or_url).convert("RGBA")

        # Ensure output directory exists
        output_dir = os.path.dirname(req.output_path)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)

        image = pipe(
            prompt=req.prompt,
            image=input_image,
            width=req.width,
            height=req.height,
            use_kv_cache=True,
            generator=torch.Generator("cpu").manual_seed(req.seed),
        ).images&#91;0]

        # Save to target filepath
        image.save(req.output_path)

        return {
            "status": "success",
            "message": "Image editing completed and saved successfully.",
            "output_path": req.output_path
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Image editing failed: {str(e)}")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("qwen_edit_api:app", host="127.0.0.1", port=8000, reload=False)</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Q: /generate api and /edit-image api 怎麼寫會比較好, 如果後續還有其他 API 要增加.</h2>



<p class="wp-block-paragraph">如果要讓 FastAPI 專案未來方便擴充更多 API，建議採用模組化設計與依賴注入的概念。</p>



<p class="wp-block-paragraph">第一點是使用 APIRouter 來拆分路由。不要把所有端點都寫在 main.py 裡面。可以建立一個 routers 資料夾，把生圖功能放在 routers/generation.py，把圖片編輯放在 routers/editing.py，最後再用 app.include_router 整合進主程式。</p>



<p class="wp-block-paragraph">第二點是使用依賴注入管理模型實體。因為模型佔用大量顯示記憶體，不適合每次請求都重新載入。可以在程式啟動時初始化一次管線，透過 FastAPI 的 Depends 或全域狀態共用同一個 pipeline 執行個體。</p>



<p class="wp-block-paragraph">第三點是統一請求與回應格式。未來如果加入文字生成或其他功能，輸入與輸出的結構可以用 Pydantic 模型嚴格定義，這樣 agent 呼叫時才能確保欄位正確。</p>



<p class="wp-block-paragraph">專案資料夾結構可以這樣規劃：</p>



<pre class="wp-block-code"><code>qwen_img_fastapi/
│
├── main.py
├── services/
│   └── model_loader.py
└── routers/
    ├── generation.py
    └── editing.py</code></pre>



<p class="wp-block-paragraph">透過這種架構，未來如果需要新增其他 API，只要在 routers 裡面多寫一支檔案，並在 main.py 註冊即可，維護起來會更有條理。</p>



<p class="wp-block-paragraph"><strong>main.py</strong></p>



<pre class="wp-block-code"><code>from fastapi import FastAPI
from routers import generation, editing

app = FastAPI(
    title="Qwen-Image-2.1-Turbo Modular REST API",
    description="A modular FastAPI service for image generation and editing using Qwen-Image-2.1-Turbo, optimized for RTX 4050 6GB VRAM.",
    version="1.0.0"
)

# Include routers for different capabilities
app.include_router(generation.router)
app.include_router(editing.router)

@app.get("/", tags=&#91;"Root"])
def root():
    return {
        "status": "online",
        "message": "Welcome to Qwen-Image-2.1-Turbo API. Check /docs for Swagger UI documentation."
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=False)</code></pre>



<p class="wp-block-paragraph"><strong>services/model_loader.py</strong></p>



<pre class="wp-block-code"><code>import torch
from diffusers import QwenImage21Pipeline

# Global container for the shared pipeline
_pipe = None

def get_pipeline() -&gt; QwenImage21Pipeline:
    global _pipe
    if _pipe is None:
        print("Loading Qwen-Image-2.1-Turbo pipeline into memory...")
        try:
            _pipe = QwenImage21Pipeline.from_pretrained(
                "Qwen/Qwen-Image-2.1-Turbo",
                torch_dtype=torch.float16,
            )
            # Enable CPU offloading to prevent OOM errors on 6GB VRAM GPUs
            _pipe.enable_model_cpu_offload()
            print("Pipeline successfully loaded and CPU offload enabled!")
        except Exception as e:
            print(f"Failed to load pipeline: {e}")
            raise e
    return _pipe</code></pre>



<p class="wp-block-paragraph"><strong>routers/generation.py</strong></p>



<pre class="wp-block-code"><code>from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field
import torch
import os
from services.model_loader import get_pipeline

router = APIRouter(prefix="/generation", tags=&#91;"Image Generation"])

class GenerateRequest(BaseModel):
    prompt: str = Field(..., description="The text description for image generation.")
    output_path: str = Field(..., description="The target file path where the generated image will be saved.")
    width: int = Field(512, description="Image width (keep low, e.g. 512 or 768 for 6GB VRAM).")
    height: int = Field(768, description="Image height (keep low, e.g. 768 or 1024 for 6GB VRAM).")
    seed: int = Field(42, description="Random seed for reproducibility.")

@app.post("/text-to-image" if False else "/generate", status_code=status.HTTP_200_OK)
def generate_image(req: GenerateRequest):
    try:
        pipe = get_pipeline()
        
        output_dir = os.path.dirname(req.output_path)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)

        image = pipe(
            prompt=req.prompt,
            width=req.width,
            height=req.height,
            use_kv_cache=True,
            generator=torch.Generator("cpu").manual_seed(req.seed),
        ).images&#91;0]

        image.save(req.output_path)

        return {
            "status": "success",
            "message": "Image generated successfully.",
            "output_path": req.output_path
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Generation failed: {str(e)}")</code></pre>



<p class="wp-block-paragraph"><strong>routers/editing.py</strong></p>



<pre class="wp-block-code"><code>from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field
import torch
import os
from diffusers.utils import load_image
from services.model_loader import get_pipeline

router = APIRouter(prefix="/editing", tags=&#91;"Image Editing"])

class EditRequest(BaseModel):
    prompt: str = Field(..., description="The editing text prompt describing the transformation.")
    input_image_path_or_url: str = Field(..., description="Local file path or URL of the source image/sketch.")
    output_path: str = Field(..., description="The target file path where the edited image will be saved.")
    width: int = Field(1024, description="Image width.")
    height: int = Field(1024, description="Image height.")
    seed: int = Field(20261009, description="Random seed for reproducibility.")

@router.post("/edit-image", status_code=status.HTTP_200_OK)
def edit_image(req: EditRequest):
    try:
        pipe = get_pipeline()

        if req.input_image_path_or_url.startswith("http://") or req.input_image_path_or_url.startswith("https://"):
            input_image = load_image(req.input_image_path_or_url).convert("RGBA")
        else:
            if not os.path.exists(req.input_image_path_or_url):
                raise HTTPException(status_code=400, detail=f"Input image not found: {req.input_image_path_or_url}")
            input_image = load_image(req.input_image_path_or_url).convert("RGBA")

        output_dir = os.path.dirname(req.output_path)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)

        image = pipe(
            prompt=req.prompt,
            image=input_image,
            width=req.width,
            height=req.height,
            use_kv_cache=True,
            generator=torch.Generator("cpu").manual_seed(req.seed),
        ).images&#91;0]

        image.save(req.output_path)

        return {
            "status": "success",
            "message": "Image editing completed successfully.",
            "output_path": req.output_path
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Image editing failed: {str(e)}")</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Q: 為什麼執行  main.py 之後, 畫面顯示還要下載其他檔案?</h2>



<p class="wp-block-paragraph">這是因為一個完整的文生圖 Pipeline（QwenImage21Pipeline）不只有 Transformer 主模型，還包含其他必備元件：</p>



<h3 class="wp-block-heading">1. 為什麼本機有了 .gguf 還在下載？</h3>



<p class="wp-block-paragraph">在 model_loader.py:35-39 中：</p>



<pre class="wp-block-code"><code># 1. 這裡載入的是您本機已有的 Transformer 權重 (約 4GB 的 GGUF)
transformer = QwenImage21Transformer2DModel.from_single_file(GGUF_MODEL_PATH, ...)

# 2. 這裡組裝整個 Pipeline：
_pipe = QwenImage21Pipeline.from_pretrained(
    "Qwen/Qwen-Image-2.1-Turbo",
    transformer=transformer,   # &lt;--- Transformer 替換成本地的 GGUF
    torch_dtype=torch.bfloat16,
)</code></pre>



<p class="wp-block-paragraph">文生圖 Pipeline 除了 Transformer（降噪核心）之外，還需要：</p>



<ol class="wp-block-list">
<li>Text Encoder（文字編碼器 / Tokenizer）：負責把您的英文 Prompt 轉成模型看得懂的語意向量。</li>



<li>VAE（變分自編碼器）：負責將潛空間特徵解碼還原成真實的 PNG/JPEG 像素圖片。</li>



<li>Scheduler / Config 設定檔：去噪步數調度器。</li>
</ol>



<p class="wp-block-paragraph">因為您的專案目錄目前只有 Qwen/qwen-image-2.1-turbo-Q4_K_M.gguf（只有 Transformer 本身），所以 Hugging Face Diffusers 會自動去 HuggingFace 官方下載其餘的文字編碼器（Text Encoder）與 VAE。<br>──────</p>



<h3 class="wp-block-heading">2. 需要下載多少？之後每次都要下載嗎？</h3>



<p class="wp-block-paragraph">• 只需要下載這一次：下載後的檔案會保存在本機快取目錄（C:\Users\max32.cache\huggingface\hub）。下一次再啟動或呼叫 API 時，就會直接讀取快取，不會再次重複下載。<br>• 檔案大小：主要是 Text Encoder 與 VAE，下載量約數 GB。</p>



<p class="wp-block-paragraph">等待這次背景下載完畢後，模型就會自動完成組裝並產出圖片！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">在 Diffusers 架構中，每個 Pipeline 需要下載哪些元件是由倉庫根目錄的 model_index.json 定義的。<br>對於 Qwen/Qwen-Image-2.1-Turbo，它需要的資料夾元件如下：<br>• 官方倉庫頁面：<a href="https://huggingface.co/Qwen/Qwen-Image-2.1-Turbo/tree/main">https://huggingface.co/Qwen/Qwen-Image-2.1-Turbo/tree/main</a><br>──────</p>



<h3 class="wp-block-heading">一、您需要手動下載的檔案清單</h3>



<p class="wp-block-paragraph">因為您已經有 transformer（即您本機的 qwen-image-2.1-turbo-Q4_K_M.gguf），不需要 下載倉庫中的 transformer/ 資料夾（這部分就佔了十幾 GB）。<br>您只需要下載其餘的 4 個元件資料夾 與 根目錄設定檔：</p>



<h4 class="wp-block-heading">1. 根目錄設定檔</h4>



<p class="wp-block-paragraph">• model_index.json</p>



<h4 class="wp-block-heading">2. text_encoder/ 資料夾</h4>



<p class="wp-block-paragraph">│ 文字編碼器（Qwen3-VL 文本主幹），主要權重檔比較大：</p>



<p class="wp-block-paragraph">• config.json<br>• generation_config.json<br>• model.safetensors（或是 model.safetensors.index.json 與相關的 .safetensors 分卷）</p>



<h4 class="wp-block-heading">3. processor/ 資料夾（含 Tokenizer）</h4>



<p class="wp-block-paragraph">• chat_template.json<br>• preprocessor_config.json<br>• processor_config.json<br>• tokenizer_config.json<br>• vocab.json / merges.txt（或相關的 tokenizer 檔案）</p>



<h4 class="wp-block-heading">4. vae/ 資料夾</h4>



<p class="wp-block-paragraph">│ 負責把潛向量轉為實際圖片：<br>• config.json<br>• diffusion_pytorch_model.safetensors</p>



<h4 class="wp-block-heading">5. scheduler/ 資料夾</h4>



<p class="wp-block-paragraph">• scheduler_config.json<br>──────</p>



<h3 class="wp-block-heading">二、手動下載後如何放置與修改程式碼？</h3>



<p class="wp-block-paragraph">建議在專案中建立一個本地目錄（例如 models/Qwen-Image-2.1-Turbo），並把上述檔案依照相同結構放好：</p>



<pre class="wp-block-code"><code>C:\Users\max32\Downloads\qwen_img_fastapi\
 ├── Qwen\
 │    └── qwen-image-2.1-turbo-Q4_K_M.gguf
 └── models\
      └── Qwen-Image-2.1-Turbo\
           ├── model_index.json
           ├── text_encoder\
           ├── processor\
           ├── vae\
           └── scheduler\</code></pre>



<p class="wp-block-paragraph">然後在 model_loader.py 將 BASE_MODEL_ID 改成此本機路徑：</p>



<pre class="wp-block-code"><code>BASE_MODEL_ID = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
    "models",
    "Qwen-Image-2.1-Turbo"
)</code></pre>



<p class="wp-block-paragraph">修改後，Pipeline 就不會連網下載任何東西，而是直接從本機硬碟快速載入！<br>──────</p>



<h3 class="wp-block-heading">三、推薦的高速下載方式（比瀏覽器手動點一個個檔案快）</h3>



<p class="wp-block-paragraph">如果您使用瀏覽器一個個點比較繁瑣，推薦使用支援多執行緒並發下載的工具：</p>



<h4 class="wp-block-heading">方式 A：使用 hf-transfer（推薦，通常可跑滿寬頻）</h4>



<p class="wp-block-paragraph">在終端機安裝並開啟加速：</p>



<pre class="wp-block-code"><code>pip install -U hf-transfer huggingface_hub
$env:HF_HUB_ENABLE_HF_TRANSFER = "1"</code></pre>



<p class="wp-block-paragraph">接著使用指令直接下載排除 transformer 以外的所有檔案至本機資料夾：</p>



<pre class="wp-block-code"><code>huggingface-cli download Qwen/Qwen-Image-2.1-Turbo --exclude "transformer/*" --local-dir models/Qwen-Image-2.1-Turbo</code></pre>



<h4 class="wp-block-heading">方式 B：使用 IDM / 多線程下載器</h4>



<p class="wp-block-paragraph">如果網路對 Hugging Face 有限制，亦可利用鏡像站（如 hf-mirror.com）：</p>



<pre class="wp-block-code"><code>$env:HF_ENDPOINT = "https://hf-mirror.com"
huggingface-cli download Qwen/Qwen-Image-2.1-Turbo --exclude "transformer/*" --local-dir models/Qwen-Image-2.1-Turbo</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">驗證與除錯用curl</p>



<p class="wp-block-paragraph">Minimal curl (omit any optional field to use defaults), linux / macOS:</p>



<pre class="wp-block-code"><code>curl -X POST "http://127.0.0.1:8000/generation/generate" \
  -H "Content-Type: application/json" \
  -d "{\"prompt\": \"a sunset over the ocean\", \"output_path\": \"output/image.png\"}"</code></pre>



<p class="wp-block-paragraph">for Windows: </p>



<pre class="wp-block-code"><code>curl -X POST "http://127.0.0.1:8000/generation/generate" ^
  -H "Content-Type: application/json" ^
  -d "{\"prompt\": \"a sunset over the ocean\", \"output_path\": \"output/image.png\"}"</code></pre>



<p class="wp-block-paragraph">With custom values:</p>



<pre class="wp-block-code"><code>curl -X POST "http://127.0.0.1:8000/generation/generate" \
  -H "Content-Type: application/json" \
  -d "{\"prompt\": \"a sunset over the ocean\", \"output_path\": \"output/image.png\", \"width\": 768,
</code></pre>



<p class="wp-block-paragraph">&#8220;height&#8221;: 1024, &#8220;seed&#8221;: 123}&#8221;</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">執行失敗</p>



<p class="wp-block-paragraph">[W1013 21:31:02.000000000 CUDACachingAllocator.cpp:3934] memory allocation failed with OOM on device 0 while trying to allocate 33554432 bytes (free: 0, total: 6438780928).<br>[W1013 21:31:02.000000000 CUDACachingAllocator.cpp:3934] memory allocation failed with OOM on device 0 while trying to allocate 33554432 bytes (free: 0, total: 6438780928).<br>INFO: 127.0.0.1:5457 &#8211; &#8220;POST /generation/generate HTTP/1.1&#8221; 500 Internal Server Error</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">您的 Transformer 確實已經正確載入為 Q4_K_M GGUF，沒有載入到原本數十 GB 的 Transformer。</p>



<p class="wp-block-paragraph">導致顯存不足（OOM，總共 6GB 卻嘗試分配超出顯存）的真正元兇是：<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> text_encoder（文字編碼器，即 Qwen3VL）。</p>



<ol class="wp-block-list">
<li>體積對比：<br>• 您量化後的 Transformer（qwen-image-2.1-turbo-Q4_K_M.gguf）：只有 約 3.9 GB。<br>• 官方未量化的 text_encoder/model.safetensors：高達 16.3 GB（BF16/FP16）！</li>



<li>為什麼報錯：<br>• 當呼叫 pipe(&#8230;) 生成圖片的第一步，Pipeline 會先執行 text_encoder 來解析 prompt。<br>• 雖然設定了 enable_model_cpu_offload()，但在執行 text_encoder 時，會嘗試將這 16.3 GB 的整個文字編碼器移進 GPU<br>顯存；但您的 RTX 4050 只有 6 GB 顯存，因此在瞬間爆顯存（OOM）。</li>
</ol>



<p class="wp-block-paragraph">──────</p>



<h3 class="wp-block-heading">解決方案</h3>



<p class="wp-block-paragraph">對於 6GB VRAM，有以下兩種解決方案：</p>



<h4 class="wp-block-heading">方案 1：啟用 enable_sequential_cpu_offload()（最直接，無需下載新模型）</h4>



<p class="wp-block-paragraph">Diffusers 提供了層級依序卸載（Sequential CPU Offload）。它不會把整個 16GB text_encoder 一次性塞入<br>GPU，而是一層一層搬進 GPU 運算後立刻移出，完美相容 6GB 甚至 4GB 顯存。<br>• 優點：馬上能跑，不需要額外下載任何量化 text_encoder。<br>• 缺點：文字編碼階段會稍慢一些。</p>



<h4 class="wp-block-heading">方案 2：對 text_encoder 使用 bitsandbytes 4-bit 量化（推薦長期使用）</h4>



<p class="wp-block-paragraph">使用 BitsAndBytesConfig(load_in_4bit=True) 載入文字編碼器，將 16GB 的 text_encoder 直接壓縮至約 4GB 甚至更小。</p>



<ol class="wp-block-list">
<li>方案 1（enable_sequential_cpu_offload）最簡單、即插即用：<br>• 它是 Diffusers 官方內建的顯存極限優化機制。<br>• 不需要安裝額外套件（目前您的環境尚未安裝 bitsandbytes，若在 Windows 安裝 bitsandbytes 常有 CUDA<br>編譯環境相容性問題）。<br>• 它會將模型逐層（Layer-by-layer）調度進 6GB 顯存，執行完立即釋放，16GB 的 text_encoder 也能順利執行，徹底解決<br>OOM。</li>



<li>兩者的關係是二選一：<br>• 方案 1 是透過「分批搬移權重」來省顯存。<br>• 方案 2 是透過「壓縮模型本體體積」來省顯存。<br>• 一般來說，直接套用方案 1 就能成功執行。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">這兩次測試讓我們看清了兩個關鍵問題與癥結點：</p>



<h3 class="wp-block-heading">1. 剛剛兩次報錯的真正原因</h3>



<p class="wp-block-paragraph">• 第一次（OOM 爆顯存）：<br>您載入的確實是 -Q4_K_M.gguf（只有 3.9GB），沒有載入錯。<br>但文生圖 Pipeline 包含的文字編碼器 text_encoder/model.safetensors（Qwen3-VL-8B）是未量化的 BF16，檔案大小高達 16.3<br>GB！<br>當使用 enable_model_cpu_offload() 時，Diffusers 在處理文字時會把整個 16.3GB 的 text_encoder 載入 GPU，而您的 RTX<br>4050 只有 6GB 顯存，直接瞬間 OOM。<br>• 第二次（KeyError: None）：<br>這是 Hugging Face Diffusers 目前官方的一個已知 Bug（Issue #14691 <a href="https://github.com/huggingface/diffusers/issues%EF%BC%89%EF%BC%9A">https://github.com/huggingface/diffusers/issues）：</a><br>當使用 enable_sequential_cpu_offload() 時，Diffusers 的層級卸載機制與 GGUF 自訂參數格式不相容（在重新包裝 tensor<br>時丟失了 quant_type 屬性），導致報錯崩潰。</p>



<p class="wp-block-paragraph">此外，檢查了您的系統記憶體，電腦實體 RAM 為 12 GB（可用約 8 GB）。要把 16.3 GB 的 BF16<br>文字編碼器同時放在記憶體中本身就非常極限。<br>──────</p>



<h3 class="wp-block-heading">2. 根本解法：使用量化版 Text Encoder</h3>



<p class="wp-block-paragraph">Qwen-Image-2.1 之所以顯存要求極高，是因為它採用了 8B 級別的多模態大模型（Qwen3-VL-8B）作為文字編碼器。</p>



<p class="wp-block-paragraph">要讓 RTX 4050 (6GB VRAM) + 12GB RAM 能夠流暢生成圖片，不能使用原本 16.3GB 的文字編碼器，而必須將 text encoder<br>也換成量化版（例如 Comfy-Org 或社群提供的 FP8 或 INT8 權重）：</p>



<p class="wp-block-paragraph">• 原版 BF16 text encoder：16.3 GB <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" />（無法塞入 6GB 顯存與 12GB RAM）<br>• FP8 / INT8 text encoder：約 4~8 GB <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" />（可順利載入運算）<br>──────</p>



<h3 class="wp-block-heading">3. 建議下一步</h3>



<p class="wp-block-paragraph">目前最推薦的處理解法：</p>



<ol class="wp-block-list">
<li>從 Hugging Face 下載輕量化版（FP8 或 INT8，約 4~8GB）的 Qwen3-VL 文字編碼器，替換掉 models/Qwen-Image-2.1-<br>Turbo/text_encoder。</li>



<li>同時保留您現有的 qwen-image-2.1-turbo-Q4_K_M.gguf 作為 Transformer。</li>



<li>這樣兩者都能控制在 4~5 GB 內，透過 enable_model_cpu_offload() 就能完美在 RTX 4050 6GB 跑完完整圖片生成流程。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">錯誤訊息</p>



<p class="wp-block-paragraph">curl -X POST &#8220;<a href="http://127.0.0.1:8000/generation/generate">http://127.0.0.1:8000/generation/generate</a>&#8221; ^<br>More? -H &#8220;Content-Type: application/json&#8221; ^<br>More? -d &#8220;{&#8220;prompt&#8221;: &#8220;a sunset over the ocean&#8221;, &#8220;output_path&#8221;: &#8220;output/image.png&#8221;}&#8221;<br><br>{&#8220;detail&#8221;:&#8221;Generation failed: The size of tensor a (4096) must match the size of tensor b (8192) at non-singleton dimension 2&#8243;}</p>



<h2 class="wp-block-heading">Root Cause: Text Encoder Size Mismatch</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Component</th><th>Hidden Size</th><th>Source</th></tr></thead><tbody><tr><td>Your text_encoder (Qwen/Qwen-Image-2.1-Turbo official)</td><td>4096 (Qwen3-VL-4B)</td><td>hidden_size: 4096 in config.json</td></tr><tr><td>Your GGUF transformer (Unsloth Q4_K_M)</td><td>expects 8192</td><td>Unsloth trained it against Qwen3-VL-8</td></tr><tr><td>transformer/config.json context_in_dim</td><td>4096 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>matches the official 4B text encoder</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">The contradiction: the official transformer/config.json says context_in_dim: 4096 (correct for 4B TE), but the<br>Unsloth GGUF you downloaded was quantized from a model that used Qwen3-VL-8B (8192) as its text encoder. So those<br>two are incompatible.<br>──────</p>



<h2 class="wp-block-heading">Should you download the Unsloth GGUF?</h2>



<p class="wp-block-paragraph">No — you don&#8217;t need to switch. The better fix is to use the official Qwen transformer weights (not Unsloth&#8217;s GGUF),<br>or use your existing GGUF correctly paired with the right text encoder.</p>



<h3 class="wp-block-heading">You have 2 paths:</h3>



<p class="wp-block-paragraph">Option A — Ditch the GGUF, load the official model fully (simplest fix)<br>Use QwenImage21Pipeline.from_pretrained with all local weights. The official models/Qwen-Image-2.1-Turbo has<br>context_in_dim=4096 matching your 4B text encoder. No GGUF at all.<br></p>



<p class="wp-block-paragraph">Option B — Keep the Unsloth GGUF, replace the text encoder with Qwen3-VL-8B<br>Download Unsloth&#8217;s recommended Qwen3-VL-8B-Instruct-UD-Q4_K_XL.gguf text encoder. But this requires significant<br>additional download (~5GB+) and more complex loading code.</p>



<p class="wp-block-paragraph"><br>──────<br>Recommendation: Option A — you already have all the official weights. Let me fix model_loader.py to load the full<br>pipeline from local files without the mismatched GGUF:<br>● Ran 1 command, checked 1 task (python -c &#8220;import diffusers; print(diffusers.<strong>version</strong>)&#8221; · tas&#8230;) (ctrl+o to expand</p>



<p class="wp-block-paragraph">Good — diffusers 0.41.0.dev0. Now let me fix the model loader to load the official pipeline without the mismatched<br>Unsloth GGUF:</p>



<p class="wp-block-paragraph">● Edited 1 file (model_loader.py) (ctrl+o to expand)</p>



<p class="wp-block-paragraph">Now restart your server and try again:</p>



<pre class="wp-block-code"><code># Kill any running server first, then:
python -m uvicorn main:app --host 127.0.0.1 --port 8000</code></pre>



<p class="wp-block-paragraph">Then test:</p>



<pre class="wp-block-code"><code>curl -X POST "http://127.0.0.1:8000/generation/generate" `
  -H "Content-Type: application/json" `
  -d "{\"prompt\": \"a sunset over the ocean\", \"output_path\": \"output/image.png\"}"
</code></pre>



<p class="wp-block-paragraph">──────</p>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">The bug: Your Unsloth GGUF (qwen-image-2.1-turbo-Q4_K_M.gguf) was trained against the Qwen3-VL-8B text encoder<br>(hidden_size=8192), but your local text_encoder/ is Qwen3-VL-4B (hidden_size=4096). The transformer&#8217;s txt_in layer<br>tried to accept a 4096-wide tensor but had weights sized for 8192.</p>



<p class="wp-block-paragraph">The fix: Removed the GGUF entirely. The official models/Qwen-Image-2.1-Turbo has matching context_in_dim=4096 in<br>transformer/config.json — all dimensions align. The full bf16 transformer weights are in the .safetensors shards<br>already in models/Qwen-Image-2.1-Turbo/transformer/.</p>



<p class="wp-block-paragraph">Re: Unsloth download — No need to download it. The mismatch would persist unless you also replaced the text encoder<br>with their 8B GGUF version, adding ~5GB more download and significantly more complex loading code. The official<br>weights work perfectly as-is.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/10/qwen-image-2-1-turbo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>將 Pi Coding Agent 無縫對接本地 llama-server 自訂模型</title>
		<link>https://stackoverflow.max-everyday.com/2026/08/pi-coding-agent-llama-server/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/08/pi-coding-agent-llama-server/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Sun, 30 Aug 2026 05:16:06 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8683</guid>

					<description><![CDATA[在體驗 LLM 驅動的命令列開發工具時，許多人喜...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/watermarked_img_11904330739739367754.jpg?v=1788066680" alt="" class="wp-image-8685" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/watermarked_img_11904330739739367754.jpg?v=1788066680 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/watermarked_img_11904330739739367754-600x335.jpg?v=1788066680 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/watermarked_img_11904330739739367754-768x429.jpg?v=1788066680 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">在體驗 LLM 驅動的命令列開發工具時，許多人喜歡在本地使用 <code>llama-server.exe</code> 載入 GGUF 模型（如 Qwen 系列），既能節省 API 費用又能保護隱私。</p>



<p class="wp-block-paragraph">不過，當嘗試將 <strong>Pi Coding Agent</strong> 連接到本地伺服器時，常會遇到 <code>Server is not running in llama.cpp router mode</code> 或 OpenAI API <code>401 Incorrect API key</code> 等驗證錯誤。</p>



<p class="wp-block-paragraph">這篇文章記錄了完整的除錯過程與最終解決方案，幫助你快速完成本地環境設定。</p>



<h2 class="wp-block-heading">問題解析</h2>



<p class="wp-block-paragraph">為什麼預設情況下會連線失敗？</p>



<ul class="wp-block-list">
<li><strong>端點路由不符</strong>：Pi Agent 預設對接 <code>llama.cpp</code> 時，期待對方開啟多模型路由（Router Mode）。如果本地只是單純啟動單一 GGUF 模型，會無法通過驗證。</li>



<li><strong>API Key 驗證攔截</strong>：如果 <code>llama-server</code> 開啟了 <code>--api-key</code>，Pi Agent 若未帶入對應金鑰，或是被預設路徑引導至 OpenAI 官方伺服器，就會觸發 401 Unauthorized 錯誤。</li>
</ul>



<h2 class="wp-block-heading">解決步驟</h2>



<h3 class="wp-block-heading">步驟一：修改 llama-server 啟動批次檔</h3>



<p class="wp-block-paragraph">首先，確保你的 <code>llama-server.exe</code> 啟動時明確設定了 API Key。</p>



<p class="wp-block-paragraph">在你的批次檔（例如 <code>start-server.bat</code>）中加入 <code>--api-key</code> 參數：</p>



<pre class="wp-block-code"><code>set MODEL=models\Qwen3.8-27B-UD-IQ1_S.gguf
set EXE=llama-server.exe

REM Large context for long code.
set CTX=16384
set BATCH=512
set NP=1

"%EXE%" ^
  -m %MODEL% ^
  -c %CTX% ^
  -np %NP% ^
  -cmoe ^
  -b %BATCH% -ub %BATCH% ^
  -ngl 999 ^
  --port 8080 ^
  --host 127.0.0.1 ^
  --api-key 12345678 ^
  -fa on ^
  -rea off ^
  --reasoning-format none ^
  --temp 0.3 ^
  --top-p 0.8 ^
  --top-k 30 ^
  --repeat-penalty 1.08 ^
  --context-shift</code></pre>



<p class="wp-block-paragraph">上面參數微調, 參考看看: 「思維鏈坍塌」超低位元量化模型遇到無休止內部思考、自我糾正<br><a href="https://stackoverflow.max-everyday.com/2026/08/chain-of-thought-collapse/">https://stackoverflow.max-everyday.com/2026/08/chain-of-thought-collapse/</a></p>



<p class="wp-block-paragraph">啟動後，可以使用 CMD 的 <code>curl</code> 測試 OpenAI 相容端點是否正常運作：</p>



<pre class="wp-block-code"><code>curl http://127.0.0.1:8080/v1/models -H "Authorization: Bearer 12345678"
</code></pre>



<p class="wp-block-paragraph">若有正確返回 JSON 格式的模型清單，代表伺服器端設定完成。</p>



<h3 class="wp-block-heading">步驟二：配置 Pi Agent 的自訂 Provider</h3>



<p class="wp-block-paragraph">Pi Agent 允許透過設定檔擴充自訂的 Provider。</p>



<p class="wp-block-paragraph">在 CMD 中建立並開啟設定檔：</p>



<pre class="wp-block-code"><code>if not exist "%USERPROFILE%\.pi\agent" mkdir "%USERPROFILE%\.pi\agent"
notepad "%USERPROFILE%\.pi\agent\models.json"
</code></pre>



<p class="wp-block-paragraph">貼上以下 JSON 設定。重點在於要明確指定 <code>"api": "openai-completions"</code>，否則 Pi 會因為缺少通訊協定設定而報錯：</p>



<pre class="wp-block-code"><code>{
  "providers": {
    "local-llama": {
      "baseUrl": "http://127.0.0.1:8080/v1",
      "apiKey": "12345678",
      "api": "openai-completions",
      "models": &#91;
        {
          "id": "models\\Qwen3.8-27B-UD-IQ1_S.gguf",
          "name": "Qwen3.8-Local",
          "contextWindow": 16384,
          "maxTokens": 4096
        },
        {
          "id": "models\\gemma-4-12B-it-qat-UD-Q4_K_XL.gguf",
          "name": "Gemma-4-12B-Local",
          "contextWindow": 16384,
          "maxTokens": 4096
        }
      ]
    }
  }
}</code></pre>



<p class="wp-block-paragraph">實際測試，模型名稱寫錯，還是可以正常執行，滿神奇的。</p>



<h3 class="wp-block-heading">步驟三：驗證並啟動 Pi Agent</h3>



<p class="wp-block-paragraph">設定完成後，在 CMD 執行模型清單檢視指令：</p>



<pre class="wp-block-code"><code>pi --list-models
</code></pre>



<p class="wp-block-paragraph">確認列表中出現了 <code>local-llama</code> 相關模型。</p>



<p class="wp-block-paragraph">接著建立一個專用的啟動批次檔 <code>run-pi-qwen3.8.bat</code>：</p>



<pre class="wp-block-code"><code>@echo off
pi --model "local-llama/models\Qwen3.8-27B-UD-IQ1_S.gguf"
</code></pre>



<p class="wp-block-paragraph">執行 <code>run-pi.bat</code> 即可順利在 Pi Agent 中與本地模型進行對話！</p>



<h2 class="wp-block-heading">總結</h2>



<p class="wp-block-paragraph">解決此問題的核心在於：</p>



<ol start="1" class="wp-block-list">
<li>本地伺服器需顯式指定 API Key 並提供 OpenAI 相容端點。</li>



<li>Pi Agent 的 <code>models.json</code> 必須完整填寫 <code>api: "openai-completions"</code> 規範。</li>
</ol>



<p class="wp-block-paragraph">透過自訂 Provider 機制，不僅能繞過官方 API 限制，還能靈活切換各種在地端運行的 GGUF 大語言模型！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">選 llama.cpp 比較好的感覺, 可以設定的參數比較多.</p>



<p class="wp-block-paragraph"><strong>ollama</strong> 與 <strong>llama.cpp</strong> 底層皆基於 C/C++ 的推論引擎，但兩者的定位、使用對象與專案目標有著本質上的不同：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>特性</strong></td><td><strong>llama.cpp</strong></td><td><strong>Ollama</strong></td></tr></thead><tbody><tr><td><strong>主要定位</strong></td><td>底層推論核心 / 開發者工具</td><td>上層封裝與管理工具 / 終端使用者應用</td></tr><tr><td><strong>使用門檻</strong></td><td>較高（需熟悉指令列參數、自行下載模型與設定）</td><td>極低（一鍵安裝，具備類似 Docker 的指令與體驗）</td></tr><tr><td><strong>模型格式</strong></td><td><strong>GGUF</strong></td><td><strong>Modelfile</strong>（內部打包並調用 GGUF）</td></tr><tr><td><strong>服務架構</strong></td><td>原生編譯後為單一可執行檔或 C++ 函式庫</td><td>後台背景服務 (Daemon) + CLI 前端</td></tr><tr><td><strong>生態系統</strong></td><td>提供各種綁定 (Python, Rust 等) 與低階 API</td><td>提供相容 OpenAI 的 REST API，整合開源 UI 極佳</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">關鍵差異解析</h3>



<p class="wp-block-paragraph"><strong>1. llama.cpp：極致效能與底層控制</strong></p>



<ul class="wp-block-list">
<li><strong>核心價值</strong>：由 Georgi Gerganov 開發，旨在讓大型語言模型能在無 GPU 或硬體受限的普通消費級設備（如 Mac Apple Silicon、普通 PC）上高效運行。</li>



<li><strong>優勢</strong>：
<ul class="wp-block-list">
<li><strong>細粒度控制</strong>：可直接調整 KV 快取、GPU 層數分流 (<code>-ngl</code>)、Context 長度、Sampler 參數等。</li>



<li><strong>高擴充性</strong>：身為基礎架構，被無數上層工具（如 Python 庫 <code>llama-cpp-python</code>、text-generation-webui 等）整合。</li>
</ul>
</li>



<li><strong>劣勢</strong>：設定繁瑣，下載的模型需要手動管理檔案路徑與參數設定。</li>
</ul>



<p class="wp-block-paragraph"><strong>2. Ollama：極簡體驗與模型生態</strong></p>



<ul class="wp-block-list">
<li><strong>核心價值</strong>：將 llama.cpp 包裝成極簡化的桌面/伺服器工具，核心體驗借鑑了 Docker。</li>



<li><strong>優勢</strong>：
<ul class="wp-block-list">
<li><strong>開箱即用</strong>：只需執行 <code>ollama run llama3</code>，就會自動下載模型並直接啟動對話。</li>



<li><strong>模型庫管理</strong>：擁有官方模型庫 (library)，下載與更新非常方便。</li>



<li><strong>標準化 API</strong>：預設提供開箱即用的 REST API，能 seamlessly 介接 Open WebUI、AnythingLLM 或各類本地插件。</li>
</ul>
</li>



<li><strong>劣勢</strong>：預設封裝隱藏了許多底層參數，若要高度客製化推論細節，需要透過編輯 <code>Modelfile</code> 完成。</li>
</ul>



<h3 class="wp-block-heading">該如何選擇？</h3>



<ul class="wp-block-list">
<li>選 <strong>Ollama</strong>：如果你想要快速在本地端跑起 LLM、連結現成的 Web UI，或是為自己的應用程式快速接上本地 API。</li>



<li>選 <strong>llama.cpp</strong>：如果你是 C/C++ 開發者、需要整合嵌入式系統、或者需要對硬體推論細節進行極致優化與自訂。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/08/pi-coding-agent-llama-server/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Fortify 掃描耗盡了記憶體</title>
		<link>https://stackoverflow.max-everyday.com/2026/08/fortify-gc-overhead-limit-exceeded/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/08/fortify-gc-overhead-limit-exceeded/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 12 Aug 2026 04:59:44 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8663</guid>

					<description><![CDATA[Fortify 掃描發生 java.lang.O...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Fortify 掃描發生 <code>java.lang.OutOfMemoryError: GC overhead limit exceeded</code>，代表 JVM 記憶體 Heap 空間不足，且垃圾回收（GC）耗費超過 98% 的時間卻只能回收不到 2% 的記憶體。堆疊訊息顯示 Fortify 在解析 PHP 的 <code>hereDoc</code> (Heredoc語法) 時耗盡了記憶體。</p>



<p class="wp-block-paragraph">請依序嘗試以下解決方案：</p>



<h2 class="wp-block-heading">1. 增加 Fortify 掃描的記憶體上限</h2>



<p class="wp-block-paragraph">預設的記憶體設定可能不足以處理大型專案或複雜的 PHP 檔案。可以在執行 <code>sourceanalyzer</code> 命令行中加入 <code>-Xmx</code> 參數來提高記憶體配額：</p>



<p class="wp-block-paragraph"><strong>命令列執行 (CLI)：</strong></p>



<pre class="wp-block-code"><code><code>sourceanalyzer -b &lt;build_id> -Xmx16G -scan -f result.fpr </code></code></pre>



<p class="wp-block-paragraph"><em>(可依你的伺服器硬體規格調整，例如 <code>-Xmx12G</code> 或 <code>-Xmx24G</code>)</em></p>



<p class="wp-block-paragraph"><strong>若使用 GUI (Audit Workbench / ScanWizard)：</strong></p>



<p class="wp-block-paragraph">開啟 Fortify Audit Workbench。</p>



<p class="wp-block-paragraph">進入 <strong>Options</strong> > <strong>Global Settings</strong> > <strong>Memory Options</strong>。</p>



<p class="wp-block-paragraph">將 <strong>Maximum Allocation (-Xmx)</strong> 調大（如 <code>16384</code> MB）。</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"><strong>修改全域設定檔 (fortify-sca.properties)：</strong></p>



<p class="wp-block-paragraph">找到 Fortify 安裝目錄或使用者目錄下的設定檔（例如 <code>&lt;Fortify_Home>/Core/config/fortify-sca.properties</code>），修改或加入以下參數：</p>



<pre class="wp-block-code"><code><code>com.fortify.sca.ProjectScanMemoryMB=16384</code></code></pre>



<h2 class="wp-block-heading">2. 排除不必要的檔案或大型自動生成檔</h2>



<p class="wp-block-paragraph">從錯誤堆疊可看出問題出在解析 PHP 的 <code>hereDoc</code> 語法（可能是巨大的模板檔、自動生成的 SQL/Data 檔、或是包含大字串陣列的檔案）。</p>



<p class="wp-block-paragraph">如果增加記憶體後仍失敗，建議排除非必要的第三方套件與靜態檔：</p>



<p class="wp-block-paragraph"><strong>在翻譯（Translation）階段使用 <code>-exclude</code> 排除特定目錄或檔案：</strong></p>



<pre class="wp-block-code"><code>sourceanalyzer -b &lt;build_id> -exclude "/vendor/" -exclude "/node_modules/" -exclude "/*.min.js" src/</code></pre>



<p class="wp-block-paragraph"><strong>過濾大檔案：</strong></p>



<p class="wp-block-paragraph">可以在 <code>fortify-sca.properties</code> 中限制 Fortify 解析單一檔案的大小上限：</p>



<pre class="wp-block-code"><code><code>com.fortify.sca.limit.file.size=10240 </code></code></pre>



<p class="wp-block-paragraph"><em>(單位為 KB，例如設定 <code>10240</code> 會跳過大於 10MB 的單一原始碼檔案)</em></p>



<h2 class="wp-block-heading">3. 採用「雙階段掃描」分離記憶體開銷</h2>



<p class="wp-block-paragraph">若原本是單一指令執行，請改為將翻譯 (Translation)<strong>與</strong>掃描 (Scan)拆為兩個獨立步驟，並只在掃描階段調大記憶體：</p>



<pre class="wp-block-code"><code># 第一步：翻譯階段
sourceanalyzer -b myproject -clean
sourceanalyzer -b myproject ./src

# 第二步：分析掃描階段 (在此處給予最大記憶體)
sourceanalyzer -b myproject -Xmx16G -scan -f result.fpr
</code></pre>



<h3 class="wp-block-heading">建議處理順序</h3>



<ol start="1" class="wp-block-list">
<li><strong>直接將記憶體調大至 16G (<code>-Xmx16G</code>)</strong> 重試一次。</li>



<li>若依然爆記憶體，檢查專案中是否有包含大量文字資料的 <code>.php</code> 檔案（如大陣列檔、DB dump 檔、巨型 Heredoc 模板），並使用 <code>-exclude</code> 將其排除。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><strong>detail error message:</strong></p>



<pre class="wp-block-preformatted">[error]: There is not enough memory available to complete analysis.  For details on making more memory available, please consult the user manual.<br>java.lang.OutOfMemoryError: GC overhead limit exceeded<br>	at com.fortify.frontend.php.parser.PHPLexerBase.nextToken(PHPLexerBase.java:95) ~[?:?]<br>	at org.antlr.v4.runtime.BufferedTokenStream.fetch(BufferedTokenStream.java:169) ~[antlr4-runtime-4.13.1.jar:4.13.1]<br>	at org.antlr.v4.runtime.BufferedTokenStream.sync(BufferedTokenStream.java:152) ~[antlr4-runtime-4.13.1.jar:4.13.1]<br>	at org.antlr.v4.runtime.BufferedTokenStream.nextTokenOnChannel(BufferedTokenStream.java:325) ~[antlr4-runtime-4.13.1.jar:4.13.1]<br>	at org.antlr.v4.runtime.CommonTokenStream.adjustSeekIndex(CommonTokenStream.java:70) ~[antlr4-runtime-4.13.1.jar:4.13.1]<br>	at org.antlr.v4.runtime.BufferedTokenStream.consume(BufferedTokenStream.java:137) ~[antlr4-runtime-4.13.1.jar:4.13.1]<br>	at org.antlr.v4.runtime.Parser.consume(Parser.java:571) ~[antlr4-runtime-4.13.1.jar:4.13.1]<br>	at org.antlr.v4.runtime.Parser.match(Parser.java:205) ~[antlr4-runtime-4.13.1.jar:4.13.1]<br>	at com.fortify.frontend.php.parser.PHPParser.hereDocPart(PHPParser.java:9032) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.hereDocExpr(PHPParser.java:8947) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.primaryExpr(PHPParser.java:9230) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.exponentExpr(PHPParser.java:8627) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.unaryExpr(PHPParser.java:8571) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.instanceofExpr(PHPParser.java:8470) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.notExpr(PHPParser.java:8412) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.productExpr(PHPParser.java:8321) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.sumExpr(PHPParser.java:8222) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.shiftExpr(PHPParser.java:8123) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.comparisonExpr(PHPParser.java:8037) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.equalityExpr(PHPParser.java:7960) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.bitwiseAndExpr(PHPParser.java:7883) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.bitwiseXorExpr(PHPParser.java:7805) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.bitwiseOrExpr(PHPParser.java:7727) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.booleanAndExpr(PHPParser.java:7649) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.booleanOrExpr(PHPParser.java:7571) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.nullCoalescingExpr(PHPParser.java:7501) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.ternaryExpr(PHPParser.java:7426) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.assignExpr(PHPParser.java:9557) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.primaryExpr(PHPParser.java:9276) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.exponentExpr(PHPParser.java:8627) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.unaryExpr(PHPParser.java:8571) ~[?:?]<br>	at com.fortify.frontend.php.parser.PHPParser.instanceofExpr(PHPParser.java:8470) ~[?:?]<br></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">這兩個問題解答如下：</p>



<h3 class="wp-block-heading">第一題：Bash 命令列上的 <code>myproject</code> 存在哪裡？</h3>



<p class="wp-block-paragraph">在 Fortify 命令列中：</p>



<pre class="wp-block-code"><code>sourceanalyzer -b myproject -clean
sourceanalyzer -b myproject ./src
sourceanalyzer -b myproject -scan -f result.fpr
</code></pre>



<p class="wp-block-paragraph"><code>-b myproject</code> 中的 <strong><code>myproject</code></strong> 稱為 <strong>Build ID（建置識別碼）</strong>。</p>



<p class="wp-block-paragraph">它<strong>不是</strong>一個真實的專案資料夾名稱，而是 Fortify 在系統內部用來記錄「翻譯（Translation）階段抽取的原始碼 AST 語法樹與中間檔」的<strong>暫存標籤</strong>。</p>



<h4 class="wp-block-heading">1. 實際檔案存在哪裡？</h4>



<p class="wp-block-paragraph">Fortify 會將 <code>myproject</code> 的中間檔案儲存在<strong>使用者家目錄下的 Fortify SCA 暫存區</strong>：</p>



<ul class="wp-block-list">
<li><strong>Linux / macOS:</strong><br><code>~/.fortify/sca&lt;版本號>/build/myproject/</code><br></li>



<li><strong>Windows (PowerShell / CMD):</strong><br><code>C:\Users\&lt;帳號>\.fortify\sca&lt;版本號>\build\myproject\</code></li>
</ul>



<h4 class="wp-block-heading">2. 注意事項</h4>



<ul class="wp-block-list">
<li>執行 <code>-clean</code> 時（如 <code>sourceanalyzer -b myproject -clean</code>），Fortify 就會<strong>刪除</strong>這個路徑下名為 <code>myproject</code> 的暫存資料夾。</li>



<li>在同一次掃描任務中，Translation（翻譯）和 Scan（分析）必須使用<strong>完全相同的 Build ID</strong>。</li>
</ul>



<h3 class="wp-block-heading">第二題：如何匯出 DeveloperWorkbook PDF 報告？</h3>



<p class="wp-block-paragraph">Fortify 提供了一個專門的命令行報表工具 <strong><code>ReportGenerator</code></strong>，可以直接讀取掃描結果檔（<code>.fpr</code>）並套用 <code>DeveloperWorkbook</code> 範本產生 PDF 報告。<sup></sup></p>



<h4 class="wp-block-heading">1. 匯出命令（Command-line）</h4>



<p class="wp-block-paragraph">請在 Terminal / Command Prompt 執行：</p>



<pre class="wp-block-code"><code>ReportGenerator -format pdf -f DeveloperWorkbook.pdf -source result.fpr -template DeveloperWorkbook.xml
</code></pre>



<h4 class="wp-block-heading">2. 參數說明</h4>



<ul class="wp-block-list">
<li><strong><code>-format pdf</code></strong>：指定輸出格式為 PDF（亦支援 <code>rtf</code>）。</li>



<li><strong><code>-f DeveloperWorkbook.pdf</code></strong>：輸出的 PDF 檔案名稱。</li>



<li><strong><code>-source result.fpr</code></strong>：你前面掃描完成所產生的 <code>.fpr</code> 結果檔。</li>



<li><strong><code>-template DeveloperWorkbook.xml</code></strong>：指定使用研發團隊常用的 <strong>Developer Workbook</strong> 報表範本。</li>
</ul>



<h4 class="wp-block-heading">3. 補充技巧 (進階)</h4>



<p class="wp-block-paragraph"><strong>如果是系統找不到 <code>DeveloperWorkbook.xml</code>：</strong>請加上完整的範本路徑，預設位在 Fortify 安裝目錄下：</p>



<pre class="wp-block-code"><code># Linux / macOS
ReportGenerator -format pdf -f DeveloperWorkbook.pdf -source result.fpr -template /opt/Fortify/Core/config/reports/DeveloperWorkbook.xml

# Windows
ReportGenerator -format pdf -f DeveloperWorkbook.pdf -source result.fpr -template "C:\Program Files\Fortify\Core\config\reports\DeveloperWorkbook.xml"</code></pre>



<p class="wp-block-paragraph"><strong>如果報告顯示的弱點數量被限制（例如預設只印前 5 個）：</strong>可以將 <code>DeveloperWorkbook.xml</code> 複製一份出來，用文字編輯器開啟，搜尋 <code>limit="5"</code> 並將其修改為 <code>limit="-1"</code>（代表不限數量，印出全部），再將修改後的 XML 作為 <code>-template</code> 帶入即可。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">翻譯（Translation）階段需要限制 memory usage?</h2>



<p class="wp-block-paragraph">答案是需要。</p>



<p class="wp-block-paragraph">雖然 Fortify 官方文件常強調在「分析（Scan）」階段給予大記憶體，但<strong>翻譯（Translation）階段如果記憶體不足，同樣會直接發生 <code>OutOfMemoryError</code> 或導致解析中斷（Crash）。</strong></p>



<p class="wp-block-paragraph">特別是你的案例中有大型 PHP 檔案（帶有巨型 Heredoc 語法），Fortify 在 Translation 階段進行語法解析（Lexing/Parsing）並建構 AST（抽象語法樹）時，就會在記憶體中吃掉大量空間。</p>



<h2 class="wp-block-heading">1. 為什麼 Translation 階段也需要限制/調大記憶體？</h2>



<ol start="1" class="wp-block-list">
<li><strong>語法分析（AST 建構）：</strong> 翻譯階段並非只是「複製檔案」，而是將原始碼編譯成 Fortify 的內部中間語言（NST/AST）。遇到複雜的 PHP 樣板、巨型 JSON/SQL 字串或第三方套件時，記憶體開銷會瞬間衝高。</li>



<li><strong>預設記憶體通常太小：</strong> 若未手動指定，<code>-b</code> 翻譯階段會使用 Fortify 的預設配額（通常僅 1GB~2GB），極易觸發 <code>java.lang.OutOfMemoryError: GC overhead limit exceeded</code>。</li>
</ol>



<h2 class="wp-block-heading">2. 如何在 Translation 階段設定記憶體？</h2>



<p class="wp-block-paragraph">語法與 Scan 階段完全相同，直接在 <code>sourceanalyzer</code> 命令列加上 <strong><code>-Xmx</code></strong> 參數：</p>



<pre class="wp-block-code"><code># 1. 先清除舊的 Build ID 暫存
sourceanalyzer -b myproject -clean

# 2. 【Translation 階段】給予足夠的記憶體（例如 16G）
sourceanalyzer -b myproject -Xmx16G ./src

# 3. 【Scan 階段】同樣給予足夠的記憶體
sourceanalyzer -b myproject -Xmx16G -scan -f result.fpr
</code></pre>



<h2 class="wp-block-heading">3. Translation 階段記憶體優化的最佳做法</h2>



<p class="wp-block-paragraph">如果給了 <code>-Xmx16G</code> 在 Translation 階段依然記憶體溢位，請搭配以下優化策略：</p>



<p class="wp-block-paragraph"><strong>主動過濾不需翻譯的目錄（最有效）：</strong>不要將非業務邏輯的套件帶入 Translation 階段：</p>



<pre class="wp-block-code"><code>sourceanalyzer -b myproject -Xmx16G \ -exclude "/vendor/" \ -exclude "/node_modules/" \ -exclude "/*.min.js" \ ./src</code></pre>



<p class="wp-block-paragraph"><strong>設定單一檔案大小上限（全域設定）：</strong>在 <code>fortify-sca.properties</code> 設定檔中加上 limit，防止 Translation 階段去解析幾十 MB 的巨型檔案：</p>



<pre class="wp-block-code"><code>com.fortify.sca.limit.file.size=10240</code></pre>



<p class="wp-block-paragraph"><strong>全域預設記憶體配置：</strong>若不想每次命令列都打 <code>-Xmx</code>，可在 <code>fortify-sca.properties</code> 中調整全域預設值，這會<strong>同時作用於 Translation 與 Scan 階段</strong>：</p>



<pre class="wp-block-code"><code>com.fortify.sca.ProjectTranslationMemoryMB=16384
com.fortify.sca.ProjectScanMemoryMB=16384</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">要匯出符合 <strong>OWASP Top 10</strong> 安全標準的 PDF 報告，選取的範本（Template）檔案說明如下：</p>



<h3 class="wp-block-heading">1. 建議選擇的 Template 檔案</h3>



<p class="wp-block-paragraph">Fortify 官方內建了不同年份版本的 OWASP Top 10 報本 XML 檔，位於 Fortify 報表目錄中：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>範本檔名 (Template File)</strong></td><td><strong>適用 OWASP 版本</strong></td><td><strong>說明 / 適用場景</strong></td></tr></thead><tbody><tr><td><strong><code>OWASP Top 10 2021.xml</code></strong></td><td><strong>OWASP Top 10 (2021)</strong></td><td><strong>【最推薦】</strong> 目前最主流的官方標準版本，建議優先選用。</td></tr><tr><td><strong><code>OWASP Top 10 2017.xml</code></strong></td><td>OWASP Top 10 (2017)</td><td>適用於客戶或公司稽核規範明確指定 2017 年版的專案。</td></tr><tr><td><strong><code>OWASP Top 10.xml</code></strong></td><td>通用/預設連結版</td><td>部分 Fortify 舊版本的預設檔名（會指向該版本的預設 OWASP）。</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">2. 命令行 (ReportGenerator) 執行指令</h3>



<p class="wp-block-paragraph">預設範本檔案存放在 Fortify 安裝目錄下的 <code>Core/config/reports/</code> 資料夾中。</p>



<h4 class="wp-block-heading"><strong>Linux / macOS:</strong></h4>



<pre class="wp-block-code"><code>ReportGenerator -format pdf \
  -f OWASP_Top10_Report.pdf \
  -source result.fpr \
  -template "/opt/Fortify/Core/config/reports/OWASP Top 10 2021.xml"
</code></pre>



<h4 class="wp-block-heading"><strong>Windows:</strong></h4>



<pre class="wp-block-code"><code>ReportGenerator -format pdf ^
  -f OWASP_Top10_Report.pdf ^
  -source result.fpr ^
  -template "C:\Program Files\Fortify\Core\config\reports\OWASP Top 10 2021.xml"
</code></pre>



<p class="wp-block-paragraph"><em>(注意：路徑中包含空白字元，必須使用雙引號 <code>"..."</code> 包裹)</em></p>



<h3 class="wp-block-heading">3. 在 Audit Workbench (GUI) 中選擇</h3>



<p class="wp-block-paragraph">若使用圖形化介面匯出：</p>



<ol start="1" class="wp-block-list">
<li>開啟 <code>.fpr</code> 結果檔。</li>



<li>點擊選單 <strong>Tools</strong> > <strong>Generate Report</strong>。</li>



<li>在 <strong>Report Template</strong> 下拉選單中選擇 <strong><code>OWASP Top 10 2021</code></strong> (或 <code>OWASP Top 10</code>)。</li>



<li>將 <strong>Format</strong> 設為 <strong>PDF</strong>，點擊 <strong>Save Report</strong> 即可。</li>
</ol>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 關鍵技巧：印出「所有」弱點細節 (解除預設 5 筆限制)</h3>



<p class="wp-block-paragraph">Fortify 官方預設的 OWASP 範本為了控制 PDF 頁數，<strong>預設每個漏洞分類只會列出前 5 筆弱點細節 (<code>limit="5"</code>)</strong>。</p>



<p class="wp-block-paragraph">若你的報告需要列出全部弱點：</p>



<ol start="1" class="wp-block-list">
<li>將 <code>OWASP Top 10 2021.xml</code> 複製到你的工作目錄，改名為 <code>OWASP_Custom.xml</code>。</li>



<li>用文字編輯器打開它，將裡面的 <strong><code>limit="5"</code></strong> 全部覆蓋替換為 <strong><code>limit="-1"</code></strong>（<code>-1</code> 代表不限制數量）。</li>



<li>執行指令時帶入 <code>-template OWASP_Custom.xml</code> 即可印出完整清單。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">command line manual:</h2>



<pre class="wp-block-code"><code>sourceanalyzer --help
OpenText SAST (Fortify) CE 25.2.0.0116
Copyright (c) 2003-2025 Open Text

Usage:

  Clean:
     sourceanalyzer.exe -b &lt;build-id> -clean
  Build:
     sourceanalyzer.exe -b &lt;build-id> &lt;sca-build-opts>
  Scan:
     sourceanalyzer.exe -b &lt;build-id> -scan &lt;sca-scan-opts>

Detailed invocation:

  Build:
     sourceanalyzer.exe -b &lt;build-id>
          &#91; &lt;sca-build-options> ]
          &lt;file-specifier>
     sourceanalyzer.exe -b &lt;build-id>
          &#91; &lt;sca-build-options> ]
          &lt;compiler> &lt;compiler-options>
     sourceanalyzer.exe -b &lt;build-id>
          &#91; &lt;sca-build-options> ]
          touchless &lt;build-tool> &#91; &lt;build-tool-options> ]
     sourceanalyzer.exe -b &lt;build-id>
          &#91; &lt;sca-build-options> ]
          devenv &lt;solution-file> /REBUILD
     sourceanalyzer.exe -b &lt;build-id>
          &#91; &lt;sca-build-options> ]
          msbuild /t:rebuild &lt;solution-or-project-file>
     sourceanalyzer.exe -b &lt;build-id>
          &#91; &lt;sca-build-options> ]
          xcodebuild -project &lt;xcodeproj-file>
     sourceanalyzer.exe -b mybuild
          -source-base-dir &lt;webapp-root> &lt;cfm-file-specifier>
  Scan:
     sourceanalyzer.exe -b &lt;build-id> -scan
          &#91; -f &lt;output-file> ]
          &#91; -scan-precision &lt;level> ]
          &#91; -rules &lt;rules.xml> &#91; -no-default-rules ] ]
          &#91; -filter &lt;filter-file> ]
  Clean:
     sourceanalyzer.exe -b &lt;build-id> -clean
  Query:
     sourceanalyzer.exe -b &lt;build-id> { -show-build-warnings | -show-files }
     sourceanalyzer.exe { -version | -show-build-ids }
     sourceanalyzer.exe { -h | -? | -help }



Options


General Options
These options are applicable to all sourceanalyzer.exe invocations.

  @&lt;file>                     Reads command line options from the specified
                              file.  Note that there is no space before the
                              file argument.

  -debug                      Causes the build step to write additional
                              troubleshooting information to the log file.
                              Use if instructed by Fortify Customer Support.
                              Also see "-logfile".

  -logfile &lt;file>             Specifies a destination for the log file.

  -verbose                    Outputs verbose messages to the console.

  -Xmx&lt;num>M                  Specifies the maximum Java heap size.
                              Default is -Xmx1800M.

  -autoheap                   Instructs SCA to set the maximum Java heap size
                              based on available physical memory. Use instead
                              of -Xmx.  Enabled by default.

  -fcontainer                 When run in a Docker container, instructs SCA
                              to detect and use only the memory allocated to
                              the container.

  -version                    Shows the sourceanalyzer.exe version.


Command Options
Note: Only one "command" option is allowed per invocation.


  -h                          Displays this help text.
  -help
  -?

  -clean                      Deletes all intermediate files and build records.
                              When a build ID is also specified with -b, only
                              files and build records related to that build ID
                              are deleted.

  -show-binaries              See the user guide.
  -show-build-tree            See the user guide.

  -show-build-ids             Lists all the Fortify build IDs (analysis models).

  -show-build-warnings        Displays all the actionable warnings that
                              occurred during the translation phase of the build
                              ID specified by "-b".

  -show-files                 Displays all the source files built into the model
                              specified by "-b".

  -show-loc                   Displays lines of code processed for files built
                              into the model specified by "-b".

  -scan                       Causes sourceanalyzer.exe to run an analysis.

  (none)                      If no command option is present, a build step
                              is assumed.


Build Options
"Build" options translate source code into a Fortify analysis model.


  -b &lt;build-id>               Specifies a unique name that identifies the
                              Fortify analysis model to be built. Also see
                              "-scan".

  -build-label &lt;label>        Specifies an optional, arbitrary string value to
                              the Fortify analysis model. Will be included in
                              the output file.

  -build-project &lt;project>    Specifies an optional, arbitrary string value to
                              the Fortify analysis model. Will be included in
                              the output file.

  -build-version &lt;version>    Specifies an optional, arbitrary string value to
                              the Fortify analysis model. Will be included in
                              the output file.

  -encoding &lt;encoding-name>   Specifies the source file encoding.
                              Default value is the platform default.

Compiler Integration Build Options
These options are used when integrating OpenText SAST (Fortify) with a compiler.

  &lt;compiler> &lt;compiler-opts>  Specifies the compiler command line. The file
                              being compiled will be added to the analysis
                              model, and the compiler will be invoked.

  touchless &lt;build-tool>      Specifies a build tool command. The build tool
    &#91; &lt;build-tool-options> ]  will be invoked, and any file being compiled
                              will be added to the analysis model.

  -nc                         When specified, the compiler is not invoked.


File Specification Build Options
These options are used to pass source files directly to OpenText SAST (Fortify).

  &lt;file-specifier>            Expression denoting a file or a group of files,
                              optionally matching a pattern:
                              file1.java - a file
                              file*.java - files matching expression
                              "path/**/*.java" - recursive expression matches.
                              Note: Always escape ** expressions in quotes.

  -exclude &lt;file-specifier>   Excludes any files matched by &lt;file-specifier>
                              from the set of files to translate


Java-specific Build Options
These options should be used in conjunction with file specification options.

  -classpath &lt;classpath>      Uses the specified classpath value for Java
  -cp &lt;classpath>             builds.

  -extdirs                    Accepts a colon or semicolon separated list
                              of directories.  Any jar files found in
                              these directories are included on the
                              classpath. Equivalent to the -extdirs option
                              to javac.

  -sourcepath                 Specifies the location of source files which will
                              not be included in the scan but will be used for
                              name resolution. Equivalent to the -sourcepath
                              option to javac.
                              The sourcepath is like classpath, except it uses
                              source files rather than class files for
                              resolution.

  -source &lt;value>             Indicates which version of the Java language the Java
  -jdk &lt;value>                code adheres to.  Valid values are 1.8, 8, 11, 17, 21.
                              Default is "11".

  -java-build-dir &lt;dir>       Used to specify one or more directories to which
                              Java sources are being compiled. May also be
                              specified at scan time.

Other Language-Specific Build Options

  -source-base-dir &lt;root>     The base directory for a ColdFusion application.

  -python-path                Add an import directory for a Python application.

  -apex                       Set ".cls" file extension to Apex language.
                              (detected based on file content by default). Equivalent to
                              -Dcom.fortify.sca.fileextensions.cls=APEX

  -apex-sobject-path          Add file to load SObject types in Apex application.

Scan Options

  -b &lt;build-id>               Specifies the build ID.  The build ID is used
                              to track which files are compiled and linked
                              as part of a build, to later scan those files.
                              This option may be specified more than once to
                              include multiple build IDs in the same scan.

  -bin &lt;binary>               All source files compiled and linked into the
                              specified binary are scanned.  Multiple binaries
                              may be specified.

  -disable-default-rule-type  See the user guide.

  -f &lt;file>                   The file to which analysis results are written.
                              Default is stdout.

  -filter &lt;file>              Specifies a filter file.  For more information,
                              see the user guide.

  -scan-policy &lt;policy>       Specifies a scan policy for vulnerability prioritization.
                              Valid values are classic, security, devops. Default is security.
                              For more information, see the user guide.

  -java-build-dir &lt;dir>       Used to specify one or more directories to which
                              Java sources have been compiled.  May also be
                              specified at build time.

  -no-default-issue-rules     See the user guide.
  -no-default-sink-rules      See the user guide.
  -no-default-source-rules    See the user guide.

  -no-default-rules           Indicates that OpenText SAST (Fortify) should not use its
                              default rules.  Must be used in conjunction with
                              "-rules"

  -rules &lt;specifier>          Specifies custom rules file or directory.  If a
                              directory is specified, all files ending in ".bin"
                              or ".xml" are included.
                              This option may be used multiple times.

  -quick                      Runs a quick scan. Quick scans complete faster at
                              the cost of reduced accuracy.

  -scan-precision &lt;level>     Configures the depth, precision and speed of the scan
  -p &lt;level>                  with configuration properties specific for the level.
                              The valid values are 1, 2, 3, and 4.

  -quiet                      Disables the command line progress bar.

  -scan                       Causes OpenText SAST (Fortify) to perform analysis against a
                              model.  The model must be specified with "-b".


Build Sessions

  -export-build-session &lt;file.mbs>

                              Store the translated model specified by -b to the
                              specified file.

  -import-build-session &lt;file.mbs>

                              Load the specified file into a build model.  If
                              the build ID of the model already exists in the
                              model registry, the import fails with the message
                              that a build already exists with that ID.


License Directives

  -store-license-pool-credentials "&lt;lim_url>|&lt;lim_pool_name>|&lt;lim_pool_pwd>|&lt;proxy_url>|&lt;proxy_user>|&lt;proxy_pwd>"

                              Stores your LIM license pool credentials to
                              allow OpenText SAST (Fortify) to use the
                              LIM for licensing. Proxy information is optional.

  -clear-license-pool-credentials

                              Removes the LIM license pool credentials from
                              the fortify-sca.properties file.

  -request-detached-lease &lt;duration>

                              Requests a detached lease from the LIM license
                              pool for exclusive use on this system for the
                              specified duration (in minutes).

  -release-detached-lease

                              Releases a detached lease back to the license
                              pool.


EXAMPLES


Build examples:
  Generic (Java, configuration, PHP, JavaScript, ASP/VBScript, VB6):
     sourceanalyzer.exe -b mybuild .
     sourceanalyzer.exe -b mybuild file1.java file2.java
     sourceanalyzer.exe -b mybuild *.bas *.cls *.frm
     sourceanalyzer.exe -b mybuild "site/**/*.php"

  ColdFusion translation:
     sourceanalyzer.exe -b mybuild -source-base-dir /www/app "/www/app/**/*.cfm"

  SQL translation:
     sourceanalyzer.exe -b mybuild -Dcom.fortify.sca.fileextensions.sql=PLSQL *.sql
     sourceanalyzer.exe -b mybuild -Dcom.fortify.sca.fileextensions.sql=TSQL *.sql

  C/C++ builds:
     sourceanalyzer.exe -b mybuild gcc -c test.c
     sourceanalyzer.exe -b mybuild CL.EXE /o HelloWorld HelloWorld.c
     sourceanalyzer.exe -b mybuild make
     sourceanalyzer.exe -b mybuild devenv myproject.msproj /REBUILD

  Objective-C/C++ builds:
     sourceanalyzer.exe -b mybuild clang -ObjC HelloWorld.m
     sourceanalyzer.exe -b mybuild xcodebuild -project myproject.xcodeproj

  .NET builds:
     sourceanalyzer.exe -b mybuild devenv myproj.sln /REBUILD
     sourceanalyzer.exe -b mybuild msbuild /t:rebuild myproj.csproj

  Java specific builds:
     sourceanalyzer.exe -b mybuild -cp lib/dependency.jar "src/**/*.java"
     sourceanalyzer.exe -b mybuild -cp mytaglibs.jar webapp/*.jsp
     sourceanalyzer.exe -b mybuild touchless ant

  J2EE specific builds:
     sourceanalyzer.exe -b mybuild -cp "app/WEB-INF/lib/*.jar" app/*.jsp

Scan step:
     sourceanalyzer.exe -b mybuild -scan -f results.fpr


See OpenText(TM) Static Application Security Testing (Fortify) User Guide for a complete explanation of each option.


You can open a support case for Fortify products online using
our customer support system. This streamlined procedure is designed to
provide easier access and improved customer satisfaction.

Access your account at https://softwaresupport.softwaregrp.com/.</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">這代表即使給了 32G 記憶體，ANTLR 在解析 PHP 語法時仍陷入了死迴圈或極度消耗記憶體的狀態（問題依然卡在 <code>PHPParser.hereDocPart</code> / <code>PHPLexerBase</code>）。</p>



<p class="wp-block-paragraph">通常這有兩個主因：</p>



<ol start="1" class="wp-block-list">
<li><strong>Windows 通配符問題</strong>：<code>-exclude "/vendor/"</code> 在 CMD/PowerShell 根本沒有生效，Fortify 依然把 <code>vendor</code> 進去了。</li>



<li><strong>存在「超大檔」或「極長 Heredoc 檔」</strong>：例如大型 SQL dump、Base64 圖片檔、自動生成的測試資料或超大樣板檔，導致 ANTLR Parser 記憶體暴增。</li>
</ol>



<p class="wp-block-paragraph">請依序透過以下方法徹底解決：</p>



<h3 class="wp-block-heading">1. 修正 Windows 的 <code>-exclude</code> 語法（最常踩坑）</h3>



<p class="wp-block-paragraph">在 Windows 命令列，<code>-exclude</code><strong>必須使用雙引號，且建議使用 <code>**</code> (Ant 語法) 與 Windows 反斜線</strong>，否則過濾會無效：</p>



<p class="wp-block-paragraph">DOS</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_project -clean

sourceanalyzer -b portal_project -Xmx32G -exclude "**/vendor/**" -exclude "**/node_modules/**" -exclude "**/*.min.js" backend/
</code></pre>



<h3 class="wp-block-heading">2. 開啟弱點檔大小限制 (Limit File Size)</h3>



<p class="wp-block-paragraph">這是解決 <code>hereDoc</code> 爆記憶體最有效的武器。直接限制 Fortify 跳過單檔超過指定大小的檔案（例如 1MB 或 2MB）：</p>



<h4 class="wp-block-heading">做法 A：命令列加上 <code>-D</code> 參數</h4>



<p class="wp-block-paragraph">DOS</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_project -Xmx32G -Dcom.fortify.sca.limit.file.size=2048 -exclude "**/vendor/**" backend/
</code></pre>



<p class="wp-block-paragraph"><em>( <code>2048</code> 代表超過 2MB 的原始碼檔案就跳過不安裝/解析，可有效避免吃到包含巨大字串或 Heredoc 的 PHP 檔案)</em></p>



<h4 class="wp-block-heading">做法 B：修改 <code>fortify-sca.properties</code></h4>



<p class="wp-block-paragraph">在 <code>C:\Users\&lt;你的帳號&gt;\.fortify\fortify-sca.properties</code> 或 Fortify 安裝目錄下的 <code>Core\config\fortify-sca.properties</code> 加入：</p>



<p class="wp-block-paragraph">Properties</p>



<pre class="wp-block-code"><code>com.fortify.sca.limit.file.size=2048
</code></pre>



<h3 class="wp-block-heading">3. 找出到底是哪個「元凶檔案」卡住，單獨排除它</h3>



<p class="wp-block-paragraph">如果是業務邏輯資料夾內有一個幾十 MB 的 PHP 檔案（例如包含超大字串陣列或字典）：</p>



<p class="wp-block-paragraph">請加上 <strong><code>-debug</code></strong> 或 <strong><code>-verbose</code></strong> 參數重新執行一次，並觀察 log 印到哪一個 <code>.php</code> 檔案時卡死：</p>



<p class="wp-block-paragraph">DOS</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_project -Xmx32G -verbose backend/ &gt; scan_log.txt
</code></pre>



<p class="wp-block-paragraph">打開 <code>scan_log.txt</code> 翻到最底端，查看崩潰前最後進行解析（Parsing/Lexing）的是哪一個 <code>.php</code> 檔案，接著直接將該檔案加入排除：</p>



<p class="wp-block-paragraph">DOS</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_project -Xmx32G -exclude "**/ProblematicFile.php" backend/
</code></pre>



<h3 class="wp-block-heading">4. （終極方案）調整 ANTLR / PHP 語法分析設定</h3>



<p class="wp-block-paragraph">如果特定 PHP 檔案無法排除，但包含極長的 Heredoc，可以在 <code>fortify-sca.properties</code> 加入以下參數，限制語法樹深度或停用部分 ANTLR 耗效能的檢查：</p>



<pre class="wp-block-code"><code># 限制 Fortify 對單一檔案最大解析時間（單位：秒，預設無限制，可設為 300 秒）
com.fortify.sca.PhaseTimeout=300

# 關閉部分過度深度的 AST 解析
com.fortify.sca.DisablePHP2MethodCallDataflow=true
</code></pre>



<h3 class="wp-block-heading">建議嘗試步驟：</h3>



<ol start="1" class="wp-block-list">
<li>先執行 <code>sourceanalyzer -b portal_project -clean</code> 清除快取。</li>



<li>加上限制檔案大小參數 <code>-Dcom.fortify.sca.limit.file.size=2048</code> 並修正過濾語法 <code>-exclude "**/vendor/**"</code> 重新執行。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">從你提供的完整 log 可以精準定位出問題的根源所在：</p>



<p class="wp-block-paragraph">Log 在處理到 <code>SendForgotPasswordTotpApi.php</code>（或緊隨其後的下一個 PHP 檔）時，Fortify 的 <strong>PHP 語法解析器 (PHPFrontEnd)</strong> 因為遇到極長或結構極度複雜的 Heredoc / String，導致 ANTLR 語法分析器陷入計算迴圈並爆記憶體崩潰，進而拋出 <code>NullPointerException</code>。</p>



<p class="wp-block-paragraph">請採用以下 <strong>4 步驟解決方案</strong> 來繞過這個解析器的死區：</p>



<h3 class="wp-block-heading">步驟 1：強制限制單檔大小 (File Size Limit)</h3>



<p class="wp-block-paragraph">這是最快且最有效的方法。加載此參數能直接讓 Fortify <strong>跳過過大或包含巨大字串 (Heredoc/Blob) 的單一原始碼檔案</strong>，避免 ANTLR 解析器卡死。</p>



<p class="wp-block-paragraph">在 command 加入 <code>-Dcom.fortify.sca.limit.file.size=2048</code>（單位為 KB，即 2MB）：</p>



<p class="wp-block-paragraph">DOS</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_backend -clean

sourceanalyzer -b portal_backend -Xmx32G -Dcom.fortify.sca.limit.file.size=2048 backend/
</code></pre>



<h3 class="wp-block-heading">步驟 2：明確排除 <code>TestTools</code> 或測試/資料庫備份檔案</h3>



<p class="wp-block-paragraph">從 Log 中可以看到你的 <code>backend/</code> 下包含了 <code>TestTools</code> 與 <code>db/migrations</code>。通常測試工具與 Migration 檔中會含有大量假資料、巨型 SQL 字串或 Heredoc 模板。</p>



<p class="wp-block-paragraph">請使用 <strong>Windows 專用 Ant 通配符</strong> 將它們排除：</p>



<p class="wp-block-paragraph">DOS</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_backend -clean

sourceanalyzer -b portal_backend -Xmx32G -Dcom.fortify.sca.limit.file.size=2048 -exclude "**/TestTools/**" -exclude "**/db/**" -exclude "**/vendor/**" backend/
</code></pre>



<h3 class="wp-block-heading">步驟 3：定位「致命檔案」並直接單獨排除</h3>



<p class="wp-block-paragraph">如果前兩步執行後依然在 <code>SendForgotPasswordTotpApi.php</code> 附近崩潰，代表問題出在業務邏輯程式碼本身的某個特定檔案。</p>



<ol start="1" class="wp-block-list">
<li>打開 <code>SendForgotPasswordTotpApi.php</code> 以及與它在同一目錄下的前後檔案。</li>



<li>檢查程式碼中是否有使用 <code>&lt;&lt;&lt;EOD</code> &#8220;<strong>/SendForgotPasswordTotpApi.php&#8221; &#8220;</strong>/TestTools/<strong>&#8221; &#8220;</strong>/db/<strong>&#8221; &#8220;</strong>/vendor/&#8221; # ### (2048 (5分鐘)，防止 <strong>Heredoc &#8212; -Dcom.fortify.sca.limit.file.size=&#8221;2048&#8243; -Xmx32G -b -clean -exclude -f -scan / 2MB 3. 300 4：寫入 ANTLR Fortify HTML KB) Nowdoc PHP Phase Timeout，讓 <code>-exclude</code> <code>&lt;&lt;&lt;HTML</code> <code>C:\Users\max\.fortify\fortify-sca.properties</code> cmd &#8220;`properties <code>fortify-sca.properties</code> backend/ com.fortify.sca.PhaseTimeout=&#8221;300&#8243; com.fortify.sca.limit.file.size=&#8221;2048&#8243; portal_backend portal_backend_result.fpr sourceanalyzer 之類的 在檔案末端寫入以下設定： 完成上述設定後，執行以下完整的標準清洗與翻譯流程： 將它單獨排除： 建議的標準執行指令 或 找到該檔案後，直接使用 檔案也發生 步驟 無限迴圈 秒 若要避免以後其他 解析單一檔案逾時自動跳過，而不是直接崩潰： 設定全域超時（終極防護） 設定單一階段解析超時為 語法</strong>，且裡面放了極長的 語法卡死，直接開啟 請開啟以下檔案（若沒有請自行建立）： 資料或密碼學金鑰/雜湊值。 郵件模板、JSON 限制單一檔案解析上限></li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">這個檔案 (<code>SendForgotPasswordTotpApi.php</code>) <strong>正是導致 Fortify ANTLR 語法分析器崩潰爆記憶體的元凶</strong>。</p>



<h3 class="wp-block-heading">為什麼是這個檔案？</h3>



<ol start="1" class="wp-block-list">
<li><strong>使用了巨大的 <code>Heredoc</code> (<code>$htmlBody = &lt;&lt;&lt;EOD ### #### $htmlBody="str_replace('{$totpCode}'," $htmlTemplate="file_get_contents($templatePath);" $htmlTemplate); $templatePath="__DIR__" $totpCode, '/../../templates/emails/forgot_password_totp.html'; (Inline) (圖案樣式)、HTML **ANTLR **修改後的 --- . ... / // 2. 25.2 &lt;&lt;&lt;EOD A：重構此 Base64 CSS、內嵌 Data EOD; EOD;</code>)</strong>： Fortify GC HTML Heredoc PHP SVG URI <code>$htmlBody</code><code>$textBody</code><code>.blade.php</code><code>.html</code><code>Heredoc</code><code>PHPLexerBase</code><code>PHPParser.hereDocPart</code><code>\</code>、雙引號、SVG php <code>java.lang.OutOfMemoryError: exceeded</code>。 limit overhead 使用的 使用簡單的字串替換填入動態變數 在處理包含複雜字元（如反斜線 字串。 將 將原本巨大的 抽離至獨立的 方案 時，內部的詞法狀態轉換會陷入計算遞迴，進而導致 替換為讀取外部模板檔案： 標籤的巨型 模板抽離，這同時也能讓你的程式碼結構更乾淨。 模板檔案中，改用檔案讀取方式載入： 檔案（最推薦，一勞永逸） 的 的內聯 程式碼： 與 解析器缺陷： 解決方案：重構此檔案（推薦）或針對性排除 語法）的長 這個檔案裡包含了一整段包含 這有兩種解決方式，<strong>最根本的方法是重構程式碼</strong>，將> <strong>優勢</strong>：抽離後 Fortify 解析這個 <code>.php</code> 時就不會觸發 Heredoc 的 ANTLR 解析 Bug，掃描能順利通過，且不會遺漏該 API 的安全弱點分析！</li>
</ol>



<h4 class="wp-block-heading">方案 B：單獨排除此檔案（最快）</h4>



<p class="wp-block-paragraph">如果你目前不想改動原始碼，可以在執行命令時，直接將該檔案單獨排除掉：</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_backend -clean

sourceanalyzer -b portal_backend -Xmx16G -Dcom.fortify.sca.limit.file.size=2048 -exclude "**/SendForgotPasswordTotpApi.php" -exclude "**/TestTools/**" -exclude "**/db/**" -exclude "**/vendor/**" backend/
</code></pre>



<h4 class="wp-block-heading">方案 C：將檔案大小限制調低</h4>



<p class="wp-block-paragraph">因為這個 <code>.php</code> 檔案因為包含巨型 HTML，檔案大小可能較大。你可以嘗試將檔案限制從 <code>2048</code> (2MB) 調小到 <code>100</code> (100KB)：</p>



<pre class="wp-block-code"><code>sourceanalyzer -b portal_backend -clean

sourceanalyzer -b portal_backend -Xmx16G -Dcom.fortify.sca.limit.file.size=100 -exclude "**/vendor/**" backend/
</code></pre>



<p class="wp-block-paragraph"><em>(這樣 Fortify 會自動跳過此類包含巨型模板的過大 PHP 檔)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/08/fortify-gc-overhead-limit-exceeded/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>搞定 Ubuntu 管理員大小事：清單、新增與刪除帳號</title>
		<link>https://stackoverflow.max-everyday.com/2026/08/ubuntu-admin-mangage/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/08/ubuntu-admin-mangage/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 11 Aug 2026 01:59:43 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8656</guid>

					<description><![CDATA[想在 Ubuntu 系統裡當個有實權的大老，或是...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/ubuntu-admin-mangage3_clean-1024x572.jpg" alt="" class="wp-image-8661" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/ubuntu-admin-mangage3_clean-1024x572.jpg?v=1786413620 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/ubuntu-admin-mangage3_clean-600x335.jpg?v=1786413620 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/ubuntu-admin-mangage3_clean-768x429.jpg?v=1786413620 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/ubuntu-admin-mangage3_clean.jpg?v=1786413620 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">想在 Ubuntu 系統裡當個有實權的大老，或是想把不安份的帳號給踢出去嗎？這篇教學帶你輕鬆掌握管理者權限的召喚與封印術！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">一、 尋找幕後黑手：查看誰有最高權限</h3>



<p class="wp-block-paragraph">想知道系統裡面到底有誰偷偷開了外掛、擁有 sudo 權限嗎？直接輸入下面這行指令，就能讓所有管理員現形：</p>



<pre class="wp-block-code"><code>getent group sudo | cut -d: -f4</code></pre>



<p class="wp-block-paragraph">覺得還不夠清楚嗎？這裡還有兩招進階招術：</p>



<p class="wp-block-paragraph">把系統的最高神明 root 一併召喚出來：</p>



<pre class="wp-block-code"><code>echo "root"; getent group sudo | cut -d: -f4 | tr ',' '\n'</code></pre>



<p class="wp-block-paragraph">連他們的登入 Shell 一起抓出來看個仔細：</p>



<pre class="wp-block-code"><code>grep -E "$(getent group sudo | cut -d: -f4 | tr ',' '|')" /etc/passwd</code></pre>



<p class="wp-block-paragraph">小知識補充： Ubuntu 預設是用 sudo 群組來發放管理者權限，而不是單獨叫 admin 群組！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">二、 召喚新夥伴：新增帳號並給予管理權限</h3>



<p class="wp-block-paragraph">想給好朋友開個權限嗎？標準流程是先建帳號，再把他拉進 sudo 俱樂部：</p>



<pre class="wp-block-code"><code>sudo adduser username
sudo usermod -aG sudo username</code></pre>



<p class="wp-block-paragraph">別忘了把 username 換成你朋友的帳號名稱！</p>



<p class="wp-block-paragraph">如果你趕時間，想要一鍵搞定，可以使用這套組合拳：</p>



<pre class="wp-block-code"><code>sudo useradd -m -s /bin/bash -G sudo username
sudo passwd username</code></pre>



<p class="wp-block-paragraph">驗證身分：</p>



<p class="wp-block-paragraph">想確認他有沒有成功拿到權限，可以執行這行命令檢查：</p>



<pre class="wp-block-code"><code>groups username</code></pre>



<p class="wp-block-paragraph">只要看到輸出裡面出現 sudo 這幾個字，就代表他已經順利晉升為管理員啦！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">三、 提防豬隊友：刪除使用者帳號</h3>



<p class="wp-block-paragraph">當某些帳號不再需要，或是對方惹你生氣時，你可以根據想留下的東西選一種方式處理：</p>



<ol start="1" class="wp-block-list">
<li>純粹把人踢掉（保留他的家目錄與檔案）：</li>
</ol>



<pre class="wp-block-code"><code>sudo deluser username</code></pre>



<p class="wp-block-paragraph">或者使用 sudo userdel username 。</p>



<ol start="2" class="wp-block-list">
<li>連同他的家目錄與郵件資料一併蒸發（乾淨俐落）：</li>
</ol>



<pre class="wp-block-code"><code>sudo deluser --remove-home username</code></pre>



<p class="wp-block-paragraph">或者使用 sudo userdel -r username 。</p>



<ol start="3" class="wp-block-list">
<li>地毯式搜捕（刪除他在整個系統留下的所有檔案）：</li>
</ol>



<pre class="wp-block-code"><code>sudo deluser --remove-all-files username</code></pre>



<p class="wp-block-paragraph">動手前請注意：</p>



<p class="wp-block-paragraph">刪除前請先確定 username 已經替換成目標帳號。</p>



<p class="wp-block-paragraph">如果對方還在線上頑強抵抗，請先強制幫他登出：</p>



<pre class="wp-block-code"><code>sudo killall -u username</code></pre>



<p class="wp-block-paragraph">如果只是想拔掉他的權限，讓他變回普通百姓，不需要直接刪帳號，輸入這行命令即可：</p>



<pre class="wp-block-code"><code>sudo deluser username sudo</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">新增的隊友，需要 ip 進白名單</h2>



<p class="wp-block-paragraph">新增的隊友，需要 IP 進白名單。</p>



<p class="wp-block-paragraph">要將 IP 位址 192.168.1.100 加入 iptables 的白名單，指令會根據您希望開放的服務種類而有所不同。</p>



<p class="wp-block-paragraph">從您提供的清單來看，您之前針對特定 IP 開放了 SSH 替代連接埠（port 8822）以及 SNMP（port 161）。以下提供兩種最常見的新增方式：</p>



<p class="wp-block-paragraph">如果您的目標是允許該 IP 存取 SSH（port 8822），請執行：</p>



<pre class="wp-block-code"><code>sudo iptables -A INPUT -i ens160 -p tcp -s 192.168.1.100 --dport 8822 -j ACCEPT</code></pre>



<p class="wp-block-paragraph">如果希望該 IP 可以存取此主機的所有服務與連接埠，請執行：</p>



<pre class="wp-block-code"><code>sudo iptables -A INPUT -i ens160 -s 192.168.1.100 -j ACCEPT</code></pre>



<p class="wp-block-paragraph">設定完成後，可以驗證新規則是否順利加入：</p>



<pre class="wp-block-code"><code>sudo iptables -L -n -v</code></pre>



<p class="wp-block-paragraph">請注意，直接使用 iptables 指令新增的規則在系統重啟後會失效。建議執行下列指令將目前的防火牆規則永久保存：</p>



<pre class="wp-block-code"><code>sudo netfilter-persistent save</code></pre>



<p class="wp-block-paragraph">或是：</p>



<pre class="wp-block-code"><code>sudo iptables-save | sudo tee /etc/iptables/rules.v4</code></pre>



<p class="wp-block-paragraph">驗證與存檔完成後，新採納的 IP 就能順利通過防火牆。請問這次新增的 IP 是要開放特定的通訊埠，還是要開啟全部的存取權限呢？</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/08/ubuntu-admin-mangage/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Windows Server 密碼最長使用期限</title>
		<link>https://stackoverflow.max-everyday.com/2026/08/windows-server-maxpwage/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/08/windows-server-maxpwage/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 10 Aug 2026 05:34:05 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8652</guid>

					<description><![CDATA[資安規範與稽核標準建議將 「密碼最長使用期限」 ...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img decoding="async" width="638" height="240" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/image-1.png?v=1786340016" alt="" class="wp-image-8654" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/image-1.png?v=1786340016 638w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/image-1-600x226.png?v=1786340016 600w" sizes="(max-width: 638px) 100vw, 638px" /></figure>



<p class="wp-block-paragraph">資安規範與稽核標準建議將 <strong>「密碼最長使用期限」</strong> 設定為 <strong><code>90</code> 天</strong>（約 3 個月換一次密碼）。</p>



<p class="wp-block-paragraph">在《行政院資通安全防護基準》及 ISO 27001 實務中，一般機關系統的密碼更換週期標準為 <strong>90 天～180 天</strong>（也就是 3 到 6 個月）。目前你的系統設定是 <code>120</code> 天（約 4 個月），雖然也符合規範，但修改為 <strong><code>90</code> 天</strong> 是各大稽核單位最喜愛的標準黃金值。</p>



<h3 class="wp-block-heading">如何在 PowerShell (管理員) 修改？</h3>



<p class="wp-block-paragraph">請開啟 <strong>PowerShell (以系統管理員身分執行)</strong>，執行以下指令：</p>



<h4 class="wp-block-heading">1. 設定為 90 天（推薦）：</h4>



<p class="wp-block-paragraph">PowerShell</p>



<pre class="wp-block-code"><code>net accounts /MAXPWAGE:90
</code></pre>



<h4 class="wp-block-heading">2. 驗證修改結果：</h4>



<p class="wp-block-paragraph">執行以下指令確認是否生效：</p>



<p class="wp-block-paragraph">PowerShell</p>



<pre class="wp-block-code"><code>net accounts
</code></pre>



<p class="wp-block-paragraph">觀察輸出中的這行：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>密碼最長使用期限 (天): 90</strong></p>
</blockquote>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 補充說明（完整密碼原則組合建議）</h3>



<p class="wp-block-paragraph">修改完成後，你的 Windows Server 2019 密碼原則組合將會非常完美且完全符合資安稽核：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>項目</strong></td><td><strong>你的設定值</strong></td><td><strong>稽核標準與備註</strong></td></tr></thead><tbody><tr><td><strong>密碼長度下限</strong></td><td><strong>13</strong> 字元</td><td>符合 $\ge 12$ 或 $13$ 字元標準</td></tr><tr><td><strong>密碼最長使用期限</strong></td><td><strong>90</strong> 天</td><td>符合 90~180 天標準，每 3 個月更換</td></tr><tr><td><strong>歷程記錄長度</strong></td><td><strong>3</strong> 次</td><td>符合「不可與前 3 次相同」</td></tr><tr><td><strong>帳戶鎖定閾值</strong></td><td><strong>5</strong> 次</td><td>失敗 5 次即鎖定</td></tr><tr><td><strong>鎖定持續期間</strong></td><td><strong>15</strong> 分鐘</td><td>鎖定 15 分鐘（防暴力破解）</td></tr></tbody></table></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/08/windows-server-maxpwage/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Windows Server 密碼變更時，至少不可以與前3次使用過之密碼相同</title>
		<link>https://stackoverflow.max-everyday.com/2026/08/windows-server-uniquepw/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/08/windows-server-uniquepw/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 10 Aug 2026 05:28:55 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8649</guid>

					<description><![CDATA[這是因為繁體中文版 Windows 的 net ...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="641" height="238" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/image.png?v=1786339647" alt="" class="wp-image-8650" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/image.png?v=1786339647 641w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/image-600x223.png?v=1786339647 600w" sizes="auto, (max-width: 641px) 100vw, 641px" /></figure>



<p class="wp-block-paragraph">這是因為繁體中文版 Windows 的 <code>net accounts</code> 指令參數名稱是 <strong>/UNIQUEPW:3</strong>（英文版才是 <code>/UNIQUEPW</code> 或 <code>/UNIQUENESS</code>）。</p>



<p class="wp-block-paragraph">請在 <strong>PowerShell (系統管理員)</strong> 中直接執行以下這行指令：</p>



<pre class="wp-block-code"><code>net accounts /UNIQUEPW:3
</code></pre>



<h3 class="wp-block-heading">執行後驗證</h3>



<p class="wp-block-paragraph">執行完畢後，再次輸入：</p>



<pre class="wp-block-code"><code>net accounts
</code></pre>



<p class="wp-block-paragraph">確認結果顯示為：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>密碼維護的歷程記錄長度： 3</strong></p>
</blockquote>



<p class="wp-block-paragraph">這樣就順利修改完成，可以直接在表單的「密碼變更時，至少不可以與前3次使用過之密碼相同」勾選 <strong>「是」</strong> 了！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">如何在 Windows Server 設定與確認？</h3>



<p class="wp-block-paragraph">如果你需要確保系統確實設定為「不能與前 3 次相同」，請依照以下步驟調整：</p>



<p class="wp-block-paragraph"><strong>1.開啟本機資安原則：</strong></p>



<p class="wp-block-paragraph">按下 <code>Win + R</code> 開啟「執行」視窗，輸入 <code>secpol.msc</code> 並按下 <strong>Enter</strong>。</p>



<p class="wp-block-paragraph"><strong>2.找到密碼歷史紀錄設定：</strong></p>



<p class="wp-block-paragraph">在左側選單點擊：<strong>帳戶原則</strong>（<code>Account Policies</code>） ➔ <strong>密碼原則</strong>（<code>Password Policy</code>）。</p>



<p class="wp-block-paragraph"><strong>3.修改強制保留密碼歷史記錄：</strong></p>



<p class="wp-block-paragraph">雙擊右側的 <strong>「強制保留密碼歷史記錄」</strong>（<code>Enforce password history</code>），將數值改為 <strong><code>3</code></strong>（或更大，例如標準常設為 <code>3</code> 或 <code>5</code>），點擊 <strong>確定</strong>。</p>



<p class="wp-block-paragraph"><strong>4.強制更新資安原則：</strong></p>



<p class="wp-block-paragraph">開啟 PowerShell（管理員身分），輸入 <code>gpupdate /force</code> 讓設定立即生效。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/08/windows-server-uniquepw/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>更改 Windows Server 密碼長度至少 N 個字元</title>
		<link>https://stackoverflow.max-everyday.com/2026/08/windows-server-min-pwd-length/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/08/windows-server-min-pwd-length/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 10 Aug 2026 05:20:12 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8645</guid>

					<description><![CDATA[說明, 使用 net accounts 看到, ...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="881" height="609" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/mstsc_2026-08-10-13-16-hc.jpg?v=1786339126" alt="" class="wp-image-8647" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/mstsc_2026-08-10-13-16-hc.jpg?v=1786339126 881w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/mstsc_2026-08-10-13-16-hc-600x415.jpg?v=1786339126 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/08/mstsc_2026-08-10-13-16-hc-768x531.jpg?v=1786339126 768w" sizes="auto, (max-width: 881px) 100vw, 881px" /></figure>



<p class="wp-block-paragraph">說明, 使用 net accounts 看到, 修改前是最少10碼, 修改之後最少 13碼。</p>



<p class="wp-block-paragraph">要在 Windows Server 2019 設定<strong>密碼長度至少 13 個字元</strong>，你需要修改群組原則中的「密碼最短長度」。</p>



<p class="wp-block-paragraph">依照以下步驟調整即可：</p>



<h3 class="wp-block-heading">方法一：使用圖形介面 (GUI) 修改</h3>



<p class="wp-block-paragraph"><strong>1.開啟本機資安原則：</strong></p>



<p class="wp-block-paragraph">按下快捷鍵 <code>Win + R</code> 開起「執行」視窗，輸入 <code>secpol.msc</code> 並按下 <strong>Enter</strong>。</p>



<p class="wp-block-paragraph"><strong>2.找到密碼原則：</strong></p>



<p class="wp-block-paragraph">在左側選單依序展開：</p>



<p class="wp-block-paragraph"><strong>機關資安設定</strong>（或 <code>安全性設定</code>） ➔ <strong>帳戶原則</strong>（<code>Account Policies</code>） ➔ <strong>密碼原則</strong>（<code>Password Policy</code>）。</p>



<p class="wp-block-paragraph"><strong>3.修改密碼最短長度：</strong></p>



<p class="wp-block-paragraph">在右側視窗點擊兩下開啟 <strong>「密碼最短長度」</strong>（<code>Minimum password length</code>）。</p>



<p class="wp-block-paragraph">將原本的 <code>10</code> 改為 <strong><code>13</code></strong>（或更多字元），然後點擊 <strong>確定</strong>。</p>



<p class="wp-block-paragraph"><strong>4.強制更新原則：</strong></p>



<p class="wp-block-paragraph">開啟 PowerShell 或 CMD（管理員權限），輸入 <code>gpupdate /force</code> 讓新原則立即生效。</p>



<h3 class="wp-block-heading">方法二：使用 PowerShell 快速修改</h3>



<p class="wp-block-paragraph">如果你習慣用 command line 操作，可以開啟 <strong>PowerShell (以系統管理員身分執行)</strong>，複製並執行以下指令：</p>



<pre class="wp-block-code"><code># 1. 將目前的安全性原則匯出至暫存檔
secedit /export /cfg C:\secpol.cfg

# 2. 將密碼最短長度從原本的數值改為 13
(Get-Content C:\secpol.cfg) -replace 'MinimumPasswordLength = \d+', 'MinimumPasswordLength = 13' | Set-Content C:\secpol.cfg

# 3. 重新套用設定檔
secedit /configure /db $env:windir\security\local.sdb /cfg C:\secpol.cfg /areas SECURITYPOLICY

# 4. 清除暫存檔
Remove-Item C:\secpol.cfg
</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 驗證是否修改成功</h3>



<p class="wp-block-paragraph">執行以下 PowerShell 指令確認設定是否成功生效：</p>



<pre class="wp-block-code"><code>net accounts
</code></pre>



<p class="wp-block-paragraph">觀察輸出結果中的 <strong>「密碼最短長度」 (Minimum password length)</strong> 是否已經變更為 <strong><code>13</code></strong>。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>補充說明（AD 網域環境）：</strong></p>



<p class="wp-block-paragraph">如果你的 Windows Server 有加入 <strong>Active Directory (AD) 網域</strong>，且密碼原則是由網域控制站 (DC) 統一管理的，請前往網域控制站開啟 <code>gpmc.msc</code> (群組原則管理)，並在 <strong>Default Domain Policy</strong> 中修改相同的項目。</p>
</blockquote>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/08/windows-server-min-pwd-length/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PHP 8.3 報錯排查：無法載入動態函式庫 php_xmlrpc.dll / php_gd2.dll 解決方案</title>
		<link>https://stackoverflow.max-everyday.com/2026/08/php_xmlrpc-php_gd2/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/08/php_xmlrpc-php_gd2/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 03 Aug 2026 07:41:00 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[php]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8642</guid>

					<description><![CDATA[將 PHP 升級至 8.0 以上版本（例如 PH...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">將 PHP 升級至 8.0 以上版本（例如 PHP 8.3）後，執行或啟動伺服器時可能會遇到類似以下的警告訊息：</p>



<pre class="wp-block-code"><code>PHP Warning: PHP Startup: Unable to load dynamic library 'php_xmlrpc.dll' (tried: C:\Program Files\PHP\v8.3\ext\php_xmlrpc.dll (找不到指定的模組。), C:\Program Files\PHP\v8.3\ext\php_php_xmlrpc.dll.dll (找不到指定的模組。)) in Unknown on line 0</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">原因分析</h3>



<p class="wp-block-paragraph">此錯誤通常源自以下兩個主要原因：</p>



<ol start="1" class="wp-block-list">
<li><strong>擴充套件（Extension）命名規則變更</strong>在舊版 PHP（PHP 7.x 及更早版本）中，設定檔常寫為 <code>extension=php_gd2.dll</code> 或帶有 <code>php_</code> 前綴。從 <strong>PHP 8.0</strong> 開始，官方簡化了指定方式，只需填寫擴充套件簡稱（例如 <code>extension=gd</code>）。若仍使用舊式檔名，PHP 在自動補充路徑時會產生如 <code>php_php_...dll.dll</code> 的重複路徑而找不到檔案。</li>



<li><strong>部分舊版擴充套件已被移除（以 XMLRPC 為例）</strong><code>xmlrpc</code> 擴充套件已於 PHP 8.0 起從核心移除並移至 PECL。因此 PHP 8.3 預設的 <code>ext/</code> 目錄下已不再提供 <code>php_xmlrpc.dll</code>。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">解決步驟</h3>



<h4 class="wp-block-heading">步驟 1：開啟 <code>php.ini</code> 設定檔</h4>



<p class="wp-block-paragraph">找到目前 PHP 8.3 環境所使用的 <code>php.ini</code> 檔案（例如：<code>C:\Program Files\PHP\v8.3\php.ini</code>）。</p>



<h4 class="wp-block-heading">步驟 2：修正或停用問題擴充套件</h4>



<p class="wp-block-paragraph">以系統管理員權限開啟 <code>php.ini</code>：</p>



<p class="wp-block-paragraph"><strong>若為舊版命名格式問題（如 GD 繪圖庫）：</strong>搜尋 <code>gd</code>，將舊式的設定修正為新版標準格式：</p>



<p class="wp-block-paragraph"><code>舊寫法：</code></p>



<pre class="wp-block-code"><code><code>extension=php_gd2.dll</code></code></pre>



<p class="wp-block-paragraph"><code>請改為： </code></p>



<pre class="wp-block-code"><code><code>extension=gd</code></code></pre>



<p class="wp-block-paragraph"><strong>若為已移出核心的擴充套件（如 XMLRPC）：</strong></p>



<p class="wp-block-paragraph">搜尋 <code>xmlrpc</code>，若無特殊需要，直接在行首加上分號 <code>;</code> 將其註解停用：</p>



<pre class="wp-block-code"><code>;extension=php_xmlrpc.dll 
;extension=xmlrpc</code></pre>



<h4 class="wp-block-heading">步驟 3：確認擴充套件目錄 (<code>extension_dir</code>)</h4>



<p class="wp-block-paragraph">確認 <code>php.ini</code> 中的 <code>extension_dir</code> 指令已正確啟用並指向正確路徑：</p>



<pre class="wp-block-code"><code>extension_dir = "ext"
</code></pre>



<p class="wp-block-paragraph"><em>(或使用絕對路徑：<code>extension_dir = "C:\Program Files\PHP\v8.3\ext"</code>)</em></p>



<h4 class="wp-block-heading">步骤 4：重啟 Web 伺服器 / PHP 服務</h4>



<p class="wp-block-paragraph">儲存 <code>php.ini</code> 後，重啟 Apache、Nginx、IIS 或重開 PHP CLI 環境，以使新的設定生效。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">The PHP startup warnings require &nbsp;php.ini&nbsp; cleanup:</p>



<pre class="wp-block-code"><code>date.timezone = Asia/Taipei<br>extension=gd</code></pre>



<p class="wp-block-paragraph">Remove duplicate entries for &nbsp;curl&nbsp;, &nbsp;mysqli&nbsp;, &nbsp;mbstring&nbsp;, &nbsp;openssl&nbsp;, &nbsp;ldap&nbsp;, and &nbsp;pdo_mysql&nbsp;.</p>



<p class="wp-block-paragraph">Also remove or install matching extensions:</p>



<ul class="wp-block-list">
<li> xmlrpc : unavailable in standard PHP 8.3; comment it out unless separately installed.</li>



<li> pdo_sqlsrv_82_nts_x64 : replace with the PHP 8.3 NTS x64 driver, or remove it if unused.</li>
</ul>



<p class="wp-block-paragraph">Do not suppress these with &nbsp;error_reporting(0)&nbsp;; correcting &nbsp;php.ini&nbsp; will eliminate the startup log noise</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/08/php_xmlrpc-php_gd2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>解決 Windows Server 下載失敗！連線意外關閉與安全封鎖的兩大妙招</title>
		<link>https://stackoverflow.max-everyday.com/2026/07/%e8%a7%a3%e6%b1%ba-windows-server-%e4%b8%8b%e8%bc%89%e5%a4%b1%e6%95%97%ef%bc%81%e9%80%a3%e7%b7%9a%e6%84%8f%e5%a4%96%e9%97%9c%e9%96%89%e8%88%87%e5%ae%89%e5%85%a8%e5%b0%81%e9%8e%96%e7%9a%84%e5%85%a9/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/07/%e8%a7%a3%e6%b1%ba-windows-server-%e4%b8%8b%e8%bc%89%e5%a4%b1%e6%95%97%ef%bc%81%e9%80%a3%e7%b7%9a%e6%84%8f%e5%a4%96%e9%97%9c%e9%96%89%e8%88%87%e5%ae%89%e5%85%a8%e5%b0%81%e9%8e%96%e7%9a%84%e5%85%a9/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 08 Jul 2026 06:45:59 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8632</guid>

					<description><![CDATA[寫程式或設定伺服器的時候，最怕遇到那種沒頭沒尾的...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">寫程式或設定伺服器的時候，最怕遇到那種沒頭沒尾的錯誤訊息。如果你在 Windows Server 上嘗試下載檔案，卻跳出要求已經中止，連接意外關閉這串字，先別急著抓狂！這通常是因為你的 Windows Server 預設停用了比較新的加密協定（ TLS 1.2 或 TLS 1.3 ），而 GitHub 偏偏強制要求使用 TLS 1.2 以上，兩邊對不上話，連線自然就被狠狠拒絕了。</p>



<p class="wp-block-paragraph">別擔心，我們在 PowerShell 裡手動強制開啟 TLS 1.2 ，就可以輕鬆搞定。</p>



<h3 class="wp-block-heading">絕招一：強制開啟 TLS 1.2 下載法</h3>



<p class="wp-block-paragraph">請複製以下這段程式碼，整段直接貼進你的 PowerShell 視窗裡面執行：</p>



<p class="wp-block-paragraph">PowerShell</p>



<pre class="wp-block-code"><code># 1. 強制讓 PowerShell 使用 TLS 1.2 加密協定
&#91;Net.ServicePointManager]::SecurityProtocol = &#91;Net.SecurityProtocolType]::Tls12

# 2. 重新嘗試下載安裝檔
Invoke-WebRequest -Uri "https://github.com/microsoft/go-sqlcmd/releases/latest/download/sqlcmd-windows-amd64.msi" -OutFile "$env:USERPROFILE\Downloads\sqlcmd.msi"

# 3. 執行安裝
Start-Process msiexec.exe -ArgumentList "/i `"$env:USERPROFILE\Downloads\sqlcmd.msi`"" -Wait
</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="695" height="652" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/image.png" alt="" class="wp-image-8633" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/image.png?v=1783492999 695w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/image-600x563.png?v=1783492999 600w" sizes="auto, (max-width: 695px) 100vw, 695px" /></figure>



<p class="wp-block-paragraph">執行之後，畫面上應該會彈出上面的截圖。</p>



<p class="wp-block-paragraph"></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">絕招二：繞過 IE 增強式安全性封鎖</h3>



<p class="wp-block-paragraph">如果用了第一招還是失敗，甚至跳出討人厭的 Internet Explorer 紅色警告，那就代表你抓到幕後黑手了！這是 Windows Server 惡名昭彰的「 IE 增強式安全性設定（ IE ESC ）」在作怪。</p>



<p class="wp-block-paragraph">因為 PowerShell 在下載檔案的時候，背後會偷偷調用 Internet Explorer 的核心元件，而 Server 的安全機制預設會把外面所有的網站（ 包括 GitHub ）通通鎖死，當成大魔王在防防禦。</p>



<p class="wp-block-paragraph">既然系統的安全性指令一直擋路，我們就改用「 全指令、不調用 IE 網頁核心 」的方法來下載，直接繞過這個煩人的限制。</p>



<p class="wp-block-paragraph">請在 PowerShell 中執行這段全新的指令：<sup></sup></p>



<p class="wp-block-paragraph">PowerShell</p>



<pre class="wp-block-code"><code># 1. 強制開啟 TLS 1.2
&#91;Net.ServicePointManager]::SecurityProtocol = &#91;Net.SecurityProtocolType]::Tls12

# 2. 使用微軟官方下載中心（保證存在的 x64 穩定版網址）
$webClient = New-Object System.Net.WebClient
$url = "https://go.microsoft.com/fwlink/?linkid=2142258"
$output = "$env:USERPROFILE\Downloads\MsSqlCmdLnUtils.msi"
$webClient.DownloadFile($url, $output)

# 3. 執行安裝
Start-Process msiexec.exe -ArgumentList "/i `"$output`"" -Wait</code></pre>



<p class="wp-block-paragraph">這次執行完之後，畫面就不會再跳出那些紅色的警告。它會非常低調、靜悄悄地下載完成，接著直接彈出 sqlcmd 的安裝視窗。你同樣只要動動手指，跟著點下一步完成安裝即可。</p>



<p class="wp-block-paragraph">最後再次提醒，安裝完畢後，一樣要關掉並重新打開一個新的 PowerShell 或 cmd 視窗，系統的環境變數才會生效。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/07/%e8%a7%a3%e6%b1%ba-windows-server-%e4%b8%8b%e8%bc%89%e5%a4%b1%e6%95%97%ef%bc%81%e9%80%a3%e7%b7%9a%e6%84%8f%e5%a4%96%e9%97%9c%e9%96%89%e8%88%87%e5%ae%89%e5%85%a8%e5%b0%81%e9%8e%96%e7%9a%84%e5%85%a9/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>網路安全防護： 5 分鐘搞懂什麼是 PKCE</title>
		<link>https://stackoverflow.max-everyday.com/2026/07/pkce/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/07/pkce/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Fri, 03 Jul 2026 08:48:52 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8625</guid>

					<description><![CDATA[大家在網站或手機 App 登入帳號時，有沒有想過...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/PKCE_clean-1024x572.jpg?v=1783068523" alt="" class="wp-image-8626" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/PKCE_clean-1024x572.jpg?v=1783068523 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/PKCE_clean-600x335.jpg?v=1783068523 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/PKCE_clean-768x429.jpg?v=1783068523 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/07/PKCE_clean.jpg?v=1783068523 1376w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">大家在網站或手機 App 登入帳號時，有沒有想過，你的密碼和資料是怎麼在網路世界裡安全傳遞的？今天我們要來聊聊一個保護你我隱私的幕後功臣，它的名字叫 PKCE。</p>



<p class="wp-block-paragraph">PKCE 全名是 Proof Key for Code Exchange，大家通常把它讀作 Pixie（就是小精靈的那個英文發音）。它是 OAuth 2.0 這個登入標準機制的安全擴充功能。簡單來說，它就像是幫你的行動 App 或單頁式網頁應用程式（SPA）請了一位動態保鏢，專門防止壞人中途攔截你的登入憑證。</p>



<h3 class="wp-block-heading">PKCE 是如何運作的？</h3>



<p class="wp-block-paragraph">傳統的登入方式需要一組固定的秘密鑰匙（Client Secret），但如果把這把鑰匙藏在手機 App 或前端網頁裡，很容易就被厲害的駭客挖出來。 PKCE 的聰明之處，就在於它每次登入都用「臨時隨機抽樣」的方式，流程主要分為四個步驟：</p>



<ol start="1" class="wp-block-list">
<li>產生驗證碼： 你的手機 App 或網頁會在本地端隨機亂數產生一組高密度的字串，這組密碼學字串叫做 code_verifier。</li>



<li>生成挑戰碼： 接下來，系統會把這組字串拿去進行雜湊運算（通常是 SHA-256 演算法），再經過編碼轉換成另一組字串，叫做 code_challenge。</li>



<li>發送請求： 你的 App 會把這組挑戰碼和運算方法，一起送到官方的授權伺服器，大喊一聲「我要登入！」</li>



<li>驗證與核發： 當 App 拿到授權碼，準備跟伺服器換取真正能通行的 Token 時，必須交出第一步產生的原始驗證碼。伺服器會在後端用同樣的方法算一次，確認跟當初的挑戰碼一模一樣，才會點頭放行。</li>
</ol>



<h3 class="wp-block-heading">為什麼我們需要 PKCE ？</h3>



<p class="wp-block-paragraph">如果沒有 PKCE 的保護，當授權碼在網路跳轉回傳的途中，很容易被惡意軟體或中間人側錄攔截。駭客只要偷到這個授權碼，就能假冒你的身份去跟伺服器換取存取權限。</p>



<p class="wp-block-paragraph">網頁後端伺服器因為躲在暗處，可以安全地藏好秘密鑰匙，但手機 App 是公開在外的客戶端，根本沒辦法安全地藏東西。 PKCE 透過每一次登入時「動態出題、動態對答案」的機制，確保發起請求和最後拿 Token 的是同一個合法的 App。這樣一來，壞人就算在半路偷到授權碼，沒有原始的驗證碼也只能乾瞪眼，完全無法盜用！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">工程師的私房補丁日誌： AppScan 安全漏洞大作戰</h2>



<p class="wp-block-paragraph">看完了上面的小精靈防護機制，順便來瞧瞧我們這次為了應付 AppScan 安全檢查，在後端默默做了哪些升級。這絕對不是在敷衍檢查，這是為了愛與和平。</p>



<h3 class="wp-block-heading">核心變更項目</h3>



<p class="wp-block-paragraph">關於 PKCE 漏洞修補，我們擴充了 OAuthProvider 介面，新增了 Supports PKCE() bool 與 Exchange(&#8230;oauth2.AuthCodeOption) 功能。現在 Entra ID 會乖乖回傳 true ，而交大 NYCU 則會回傳 false 。登入處理器會自動產生一組驗證碼並發送 S256 挑戰碼，回呼處理器則會負責把驗證碼傳給 Exchange 進行核對。</p>



<p class="wp-block-paragraph">關於 Cookie 檔安全，我們在前一次的提交中，就已經把驗證 Cookie 檔通通綁上 SameSite=Strict 機制了。</p>



<p class="wp-block-paragraph">關於 API 安全標頭，我們加入了 AddAPISecurityHeaders 中介軟體，強制執行以下防禦：</p>



<ul class="wp-block-list">
<li>Cross-Origin-Resource-Policy 設為 same-origin</li>



<li>Strict-Transport-Security 設為 max-age=31536000; includeSubDomains</li>



<li>Cache-Control 設為 no-store 搭配 Pragma 設為 no-cache</li>



<li>Referrer-Policy 設為 strict-origin-when-cross-origin</li>
</ul>



<h3 class="wp-block-heading">AppScan 漏洞修補成績單</h3>



<p class="wp-block-paragraph">以下是這次的戰果，我們把所有紅字都變成綠色的勾勾了。</p>



<ul class="wp-block-list">
<li>OAuth Implicit Grant 缺 PKCE 漏洞： 中級風險，修復狀態為已完成。</li>



<li>Cookie 的 SameSite 設定不當： 中級風險，修復狀態為已完成。</li>



<li>API 缺少 CORP 標頭： 中級風險，修復狀態為已完成。</li>



<li>可快取的 SSL 網頁： 低級風險，修復狀態為已完成，透過 Cache-Control 防禦。</li>



<li>缺少 Referrer-Policy 標頭： 低級風險，修復狀態為已完成。</li>



<li>缺少 HSTS 標頭： 低級風險，修復狀態為已完成。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Addresses AppScan security findings for the backend.</p>



<h3 class="wp-block-heading">Changes</h3>



<ul class="wp-block-list">
<li><strong>PKCE (OAuth Implicit Grant flaw)</strong>: OAuthProvider interface extended with SupportsPKCE() bool and Exchange(&#8230;oauth2.AuthCodeOption). Entra ID returns rue; NYCU returns alse. Login handler generates a verifier and sends S256 challenge; Callback handler passes verifier to Exchange.</li>



<li><strong>SameSite=Strict</strong>: Auth cookies already fixed in previous commit (SameSiteStrictMode).</li>



<li><strong>API Security Headers</strong> (AddAPISecurityHeaders middleware):
<ul class="wp-block-list">
<li>Cross-Origin-Resource-Policy: same-origin</li>



<li>Strict-Transport-Security: max-age=31536000; includeSubDomains</li>



<li>Cache-Control: no-store + Pragma: no-cache</li>



<li>Referrer-Policy: strict-origin-when-cross-origin</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">AppScan Findings Addressed</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Finding</th><th>Severity</th><th>Status</th></tr></thead><tbody><tr><td>OAuth Implicit Grant (missing PKCE)</td><td>M</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed</td></tr><tr><td>Cookie SameSite improper</td><td>M</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed</td></tr><tr><td>CORP header missing on API</td><td>M</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed</td></tr><tr><td>Cacheable SSL pages</td><td>L</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed (Cache-Control: no-store)</td></tr><tr><td>Missing Referrer-Policy</td><td>L</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed</td></tr><tr><td>Missing HSTS</td><td>L</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed</td></tr></tbody></table></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/07/pkce/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
