<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hardware on kenji.blog</title><link>http://kenji.blog/zh-tw/categories/hardware/</link><description>Recent content in Hardware on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-tw</language><copyright>kenjinote</copyright><lastBuildDate>Sat, 12 Sep 2026 12:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-tw/categories/hardware/index.xml" rel="self" type="application/rss+xml"/><item><title>減輕程式設計師眼睛疲勞的小工具與螢幕設定</title><link>http://kenji.blog/zh-tw/p/programmer-eye-strain-relief/</link><pubDate>Sat, 12 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/zh-tw/p/programmer-eye-strain-relief/</guid><description>&lt;img src="http://kenji.blog/p/programmer-eye-strain-relief/img/eyecatch.jpg" alt="Featured image of post 減輕程式設計師眼睛疲勞的小工具與螢幕設定" />&lt;p>對程式設計師和軟體工程師來說，「眼睛」是最重要且最常被過度使用的生財工具。在每天需要面對編輯器、終端機和瀏覽器畫面 8 到 10 個小時，甚至更長時間的生活中，幾乎所有的工程師都會面臨「電腦視覺症候群（Computer Vision Syndrome: CVS）」，也就是眼睛疲勞的問題。&lt;/p>
&lt;p>一般來說，對於眼睛疲勞的對策，往往侷限於「點眼藥水」、「適度休息」、「戴抗藍光眼鏡」等表面上的建議。然而，身為工程師，我們應該找出問題的根本原因（Root Cause），並從系統（環境）層面進行最佳化。&lt;/p>
&lt;p>本文將從物理學（光學）、生物化學、人體工學，以及顯示器硬體架構的觀點出發，徹底剖析程式設計師眼睛疲勞的機制，並透過數學公式和圖解深入探討能減輕此症狀的終極螢幕設定與小工具。&lt;/p>
&lt;hr>
&lt;h1 id="第1章從物理與生物化學解開電腦視覺症候群cvs的機制">第1章：從物理與生物化學解開電腦視覺症候群（CVS）的機制
&lt;/h1>&lt;p>電腦視覺症候群（CVS）並非由單一因素所引起。如下方的圓餅圖所示，它是多種因素錯綜複雜地交織在一起，進而導致眼睛疲勞、疼痛、乾眼症，甚至是全身的疲勞感。&lt;/p>
&lt;pre class="mermaid">
pie title 電腦視覺症候群 (CVS) 的成因
&amp;#34;藍光與眩光&amp;#34; : 30
&amp;#34;螢幕閃爍 (PWM)&amp;#34; : 25
&amp;#34;對比度與照明不當&amp;#34; : 20
&amp;#34;對焦疲勞 (睫狀肌)&amp;#34; : 15
&amp;#34;乾眼症 (眨眼次數減少)&amp;#34; : 10
&lt;/pre>
&lt;p>在此，我們將針對影響特別大的「光的物理特性」與「眼球的對焦調節功能」進行解說。&lt;/p>
&lt;h2 id="11-藍光的物理特性與光子能量">1.1 藍光的物理特性與光子能量
&lt;/h2>&lt;p>顯示器發出的藍光（藍色光）大約落在 $400 \text{ nm} \sim 490 \text{ nm}$ 的波長範圍。為何這會對眼睛造成負擔，可以透過量子力學基礎的「普朗克-愛因斯坦關係式」來解釋。&lt;/p>
&lt;p>光所帶有的能量 $E$ 可以用以下數學公式表示：&lt;/p>
$$ E = h\nu = \frac{hc}{\lambda} $$&lt;p>其中，各變數的意義如下：&lt;/p>
&lt;ul>
&lt;li>$E$ : 每個光子（Photon）的能量 (Joule)&lt;/li>
&lt;li>$h$ : 普朗克常數 ($6.626 \times 10^{-34} \text{ J}\cdot\text{s}$)&lt;/li>
&lt;li>$c$ : 真空中的光速 ($3.0 \times 10^8 \text{ m/s}$)&lt;/li>
&lt;li>$\lambda$ : 光的波長 (m)&lt;/li>
&lt;li>$\nu$ : 光的頻率 (Hz)&lt;/li>
&lt;/ul>
&lt;p>這個數學公式所揭示的重要事實是，&lt;strong>「光的能量 $E$ 與波長 $\lambda$ 成反比」&lt;/strong>。也就是說，在可見光中波長最短的藍光，具有極高的能量。這種高能量的光子不易被角膜或水晶體吸收和衰減，會直接到達視網膜深處，對視細胞造成強烈的氧化壓力。&lt;/p>
&lt;h2 id="12-色差chromatic-aberration與焦點偏移">1.2 色差（Chromatic Aberration）與焦點偏移
&lt;/h2>&lt;p>進一步從光學的角度來看，光波長的不同會產生「折射率」的差異。介質（在這裡指水晶體等）的折射率 $n$ 依賴於波長 $\lambda$，可以透過柯西色散公式（Cauchy&amp;rsquo;s equation）來近似。&lt;/p>
$$ n(\lambda) = B + \frac{C}{\lambda^2} $$&lt;p>($B, C$ 為介質固有的常數)&lt;/p>
&lt;p>從這個公式可以看出，波長 $\lambda$ 越短的藍光，折射率 $n$ 越大。因此，即使是在紅光等光線恰好在視網膜上聚焦的狀態下，藍光也會產生較大的折射，並在&lt;strong>視網膜前方&lt;/strong>成像。
當大腦意識到這種「由藍光引起的影像模糊（色差）」時，就會不斷地向睫狀肌發出重新對焦的指令。這是導致眼部肌肉在無意識下逐漸疲勞的一大主因。&lt;/p>
&lt;h2 id="13-對焦調節肌睫狀肌與透鏡公式">1.3 對焦調節肌（睫狀肌）與透鏡公式
&lt;/h2>&lt;p>當我們將焦點對準螢幕上的細小文字時，眼睛內部會調節水晶體（透鏡）的厚度。薄透鏡公式如下：&lt;/p>
$$ \frac{1}{f} = \frac{1}{a} + \frac{1}{b} $$&lt;ul>
&lt;li>$f$: 水晶體的焦距&lt;/li>
&lt;li>$a$: 眼睛到螢幕的距離（物距）&lt;/li>
&lt;li>$b$: 水晶體到視網膜的距離（像距：成人眼球大約固定為 $24 \text{ mm}$）&lt;/li>
&lt;/ul>
&lt;p>在編寫程式時，如果與螢幕的距離 $a$ 長期處於較短的狀態（例如：$40 \text{ cm} \sim 50 \text{ cm}$），為了在視網膜上形成準確的影像（保持 $b$ 恆定），就必須維持極短的焦距 $f$。當睫狀肌極度收縮的狀態持續數小時，肌肉就會陷入痙攣，進而引發伴隨肩頸痠痛和頭痛的嚴重眼睛疲勞。&lt;/p>
&lt;hr>
&lt;h1 id="第2章硬體顯示器的選擇與疲勞因素的排除">第2章：硬體顯示器的選擇與疲勞因素的排除
&lt;/h1>&lt;p>為了減輕眼睛疲勞，在進行軟體設定之前，必須先確認並改善硬體的規格。特別是「調光方式」和「螢幕更新率」是絕對不能妥協的重點。&lt;/p>
&lt;h2 id="21-pwm調光的恐怖揭開看不見的閃爍">2.1 PWM調光的恐怖：揭開看不見的閃爍
&lt;/h2>&lt;p>調整液晶（LCD）或有機發光二極體（OLED）螢幕亮度的技術，主要可分為「DC（直流電）調光」和「PWM（脈衝寬度調變）調光」兩種。&lt;/p>
&lt;p>PWM調光是透過讓人眼無法察覺的高速閃爍背光 LED，並利用「亮起時間」與「熄滅時間」的比例來模擬調整畫面亮度的技術。根據PWM的空占比（Duty Cycle），其平均亮度 $L$ 可以用以下公式表示：&lt;/p>
$$ L = L_{max} \times \frac{T_{on}}{T_{on} + T_{off}} \times 100 \ (\%) $$&lt;ul>
&lt;li>$T_{on}$ : LED 亮起的時間&lt;/li>
&lt;li>$T_{off}$ : LED 熄滅的時間&lt;/li>
&lt;li>$L_{max}$ : 峰值時的最大亮度&lt;/li>
&lt;/ul>
&lt;p>當 PWM 調光的頻率較低（例如：$200 \text{ Hz} \sim 300 \text{ Hz}$）時，即使在意識上感覺不到畫面的閃爍（Flicker），大腦和瞳孔也會在無意識中對光的閃爍產生反應，導致瞳孔不斷地放大與收縮。這會引發極度疲勞、頭痛，甚至是噁心感。&lt;/p>
&lt;p>&lt;strong>【PWM 的檢測方法與對策】&lt;/strong>
要確認自己的螢幕是否為 PWM 調光，可以開啟智慧型手機的相機應用程式，使用「慢動作攝影」模式拍攝螢幕的白色畫面（如瀏覽器的空白頁面）。如果影片中出現移動的黑色條紋（Banding），就表示該螢幕採用的是低頻的 PWM 調光。
程式設計師在挑選螢幕時，絕對應該選擇規格表上明確標示為**「不閃屏（DC調光）」**的產品。&lt;/p>
&lt;h2 id="22-螢幕更新率hz與動態模糊在眼科學上的影響">2.2 螢幕更新率（Hz）與動態模糊在眼科學上的影響
&lt;/h2>&lt;p>螢幕更新率是指顯示器每秒鐘重繪畫面的次數（Hz）。
一般的辦公室螢幕為 $60 \text{ Hz}$，但近年來 $120 \text{ Hz}$ 或 $144 \text{ Hz}$ 的高更新率螢幕已經相當普及。這不僅對遊戲玩家有益，對程式設計師來說也是極為有利的。&lt;/p>
&lt;p>當捲動大量程式碼或在終端機中查看快速捲動的日誌時，在 $60 \text{ Hz}$ 的顯示器上，加上像素反應時間的限制，很容易產生「動態模糊（殘影）」。眼睛在捲動過程中會無意識地試圖捕捉文字的形狀並持續對焦，但如果文字是模糊的，大腦視覺皮層的處理負載就會急劇飆升。
如果是 $120 \text{ Hz}$ 以上的顯示器，即使在捲動中文字也能清晰可見，因此可以大幅減少這種無意識的眼球運動和對焦調節的負擔。&lt;/p>
&lt;h2 id="23-面板類型與對比度ips-va-oled">2.3 面板類型與對比度（IPS, VA, OLED）
&lt;/h2>&lt;p>畫面的對比度直接關係到文字的清晰度。
「韋伯-費希納定律」指出，人類的感覺量與刺激的對數成正比，其公式如下：&lt;/p>
$$ p = k \ln \left( \frac{S}{S_0} \right) $$&lt;p>（$p$: 感覺量, $S$: 刺激的物理量, $S_0$: 閾值, $k$: 常數）&lt;/p>
&lt;p>也就是說，相較於絕對的亮度，人類的眼睛對「相對亮度的比例（對比度）」反應更為強烈。
當需要長時間閱讀具有語法突顯（Syntax Highlighting）的程式碼時，黑色下潛更深（對比度高）的 VA 面板（$3000:1$），或是能以像素為單位完全關閉背光的 OLED 面板（$1,000,000:1$起跳），都能讓文字的輪廓變得非常清晰，從而提升閱讀體驗。
然而，正如後文所述，若在全黑的房間觀看對比度極高的螢幕，瞳孔會因過度收縮而感到疲勞，因此必須與環境光取得平衡。&lt;/p>
&lt;p>下方的圖表比較了標準的 LCD 螢幕與近年備受關注的 OLED（低藍光設計）發光光譜的差異。&lt;/p>
&lt;pre class="mermaid">
xychart-beta
title 藍光發射光譜比較
x-axis &amp;#34;波長 (nm)&amp;#34; [400, 420, 440, 460, 480, 500]
y-axis &amp;#34;相對強度&amp;#34; 0 --&amp;gt; 100
bar &amp;#34;標準 LCD (W-LED)&amp;#34; [10, 30, 95, 80, 40, 20]
line &amp;#34;現代 OLED / 低藍光&amp;#34; [5, 10, 40, 75, 55, 30]
&lt;/pre>
&lt;hr>
&lt;h1 id="第3章螢幕校色與作業系統及軟體設定">第3章：螢幕校色與作業系統及軟體設定
&lt;/h1>&lt;p>與挑選硬體同等重要的，是作業系統端的色彩空間管理和螢幕校色。&lt;/p>
&lt;h2 id="31-色域srgb-vs-dci-p3的陷阱與-icc-色彩描述檔">3.1 色域（sRGB vs DCI-P3）的陷阱與 ICC 色彩描述檔
&lt;/h2>&lt;p>近期的螢幕常以具備 DCI-P3 覆蓋率 95% 以上的「廣色域」為賣點，但這在程式開發用途上有時反而會成為缺點。
在 Windows 環境下，若未套用正確的 ICC 色彩描述檔（由國際色彩聯盟所制定的色彩設定檔）就使用廣色域螢幕，那些原本基於標準 sRGB 指定的 VS Code 語法突顯顏色（例如紅色或綠色的警告色），將會呈現出極度不自然的飽和度（過飽和狀態）。
這些強烈的色彩會對眼睛造成強大刺激，因此強烈建議從作業系統的顯示器設定中安裝正確的 ICC 色彩描述檔，或者在螢幕的 OSD 設定中切換至「sRGB 模擬模式」。&lt;/p>
&lt;p>下方的循序圖顯示了套用正確的 ICC 色彩描述檔，直至渲染出對眼睛舒適的色彩的過程。&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant OS as &amp;#34;作業系統&amp;#34;
participant LUT as &amp;#34;色彩 LUT (Look-Up Table)&amp;#34;
participant Mon as &amp;#34;顯示器&amp;#34;
participant Eye as &amp;#34;程式設計師的眼睛&amp;#34;
OS-&amp;gt;&amp;gt;LUT: &amp;#34;載入正確的 ICC 色彩描述檔 (例如 sRGB)&amp;#34;
OS-&amp;gt;&amp;gt;LUT: &amp;#34;套用夜間光線設定 (3400K)&amp;#34;
LUT-&amp;gt;&amp;gt;Mon: &amp;#34;調整 RGB 訊號輸出&amp;#34;
Mon-&amp;gt;&amp;gt;Eye: &amp;#34;渲染精準且降低飽和度的色彩&amp;#34;
Eye--&amp;gt;&amp;gt;Eye: &amp;#34;減輕視覺皮層負擔&amp;#34;
&lt;/pre>
&lt;h2 id="32-軟體的應對措施flux--夜間光線">3.2 軟體的應對措施（f.lux / 夜間光線）
&lt;/h2>&lt;p>作為抗藍光的對策，最簡單且有效的方法是使用能根據時間動態調整色溫（Color Temperature）的軟體。&lt;/p>
&lt;ul>
&lt;li>Windows: &lt;strong>夜間光線（Night Light）&lt;/strong>&lt;/li>
&lt;li>macOS: &lt;strong>夜覽（Night Shift）&lt;/strong>&lt;/li>
&lt;li>第三方軟體: &lt;strong>f.lux&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>色溫以克耳文（$\text{K}$）為單位表示。白天的陽光大約是 $5500\text{K} \sim 6500\text{K}$（帶藍的白光），如果眼睛持續接收這種光線，大腦松果體分泌「褪黑激素（睡眠荷爾蒙）」的作用就會受到抑制。
到了傍晚之後，可透過這些軟體將色溫降低到 $3400\text{K} \sim 1900\text{K}$（暖色系的橘色到紅色）。這不僅能物理性地減少藍光的發光量，也有助於維持正常的晝夜節律（生理時鐘），同時防止高能量光子到達眼球。&lt;/p>
&lt;hr>
&lt;h1 id="第4章終極硬體解決方案導入最新小工具">第4章：終極硬體解決方案：導入最新小工具
&lt;/h1>&lt;p>如果上述說明的對策仍無法消除疲勞，那就必須投資外部小工具，以徹底改變周圍的環境。&lt;/p>
&lt;h2 id="41-偏置照明bias-lighting與螢幕掛燈screenbar">4.1 偏置照明（Bias Lighting）與螢幕掛燈（ScreenBar）
&lt;/h2>&lt;p>在黑暗的房間中注視明亮的螢幕，會造成視野中心（高亮度）與周圍（低亮度）之間產生極大的對比。這被稱為**「不適眩光（Discomfort Glare）」**。
在這種環境下，眼睛會為了接收光線而試圖放大瞳孔，同時又為了抵擋中心的刺眼強光而試圖縮小瞳孔。這種矛盾的狀態會導致虹彩肌嚴重疲勞。&lt;/p>
&lt;p>解決這個問題的方法是採用「偏置照明（Bias Lighting）」。
特別推薦像 &lt;strong>BenQ ScreenBar&lt;/strong> 這種「螢幕掛燈」。&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;黑暗的房間環境&amp;#34;] --&amp;gt; B[&amp;#34;高亮度對比 (螢幕 vs 房間)&amp;#34;]
B --&amp;gt; C[&amp;#34;瞳孔收縮/放大的矛盾狀態&amp;#34;]
C --&amp;gt; D[&amp;#34;虹彩肌嚴重疲勞&amp;#34;]
A --&amp;gt; E[&amp;#34;安裝螢幕掛燈 (例如 ScreenBar)&amp;#34;]
E --&amp;gt; F[&amp;#34;非對稱光學設計 (螢幕不反光)&amp;#34;]
F --&amp;gt; G[&amp;#34;平衡的環境亮度&amp;#34;]
G --&amp;gt; H[&amp;#34;放鬆虹彩肌並減緩眼睛疲勞&amp;#34;]
&lt;/pre>
&lt;p>ScreenBar 的最大特色在於其「非對稱光學設計（Asymmetrical Optical Design）」。透過特殊的反射板和透鏡，它不會將光線直接照射到螢幕表面（防止螢幕反光和眩光），而是均勻地照亮手邊的鍵盤以及螢幕後方的空間。這能大幅減緩整個視野的亮度差異（對比度），從而消除眼睛的負擔。&lt;/p>
&lt;h2 id="42-e-ink-顯示器的典範轉移dasung--boox">4.2 E-Ink 顯示器的典範轉移（Dasung &amp;amp; Boox）
&lt;/h2>&lt;p>在閱讀冗長的 API 參考文件、技術書籍（PDF）或原始碼時，現代終極的解決方案可以說是&lt;strong>將「E-Ink（電子紙）顯示器」作為副螢幕使用&lt;/strong>。&lt;/p>
&lt;p>與液晶或 OLED 不同，E-Ink 沒有自體發光的背光模組。它是透過電壓使膠囊內帶電的黑白顏料粒子（如二氧化鈦等）移動（電泳技術），並藉由反射周圍的環境光來顯示文字。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>物理性的藍光發光量：零&lt;/strong>&lt;/li>
&lt;li>&lt;strong>伴隨 PWM 或畫面更新的閃爍：完全為零&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>如果將 &lt;strong>Dasung Paperlike&lt;/strong> 系列（如 25.3 吋）或 &lt;strong>Onyx Boox Mira&lt;/strong> 等 E-Ink 螢幕直立放置，作為純文字專用的副螢幕，就能體驗到如同閱讀紙本印刷品般的文件閱讀感受。
雖然它有畫面重繪延遲（更新率較低）的弱點，但若僅限於程式開發環境中的「靜態文字閱讀」用途，這世界上恐怕沒有比它對眼睛更友善的設備了。&lt;/p>
&lt;hr>
&lt;h1 id="第5章人體工學ergonomics與運作規則">第5章：人體工學（Ergonomics）與運作規則
&lt;/h1>&lt;p>無論準備了多麼頂級的硬體設備，如果操作者的姿勢或使用規則錯誤，也是徒勞無功。&lt;/p>
&lt;h2 id="51-乾眼症的流體力學與視線角度">5.1 乾眼症的流體力學與視線角度
&lt;/h2>&lt;p>乾眼症不僅僅是「眼睛乾澀」的不適感，角膜表面的淚液層一旦被破壞，就會導致光線漫反射、視線模糊，進而造成更嚴重的眼睛疲勞（睫狀肌過度使用），形成惡性循環。
淚液的蒸發量與眼球接觸空氣的表面積（眼裂面積）成正比。&lt;/p>
&lt;p>理想的螢幕配置視線角度 $\theta$，應該在水平線下方 $15^\circ \sim 20^\circ$ 之間。
假設螢幕中心到眼睛的水平距離為 $d$，螢幕中心與眼睛高度的高低差為 $h$，則適用以下三角函數：&lt;/p>
$$ \tan \theta = \frac{h}{d} $$&lt;p>舉例來說，若與螢幕的距離 $d$ 為 $60 \text{ cm}$（一般辦公桌環境），要使 $\theta = 15^\circ$ 的話：&lt;/p>
$$ h = 60 \times \tan(15^\circ) \approx 60 \times 0.267 = 16.02 \text{ cm} $$&lt;p>換言之，&lt;strong>螢幕的中心點最好低於眼睛高度約 $16 \text{ cm}$&lt;/strong>。
藉由視線微幅向下，上眼瞼會自然垂下，進而減少眼球的暴露面積，從而大幅防止淚液蒸發。強烈建議導入螢幕支架（如 Ergotron 等），並將這個高度調整到以毫米為單位的精準度。&lt;/p>
&lt;h2 id="52-徹底執行並自動化世界標準的20-20-20-法則">5.2 徹底執行並自動化世界標準的「20-20-20 法則」
&lt;/h2>&lt;p>美國眼科醫學會（AAO）以及世界各地眼科醫師所推崇的數位裝置使用時舒緩眼睛疲勞的方法，就是**「20-20-20 法則」**。&lt;/p>
&lt;p>&lt;strong>『每 20 分鐘，眺望 20 英尺（約 6 公尺）以外的距離，維持 20 秒』&lt;/strong>&lt;/p>
&lt;p>透過這個簡單的動作，可以強迫極度收縮的睫狀肌放鬆，使水晶體變薄，並重設對焦調節功能。
由於程式設計師進入心流狀態時往往會忘記時間，因此建立一套自動強制執行此法則的機制，才是符合工程師風格的解決方案。
以下是一個極為簡單的 Python 腳本範例，使用 &lt;code>tkinter&lt;/code> 每 20 分鐘強制顯示一次彈出視窗。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tkinter&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">tk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">tkinter&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">messagebox&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">remind_20_20_20&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 隱藏主視窗&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tk&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Tk&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">root&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">withdraw&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 等待 20 分鐘 (1200 秒)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">20&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 在最上層顯示警告對話方塊&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messagebox&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">showinfo&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;20-20-20 Rule&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;請將視線離開螢幕，眺望 6 公尺外至少 20 秒！&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">(讓睫狀肌放鬆)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 用於放鬆的 20 秒鐘&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;__main__&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 在背景執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">remind_20_20_20&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>透過將這樣的腳本註冊到開機啟動項，或利用作業系統內建的工作排程器、Cron 來執行，就能將強制恢復的週期融入日常生活中。&lt;/p>
&lt;hr>
&lt;h1 id="結語將眼睛疲勞對策視為對未來的投資">結語：將眼睛疲勞對策視為對未來的投資
&lt;/h1>&lt;p>我們軟體工程師的職涯長達數十年。而支撐這段職涯的，不是昂貴的鍵盤，也不是最新款的 CPU，毫無疑問，而是我們自己的「眼睛」與「大腦」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>理解光能量 ($E = hc/\lambda$) 與對焦調節所帶來的物理負荷&lt;/strong>&lt;/li>
&lt;li>&lt;strong>導入不閃屏（DC調光）且具備高螢幕更新率的顯示器&lt;/strong>&lt;/li>
&lt;li>&lt;strong>使用 ScreenBar 等偏置照明，最佳化環境的相對對比度&lt;/strong>&lt;/li>
&lt;li>&lt;strong>考慮將 E-Ink 螢幕作為終極的純文字閱讀設備&lt;/strong>&lt;/li>
&lt;li>&lt;strong>使用螢幕支架打造基於 $\tan \theta = h/d$ 的最佳視線角度，並將「20-20-20 法則」系統化&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>這些對策或許會伴隨著短期的花費和功夫，但為了延長眼睛的健康壽命，並將一生中的生產力和生活品質（QOL）最大化，這可以說是最具成本效益的「技術投資」。請立即重新檢視自己的開發環境，將對眼睛的體貼實作進去吧。&lt;/p></description></item><item><title>適合長時間寫程式！推薦給工程師的 5 款機械式鍵盤</title><link>http://kenji.blog/zh-tw/p/engineer-mechanical-keyboard-recommendations/</link><pubDate>Sat, 12 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/zh-tw/p/engineer-mechanical-keyboard-recommendations/</guid><description>&lt;img src="http://kenji.blog/p/engineer-mechanical-keyboard-recommendations/img/eyecatch.jpg" alt="Featured image of post 適合長時間寫程式！推薦給工程師的 5 款機械式鍵盤" />&lt;h1 id="適合長時間寫程式推薦給工程師的-5-款機械式鍵盤">適合長時間寫程式！推薦給工程師的 5 款機械式鍵盤
&lt;/h1>&lt;p>對於程式設計師、系統工程師、資料科學家等在 IT 產業工作的專業人士來說，鍵盤不僅僅是一個輸入設備。它是「將思考轉化為程式碼形式的介面」，更是每天連續直接接觸數小時的最重要工作工具。&lt;/p>
&lt;p>持續使用劣質的鍵盤不僅會導致打字速度下降，還會對手腕和手指關節造成過度的負擔，進而增加罹患腱鞘炎（如腕隧道症候群）的風險。相反地，擁有一款順手、敲擊感良好且具備高度客製化能力的鍵盤，將是大幅提升生產力與健康的「最佳投資」。&lt;/p>
&lt;p>本文將針對各位工程師，超越單純的「推薦」，從鍵盤的物理學、內部電子電路，一直到最新的韌體技術進行徹底的解說。在此基礎上，為您介紹 5 款真正經得起實用考驗的終極鍵盤。&lt;/p>
&lt;h2 id="1-鍵軸的物理學與機制">1. 鍵軸的物理學與機制
&lt;/h2>&lt;p>決定鍵盤敲擊感的最重要因素就是「鍵軸（Key Switch）」。機械式鍵盤的軸體由彈簧和接點機構組成，其物理特性會作為回饋傳達至我們的指尖。&lt;/p>
&lt;h3 id="11-虎克定律與彈簧常數">1.1 虎克定律與彈簧常數
&lt;/h3>&lt;p>機械軸的觸發壓力（Actuation Force）主要由內部彈簧的特性決定。這個彈簧的行為可以近似地用古典力學中的「虎克定律（Hooke&amp;rsquo;s Law）」來表示。&lt;/p>
$$ F = -k x $$&lt;p>在這裡，$F$ 是恢復力（手指感覺到的反彈力），$k$ 是彈簧的彈簧常數，$x$ 是按下的距離（行程）。
如果是線性軸（Linear switch，如紅軸或黑軸），幾乎完全遵循這個虎克定律，具有隨著按下深度成比例增加反彈力的線性（Linear）特性。&lt;/p>
&lt;h3 id="12-觸發能量的積分計算">1.2 觸發能量的積分計算
&lt;/h3>&lt;p>將按鍵識別為「已輸入」的點稱為觸發點（Actuation Point）。從開始按下按鍵到抵達觸發點 $x_a$ 為止，手指所消耗的能量（作功） $E$ 可以用力對距離的積分來表示。&lt;/p>
$$ E = \int_{0}^{x_a} F(x) \, dx $$&lt;p>在段落軸（Tactile switch，茶軸）或有聲段落軸（Clicky switch，青軸）的情況下，由於存在接點摩擦的物理阻力（段落感，Tactile bump），$F(x)$ 不是簡單的一次函數，而是在特定行程位置呈現非線性峰值的函數。&lt;/p>
&lt;pre class="mermaid">
flowchart TD
A[&amp;#34;手指開始按下&amp;#34;] --&amp;gt; B{&amp;#34;軸的種類&amp;#34;}
B --&amp;gt;|線性| C[&amp;#34;阻力線性增加&amp;#34;]
B --&amp;gt;|段落| D[&amp;#34;中間有物理阻力 (段落感)&amp;#34;]
B --&amp;gt;|有聲段落| E[&amp;#34;產生段落感的同時發聲機構運作&amp;#34;]
C --&amp;gt; F[&amp;#34;抵達觸發點 (Actuation Point)&amp;#34;]
D --&amp;gt; F
E --&amp;gt; F
F --&amp;gt; G[&amp;#34;觸底 (Bottom Out)&amp;#34;]
&lt;/pre>
&lt;p>當工程師進行長時間的程式編寫時，如果這個 $E$（觸發能量）過大，手指容易疲勞；如果過小，則容易增加誤觸（Typo）的機率。一般來說，具有 45g～55g 左右觸發力的軸體被認為在減少疲勞和準確性之間取得了平衡，受到許多工程師的喜愛。&lt;/p>
&lt;h3 id="13-最先進的鍵軸技術靜電容無接點與霍爾效應">1.3 最先進的鍵軸技術：靜電容無接點與霍爾效應
&lt;/h3>&lt;p>還有一些不具有實體金屬接點的更先進的軸體技術。&lt;/p>
&lt;p>&lt;strong>靜電容無接點方式（Topre）&lt;/strong>
利用圓錐形彈簧和橡膠碗，透過偵測按下時靜電容量的變化來判定輸入。因為沒有實體接點，所以幾乎沒有磨損，也不會發生按鍵連擊（Chattering，按一次卻輸入多次的現象）。橡膠碗帶來獨特的「啵叩叩叩」敲擊感，一旦體驗過便會無法自拔。&lt;/p>
&lt;p>&lt;strong>磁軸（Hall Effect）&lt;/strong>
利用霍爾效應，將嵌入在軸心（Stem）的磁鐵靠近電路板上的霍爾感測器，讀取磁通量密度的變化作為電壓。
霍爾效應產生的電動勢 $V_H$ 可由以下公式表示：&lt;/p>
$$ V_H = R_H \left( \frac{I \cdot B}{t} \right) $$&lt;p>在這裡，$R_H$ 是霍爾係數，$I$ 是電流，$B$ 是磁通量密度，$t$ 是導體的厚度。透過這項技術，可以將按鍵行程的深度作為類比數值連續取得，從而實現「以 0.1mm 為單位改變觸發點（Actuation Point Adjustment）」或是「在開始放開按鍵的瞬間即關閉（Rapid Trigger）」等驚人的控制。&lt;/p>
&lt;h2 id="2-鍵盤的電子電路與效能指標">2. 鍵盤的電子電路與效能指標
&lt;/h2>&lt;p>即使鍵軸再優秀，如果處理它的電子電路或微控制器（Microcontroller, MCU）效能低下，也無法發揮最佳效能。&lt;/p>
&lt;h3 id="21-矩陣掃描與回報率">2.1 矩陣掃描與回報率
&lt;/h3>&lt;p>鍵盤內部存在幾十到一百多個鍵軸，但微控制器的腳位數量有限，無法將所有鍵軸連接到個別的腳位。因此，會將鍵軸佈線成行（Row）和列（Column）的網格狀（矩陣），並透過高速掃描來判定哪個按鍵被按下。&lt;/p>
&lt;pre class="mermaid">
flowchart LR
M[&amp;#34;微控制器 (MCU)&amp;#34;] --&amp;gt;|將 Row 輸出切換為 High/Low| R1[&amp;#34;Row 1&amp;#34;]
M --&amp;gt; R2[&amp;#34;Row 2&amp;#34;]
R1 --&amp;gt; S11[&amp;#34;Switch 1,1&amp;#34;] &amp;amp; S12[&amp;#34;Switch 1,2&amp;#34;]
R2 --&amp;gt; S21[&amp;#34;Switch 2,1&amp;#34;] &amp;amp; S22[&amp;#34;Switch 2,2&amp;#34;]
S11 &amp;amp; S21 --&amp;gt; C1[&amp;#34;Column 1&amp;#34;]
S12 &amp;amp; S22 --&amp;gt; C2[&amp;#34;Column 2&amp;#34;]
C1 &amp;amp; C2 --&amp;gt;|偵測電壓並讀取| M
&lt;/pre>
&lt;p>&lt;strong>回報率（Polling Rate）&lt;/strong> 是鍵盤向電腦報告「目前按鍵狀態」的頻率。標準的鍵盤是 125Hz（每 8ms 一次），但在高階型號中，也有進行 1000Hz（每 1ms 一次），甚至最近的 8000Hz（每 0.125ms 一次）等超高速通訊的產品。
在寫程式時 1000Hz 已經綽綽有餘，但這能帶來防止在超高速打字時漏鍵的安心感。&lt;/p>
&lt;h3 id="22-n-key-rollover-nkro-與防鬼鍵-anti-ghosting">2.2 N-Key Rollover (NKRO) 與防鬼鍵 (Anti-Ghosting)
&lt;/h3>&lt;p>&lt;strong>N-Key Rollover（NKRO）&lt;/strong> 是指同時按下多個按鍵時，所有按鍵都能被準確識別的功能。過去受到 USB 連接的限制，有著「最多 6 鍵」等限制，但現在的高階鍵盤透過巧妙運用 USB 的 HID 報告，實現了實質上無限制的同時按下（Full NKRO）。&lt;/p>
&lt;p>對於在 Vim 或 Emacs 等編輯器中頻繁使用複雜快捷鍵（例如：&lt;code>Ctrl + Shift + Alt + 任意鍵&lt;/code> 等）的工程師來說，完整的 NKRO 是必備條件。&lt;/p>
&lt;h3 id="23-去抖動延遲-debounce-delay">2.3 去抖動延遲 (Debounce Delay)
&lt;/h3>&lt;p>帶有金屬接點的機械軸，在按下或放開時會發生接點微小彈跳的「彈跳現象 (Bounce)」。為了讓微控制器忽略這個現象的處理時間就是&lt;strong>去抖動延遲 (Debounce Delay)&lt;/strong>。通常會刻意設定 5ms～20ms 左右的延遲，但在前面提到的靜電容無接點方式或磁軸中，因為不存在實體的接點雜訊，可以將去抖動延遲設定為零（或極小），實現壓倒性的反應速度。&lt;/p>
&lt;h2 id="3-韌體與客製化能力qmk--via">3. 韌體與客製化能力（QMK / VIA）
&lt;/h2>&lt;p>如果說硬體是「肉體」，那麼韌體就是鍵盤的「大腦」。現代專為工程師設計的高階鍵盤，不僅僅能發送按鍵碼，還具備執行複雜程式的能力。&lt;/p>
&lt;h3 id="31-qmk-firmware">3.1 QMK Firmware
&lt;/h3>&lt;p>&lt;strong>QMK (Quantum Mechanical Keyboard)&lt;/strong> 是一個開源的鍵盤韌體。使用 C 語言編寫，從更改鍵位配置、建立巨集，到控制 LED 動畫，毫不誇張地說「無所不能」。&lt;/p>
&lt;h3 id="32-進階按鍵配置功能">3.2 進階按鍵配置功能
&lt;/h3>&lt;p>在 QMK 提供的功能中，特別能爆發性提升工程師生產力的功能如下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>層級功能 (Layers):&lt;/strong> 就像手機鍵盤切換「文字」和「數字」一樣，只有在按下特定按鍵（如 Fn 鍵）時，才會將整個鍵盤的配置切換成另一組。可以讓雙手不離開主鍵盤區（Home Position）就能輸入方向鍵、巨集或符號。&lt;/li>
&lt;li>&lt;strong>Mod-Tap:&lt;/strong> 讓一個按鍵在「短按」和「長按」時具有不同的作用。例如，將空白鍵設定為「短按為 Space，長按為 Shift」（Space Cadet Shift），可以有效利用拇指。&lt;/li>
&lt;li>&lt;strong>Home Row Mods:&lt;/strong> 這是一種將主鍵盤區的按鍵（ASDF, JKL; 等）在長按時指派為修飾鍵（Ctrl, Shift, Alt, GUI）的方法。不再需要過度使用小指去伸長按 Ctrl 鍵，大幅減輕 Vim 或 Emacs 使用者手腕的疲勞。&lt;/li>
&lt;/ul>
&lt;h3 id="33-透過-via--vial-即時設定">3.3 透過 VIA / VIAL 即時設定
&lt;/h3>&lt;p>QMK 的缺點是「每次更改設定都需要編譯原始碼，並刷寫（寫入）韌體」。解決這個問題的就是 &lt;strong>VIA&lt;/strong> 或 &lt;strong>VIAL&lt;/strong>。它們可以從 GUI 應用程式（或網頁瀏覽器上）存取鍵盤，無需重新啟動即可即時改寫鍵位配置。&lt;/p>
&lt;h2 id="4-人體工學與鍵盤配置的科學">4. 人體工學與鍵盤配置的科學
&lt;/h2>&lt;p>一般的「水平錯位（Row-staggered，每一列按鍵錯開的配置）」是為了避免打字機的實體搖臂糾纏在一起而留下的殘餘設計，並非基於人類手的構造。&lt;/p>
&lt;pre class="mermaid">
pie title 工程師理想的鍵盤配置偏好 (推測數據)
&amp;#34;水平錯位 (傳統型)&amp;#34; : 45
&amp;#34;Alice 配置 (人體工學)&amp;#34; : 15
&amp;#34;直列 (正交配置)&amp;#34; : 10
&amp;#34;垂直錯位 (分離式)&amp;#34; : 30
&lt;/pre>
&lt;p>更符合人體工學（Ergonomics）的配置有以下幾種：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>直列 (Ortholinear):&lt;/strong> 按鍵在水平和垂直方向完全排成一直線的網格狀配置。手指的彎曲和伸展呈直線，減少了運指的浪費。&lt;/li>
&lt;li>&lt;strong>垂直錯位 (Columnar Stagger):&lt;/strong> 配合人類手指的長度（中指較長，小指較短），將垂直列（Column）錯開的配置。能以自然的手部姿勢打字。&lt;/li>
&lt;li>&lt;strong>分離式 (Split):&lt;/strong> 左右手可以完全分開放置，因此能以展開肩膀、挺胸的自然姿勢打字，對於預防肩膀僵硬和頸椎直化（頸部前傾）有極大的效果。&lt;/li>
&lt;/ul>
&lt;h2 id="5-推薦給工程師的-5-款終極機械式鍵盤">5. 推薦給工程師的 5 款終極機械式鍵盤
&lt;/h2>&lt;p>綜合物理學、電子電路、韌體以及人體工學的觀點，我們精心挑選了 5 款真正經得起長時間編寫程式考驗、專為專業人士打造的鍵盤。&lt;/p>
&lt;hr>
&lt;h3 id="1-keychron-q-系列-q1-pro--q8-等---進入客製化鍵盤世界的入口">1. Keychron Q 系列 (Q1 Pro / Q8 等) - 進入客製化鍵盤世界的入口
&lt;/h3>&lt;p>來自香港的 Keychron 是引領近期客製化鍵盤風潮的代表。其中「Q 系列」採用了全鋁合金的厚重機身，以及將敲擊音調校至極限的「Gasket Mount (墊片結構)」設計。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>軸體:&lt;/strong> 機械軸（支援熱插拔，可自由更換軸體）&lt;/li>
&lt;li>&lt;strong>韌體:&lt;/strong> 完美支援 QMK/VIA&lt;/li>
&lt;li>&lt;strong>特色:&lt;/strong> 支援 macOS/Windows 雙系統切換開關。可選擇喜歡的配列，如 Alice 配置的 Q8 或 75% 配置的 Q1。&lt;/li>
&lt;li>&lt;strong>對工程師的優點:&lt;/strong> 雖然是量產產品，但開箱即可享受媲美自組鍵盤的極致敲擊感與客製化能力。非常適合使用 VIA 設定出類似 Vim 的方向鍵層級。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="2-hhkb-studio---專為駭客打造的-all-in-one-指標裝置">2. HHKB Studio - 專為駭客打造的 All-in-One 指標裝置
&lt;/h3>&lt;p>「Happy Hacking Keyboard (HHKB)」是為了 UNIX 程式設計師而誕生的傳奇鍵盤。最新的「HHKB Studio」並未採用傳統的靜電容無接點方式，而是採用了專屬開發的靜音機械軸，達到了進一步的進化。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>軸體:&lt;/strong> 線性・靜音機械軸（Kailh 製造，支援熱插拔）&lt;/li>
&lt;li>&lt;strong>特色:&lt;/strong> 鍵盤中央的指標桿（TrackPoint），以及 4 個手勢觸控板。&lt;/li>
&lt;li>&lt;strong>對工程師的優點:&lt;/strong> 雙手完全不需離開主鍵盤區，即可完成滑鼠游標操作、捲動以及視窗切換。一旦體驗過這種「一切都在指尖完成的體驗」，就再也回不去將右手伸向滑鼠的工作方式了。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="3-zsa-moonlander--ergodox-ez---終極的分離式人體工學">3. ZSA Moonlander / ErgoDox EZ - 終極的分離式人體工學
&lt;/h3>&lt;p>這是由加拿大 ZSA 公司開發的最高峰分離式鍵盤。由於左右獨立，可以配合肩膀的寬度來配置，即使長時間打字，肩膀和脖子的負擔也會驚人地減輕。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>軸體:&lt;/strong> 機械軸（相容 Cherry MX，支援熱插拔）&lt;/li>
&lt;li>&lt;strong>韌體:&lt;/strong> 基於 QMK（使用其獨特的強大 GUI 工具「Oryx」）&lt;/li>
&lt;li>&lt;strong>特色:&lt;/strong> 垂直錯位（Columnar Stagger）配置，拇指專用按鍵區，標配可調整傾斜角度（Tent）的支架。&lt;/li>
&lt;li>&lt;strong>對工程師的優點:&lt;/strong> 透過將 Enter、Space、Backspace 以及 Layer 切換指派給拇指，能大幅減少力量最弱的小指的負擔。對於深受腕隧道症候群困擾的工程師來說，這是一款救世主般的裝置。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="4-realforce-r3---國產的信賴與極致的敲擊感靜電容無接點方式">4. REALFORCE R3 - 國產的信賴與極致的敲擊感（靜電容無接點方式）
&lt;/h3>&lt;p>東普雷（Topre）引以為傲的日本傑作。長年被金融機構等專業現場使用的實績絕非浪得虛名。從 R3 世代開始也支援了 Bluetooth 藍牙連接。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>軸體:&lt;/strong> 靜電容無接點方式 (Topre)&lt;/li>
&lt;li>&lt;strong>特色:&lt;/strong> 具備 APC（Actuation Point Changer，觸發點調整）功能，可為每個按鍵個別設定 0.8mm、1.5mm、2.2mm、3.0mm 的觸發點。&lt;/li>
&lt;li>&lt;strong>對工程師的優點:&lt;/strong> 由於沒有實體接點，其滑順的按鍵觸感被稱為「羽毛觸感（Feather Touch）」，即使長時間寫程式，對手指的反彈壓力也能降至最低。可以將小指負責的按鍵（如 A 或 Enter 等）觸發點設定得較淺（0.8mm），輕觸即可反應，實現這樣的客製化。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="5-wooting-60he---磁軸帶來的革命性反應速度">5. Wooting 60HE - 磁軸帶來的革命性反應速度
&lt;/h3>&lt;p>這原本是專為電競玩家開發的鍵盤，但其創新的科技在追求最快打字和反應速度的工程師之間也獲得了極高的評價。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>軸體:&lt;/strong> Lekker Switch（霍爾效應磁軸）&lt;/li>
&lt;li>&lt;strong>特色:&lt;/strong> Rapid Trigger（快速觸發）功能，從 0.1mm 到 4.0mm，可以 0.1mm 為單位調整的觸發點。&lt;/li>
&lt;li>&lt;strong>對工程師的優點:&lt;/strong> 活用類比輸入，可以實現「淺按是小寫，深按是大寫（結合 Shift）」這樣變態級的設定（Dynamic Keystroke）。此外，只要手指稍微抬起瞬間就會解除按鍵觸發（Key-off），能防止在高速打字時發生意外的按鍵連續輸入，提供無與倫比的精準輸入體驗。&lt;/li>
&lt;/ul>
&lt;h2 id="結語">結語
&lt;/h2>&lt;p>選擇鍵盤是工程師職業生涯中「自我介面最佳化」的過程。從遵循虎克定律的物理彈簧觸感，到透過積分計算的觸發能量、使用 QMK 建構的巨集，再到終極的人體工學，值得追求的深度是無止境的。&lt;/p>
&lt;p>這次介紹的 5 款鍵盤（Keychron、HHKB Studio、Moonlander、REALFORCE、Wooting），每一款都是透過不同方法追求「最佳輸入體驗」的傑作。請務必配合您自身的打字風格和身體的困擾，找到最棒的夥伴。&lt;/p>
&lt;p>對鍵盤的投資，必定會化為「數百萬行沒有 Bug 的程式碼」，為您帶來回報。&lt;/p></description></item><item><title>AI開發中解決GPU記憶體不足的技巧（CPU卸載等）</title><link>http://kenji.blog/zh-tw/p/ai-gpu-vram-optimization-cpu-offloading/</link><pubDate>Fri, 11 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/zh-tw/p/ai-gpu-vram-optimization-cpu-offloading/</guid><description>&lt;img src="http://kenji.blog/p/ai-gpu-vram-optimization-cpu-offloading/img/eyecatch.jpg" alt="Featured image of post AI開發中解決GPU記憶體不足的技巧（CPU卸載等）" />&lt;h1 id="前言ai開發與vram之壁">前言：AI開發與「VRAM之壁」
&lt;/h1>&lt;p>近年來，大型語言模型（LLM）和擴散模型（Diffusion Models）等生成式AI技術取得了飛速的發展。然而，當許多開發者和研究人員在本地環境中對這些最先進的AI模型進行訓練（微調）或推論（Inference）時，面臨了一個極其物理的障礙——&lt;strong>「GPU記憶體（VRAM）不足」&lt;/strong>。&lt;/p>
&lt;p>即使是NVIDIA GeForce RTX 4090等消費級高階GPU，其VRAM最大也只有24GB，根本無法直接載入像Llama 3 70B這樣巨大的模型。而資料中心級別的H100（80GB）或B200（192GB）等則非常昂貴，並非個人或小團隊能輕易使用。如果無法突破這道「VRAM之壁（The Wall of VRAM）」，就連接觸最先進模型的機會都沒有。&lt;/p>
&lt;p>本文將從推論和訓練兩個方面，深入解說如何透過軟體和硬體架構的巧思，打破VRAM限制這個物理性約束的高階技巧。我們將結合數學公式和圖解，深入探討CPU卸載、KV Cache最佳化、梯度檢查點（Gradient Checkpointing），以及最新的統一記憶體（Unified Memory）架構。閱讀本文後，您將深刻理解VRAM的運作機制，並掌握在有限資源下處理巨大模型的實務知識。&lt;/p>
&lt;hr>
&lt;h1 id="1-ai模型vram消耗解析推論與訓練">1. AI模型VRAM消耗解析（推論與訓練）
&lt;/h1>&lt;p>解決VRAM不足的第一步，必須先從微觀的角度準確掌握「是什麼」以及「消耗了多少」記憶體。與其將其視為黑盒子，不如使用數學公式進行精確估算，這樣才能選擇合適的最佳化方法。&lt;/p>
&lt;h2 id="11-模型參數權重的記憶體計算">1.1 模型參數（權重）的記憶體計算
&lt;/h2>&lt;p>構成AI模型的參數（Weights）所消耗的基本記憶體量，是由模型的總參數數量以及表示這些參數的資料型別（Precision：精度）所決定的。&lt;/p>
&lt;p>深度學習中常用的資料型別，以及每個參數所佔用的位元組數（$B$）如下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FP32 (單精度浮點數):&lt;/strong> 4 bytes (標準訓練時的精度)&lt;/li>
&lt;li>&lt;strong>FP16 / BF16 (半精度浮點數):&lt;/strong> 2 bytes (一般的推論及混合精度訓練)&lt;/li>
&lt;li>&lt;strong>INT8 (8位元整數):&lt;/strong> 1 byte (量化模型)&lt;/li>
&lt;li>&lt;strong>INT4 (4位元整數量化):&lt;/strong> 0.5 bytes (GPTQ, AWQ, GGUF等極度量化)&lt;/li>
&lt;/ul>
&lt;p>假設模型整體的參數數量為 $P$，那麼單純由權重佔用的基礎記憶體量 $M_{weights}$ 可用以下公式表示：&lt;/p>
$$ M_{weights} = P \times B $$&lt;p>例如，若將Meta公開的「Llama 3 8B」模型（約80億參數）以FP16（半精度）載入，計算結果如下：&lt;/p>
$$ M_{weights} = 8,000,000,000 \times 2 \text{ bytes} \approx 16,000,000,000 \text{ bytes} \approx 16 \text{ GB} $$&lt;p>也就是說，純粹只將模型的權重載入GPU，就會消耗16GB的VRAM。在RTX 3060 (12GB) 上，這時候就會發生Out of Memory (OOM) 錯誤。然而，如果將模型量化為INT4，則為 $8 \times 0.5 = 4 \text{ GB}$，就能夠輕鬆載入。&lt;/p>
&lt;h2 id="12-推論時的記憶體消耗kv-cache的增長">1.2 推論時的記憶體消耗：KV Cache的增長
&lt;/h2>&lt;p>在LLM的推論（特別是自迴歸式的文字生成）中，與權重消耗相當，甚至更嚴重擠壓VRAM的元兇就是&lt;strong>KV Cache（Key-Value Cache）&lt;/strong>。
在Transformer架構中，為了避免重新計算過去已經生成和處理過的Token資訊，會將各個注意力層（Attention Layer）中的Key和Value張量（Tensor）持續快取在VRAM中。這雖然能提升計算速度（Compute），但隨著上下文長度（輸入提示詞長度＋生成長度）的增加，記憶體消耗量將呈線性爆炸性增長。&lt;/p>
&lt;p>處理1個Token時所消耗的KV Cache記憶體量 $M_{kv\_token}$，可根據模型架構透過以下公式嚴格計算出來：&lt;/p>
$$ M_{kv\_token} = 2 \times N_{layers} \times N_{heads\_kv} \times D_{head} \times B $$&lt;p>這裡各變數的意義如下：&lt;/p>
&lt;ul>
&lt;li>$2$ : 因為存在Key和Value兩個張量&lt;/li>
&lt;li>$N_{layers}$ : Transformer的層數 (Layer)&lt;/li>
&lt;li>$N_{heads\_kv}$ : KV注意力頭數（如果是GQA: Grouped Query Attention，會比一般的頭數少）&lt;/li>
&lt;li>$D_{head}$ : 各注意力頭的維度數（通常為隱藏層的維度數 $D_{model} / N_{heads}$）&lt;/li>
&lt;li>$B$ : 資料型別的位元組數（FP16為2）&lt;/li>
&lt;/ul>
&lt;p>整體的KV Cache量 $M_{kv\_total}$，則是將這個值乘以序列長度（$L_{seq}$）和批次大小（$BatchSize$）。&lt;/p>
$$ M_{kv\_total} = M_{kv\_token} \times L_{seq} \times BatchSize $$&lt;p>&lt;strong>具體範例：以Llama 2 7B為例&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>$N_{layers} = 32$&lt;/li>
&lt;li>$N_{heads\_kv} = 32$ (MHA的情況)&lt;/li>
&lt;li>$D_{head} = 128$&lt;/li>
&lt;li>FP16 ($B=2$)&lt;/li>
&lt;li>批次大小 1，序列長度 8192 (8K上下文)&lt;/li>
&lt;/ul>
$$ M_{kv\_total} = 2 \times 32 \times 32 \times 128 \times 2 \times 8192 \times 1 = 4,294,967,296 \text{ bytes} \approx 4 \text{ GB} $$&lt;p>如果將上下文長度延伸到32K（32768 Tokens），光是KV Cache就會消耗約16GB。若將批次大小增加到4，就會高達64GB。相比模型本身的體積，在推論時往往會要求大得多的VRAM，這是一大挑戰。&lt;/p>
&lt;h2 id="13-訓練時的記憶體消耗優化器梯度與激勵值">1.3 訓練時的記憶體消耗：優化器、梯度與激勵值
&lt;/h2>&lt;p>與推論時相比，模型的訓練（預訓練或微調）會消耗多得多的VRAM。這是因為不只要進行單純的前向傳播（Forward Pass），還必須保留反向傳播（Backward Pass）所需的資訊。訓練時的記憶體主要由以下4個要素構成：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>模型權重 (Model Weights):&lt;/strong> 與推論時相同，但在混合精度訓練中，有時會同時保留FP16和FP32（主權重）。&lt;/li>
&lt;li>&lt;strong>梯度 (Gradients):&lt;/strong> 透過反向傳播計算出的每個參數的梯度。在FP16的情況下，每個參數佔2位元組。&lt;/li>
&lt;li>&lt;strong>優化器狀態 (Optimizer States):&lt;/strong> AdamW等進階優化器，會針對每個參數保留一階動量（Momentum）和二階動量（Variance）。為了保持訓練的穩定性，這些通常以FP32（4位元組）保存。也就是說，兩個動量會消耗 $4 + 4 = 8$ 位元組/參數。&lt;/li>
&lt;li>&lt;strong>激勵值 (Activations):&lt;/strong> 為了計算反向傳播的梯度，必須將前向傳播時各層的輸出（中間狀態）保存在記憶體中。這與批次大小和序列長度有很強的相關性，會變得非常巨大。&lt;/li>
&lt;/ol>
&lt;p>總結來說，在使用標準Adam優化器的混合精度訓練（Mixed Precision Training）中，每個參數大約需要&lt;strong>16到20位元組&lt;/strong>（主權重4 + FP16權重2 + 梯度2 + 優化器8 + α）的記憶體。&lt;/p>
$$ M_{train\_param} \approx P \times 16 \text{ bytes} $$&lt;p>要訓練7B（70億參數）的模型，光是參數相關的記憶體就需要 $7B \times 16 = 112 \text{ GB}$，再加上激勵值，計算起來將需要超過140GB的VRAM。要在24GB的VRAM上執行此操作，必須採用下一章將要解說的強大最佳化技術。&lt;/p>
&lt;hr>
&lt;h1 id="2-推論時的vram節省技巧">2. 推論時的VRAM節省技巧
&lt;/h1>&lt;p>為了在推論時運行巨大的模型，目前已經開發了許多跨越硬體界限的軟體技術。&lt;/p>
&lt;h2 id="21-cpu卸載cpu-offloading與模型層分割">2.1 CPU卸載（CPU Offloading）與模型層分割
&lt;/h2>&lt;p>當巨大的模型無法完全裝入單一或多個GPU時，將模型的一部分配置到系統記憶體（CPU RAM）中，並在需要時才傳輸到GPU進行計算的手法稱為&lt;strong>CPU卸載（CPU Offloading）&lt;/strong>。&lt;code>llama.cpp&lt;/code>和Hugging Face的&lt;code>Accelerate&lt;/code>等工具都支援此功能。&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;系統記憶體 (DDR4 / DDR5)&amp;#34;] --&amp;gt; B[&amp;#34;GPU VRAM (GDDR6X)&amp;#34;]
B[&amp;#34;GPU VRAM (GDDR6X)&amp;#34;] --&amp;gt; C[&amp;#34;Tensor Cores (計算)&amp;#34;]
subgraph &amp;#34;層分割與卸載&amp;#34;
D[&amp;#34;較低層 1-15 (GPU 釘選)&amp;#34;]
E[&amp;#34;較高層 16-32 (CPU 卸載)&amp;#34;]
end
E[&amp;#34;較高層 16-32 (CPU 卸載)&amp;#34;] -.-&amp;gt; B[&amp;#34;GPU VRAM (GDDR6X)&amp;#34;]
&lt;/pre>
&lt;p>&lt;strong>機制與挑戰:&lt;/strong>
由於Transformer模型採用層（Layer）串聯堆疊的結構，在某一層的計算完成之前，下一層的計算不會開始。利用這點，我們只將能夠裝入GPU的層（例如：第1到15層）常駐（釘選）在VRAM中，並將剩餘的層（第16到32層）放在容量大但速度慢的CPU RAM中。推論過程中，當第15層的計算完成後，會透過PCIe匯流排將第16層的權重從CPU傳輸（複製）到GPU，接著在GPU上執行計算。&lt;/p>
&lt;p>然而，&lt;strong>PCIe的頻寬（Bandwidth）會成為嚴重的瓶頸&lt;/strong>。PCIe 4.0 x16的理論最大頻寬為32GB/s（單向），與最新GPU的VRAM內部頻寬（例如RTX 4090的GDDR6X為1008GB/s，H100的HBM3更是超過3TB/s）相比，慢了兩個數量級，因此若過度依賴CPU卸載，推論速度（Tokens per Second）將會急劇下降。
為了將速度降低的程度降至最低，實務上的重點在於盡可能將更多的層載入GPU（最大化GPU Layers），並將卸載的層數減至最少。&lt;/p>
&lt;h2 id="22-kv-cache量化與pagedattention">2.2 KV Cache量化與PagedAttention
&lt;/h2>&lt;p>針對推論時消耗VRAM的元兇「KV Cache」，目前也有兩種強大的最佳化技術。&lt;/p>
&lt;p>&lt;strong>1. KV Cache量化 (KV Cache Quantization):&lt;/strong>
不只是模型的權重，這個方法將執行時動態生成的KV Cache本身，也以INT8、INT4甚至FP8進行量化後再儲存於VRAM中。藉此可將KV Cache的大小縮減至二分之一或四分之一。最新的推論引擎（如vLLM或llama.cpp）已經內建了這個功能，能在將精確度下降幅度降至最低的同時，大幅節省VRAM。&lt;/p>
&lt;p>&lt;strong>2. PagedAttention:&lt;/strong>
vLLM這個推論引擎導入的&lt;strong>PagedAttention&lt;/strong>，是將作業系統虛擬記憶體的「分頁（Paging）」概念應用於KV Cache。在傳統的推論引擎中，會根據設定的最大序列長度，預先分配（Pre-allocation）連續的VRAM空間。因此，當實際輸入較短時，就會產生碎片化（Fragmentation）或浪費未使用的記憶體，有時甚至會浪費掉60%以上的VRAM。&lt;/p>
&lt;p>PagedAttention會將KV Cache分割成固定大小的區塊（Page），並允許將它們分散儲存在不連續的實體記憶體空間中。這樣一來，記憶體的浪費幾乎降至零（僅限於內部碎片），即使在相同的VRAM容量下，也能夠大幅提升批次大小。&lt;/p>
&lt;pre class="mermaid">
graph LR
A[&amp;#34;邏輯 KV Cache&amp;#34;] --&amp;gt; B[&amp;#34;實體 VRAM 區塊&amp;#34;]
A1[&amp;#34;Token 1, 2, 3, 4&amp;#34;] --&amp;gt; B3[&amp;#34;區塊 3 (已分配)&amp;#34;]
A2[&amp;#34;Token 5, 6, 7, 8&amp;#34;] --&amp;gt; B1[&amp;#34;區塊 1 (已分配)&amp;#34;]
A3[&amp;#34;未來的 Tokens...&amp;#34;] -.-&amp;gt; B2[&amp;#34;區塊 2 (可用)&amp;#34;]
&lt;/pre>
&lt;h2 id="23-flashattention打破注意力計算的記憶體複雜度">2.3 FlashAttention：打破注意力計算的記憶體複雜度
&lt;/h2>&lt;p>VRAM不足的問題，不僅來自於儲存資料所需的記憶體量，還因為計算過程中的「暫存工作區間」不足所引起。標準Transformer的自我注意力（Self-Attention）機制，針對序列長度 $N$，必須在VRAM上具體化（Materialize）出一個 $N \times N$ 的巨大注意力矩陣。這會讓記憶體複雜度變成 $O(N^2)$，成為長上下文中發生OOM的主因。&lt;/p>
&lt;p>解決這個問題的技術就是&lt;strong>FlashAttention&lt;/strong>（及其後續的FlashAttention-2, 3）。
FlashAttention是一種有意識地配合GPU硬體架構（巨大但慢速的HBM，與極小但超高速的SRAM所構成的階層結構）而設計的演算法。它利用稱為平鋪（Tiling）的手法，將資料分塊載入SRAM，並在其中完成注意力計算，徹底避免了將 $N \times N$ 的矩陣寫入HBM（VRAM）的過程。&lt;/p>
&lt;p>透過這項技術，注意力層的記憶體複雜度從 $O(N^2)$ 急劇下降到 $O(N)$（與序列長度成正比），從而大幅放寬了上下文長度的限制。&lt;/p>
&lt;h2 id="24-統一記憶體unified-memory的崛起與apple-silicon">2.4 統一記憶體（Unified Memory）的崛起與Apple Silicon
&lt;/h2>&lt;p>從PC架構的根本來解決這個問題的，是Apple Silicon（M1/M2/M3/M4系列的Max或Ultra），以及部分最新APU（如AMD Strix Point等）所採用的&lt;strong>統一記憶體架構（Unified Memory Architecture: UMA）&lt;/strong>。&lt;/p>
&lt;p>在這些架構中，主機板上的CPU和GPU共享完全相同的實體記憶體（例如高達192GB的LPDDR5）。因此，物理上根本不存在「從CPU到GPU透過PCIe進行慢速資料傳輸」的概念。&lt;/p>
&lt;pre class="mermaid">
graph TD
subgraph &amp;#34;統一記憶體架構 (如 Apple Silicon)&amp;#34;
A[&amp;#34;CPU 核心&amp;#34;] &amp;lt;--&amp;gt; C[&amp;#34;共享記憶體控制器&amp;#34;]
B[&amp;#34;GPU 核心 / 神經引擎&amp;#34;] &amp;lt;--&amp;gt; C[&amp;#34;共享記憶體控制器&amp;#34;]
C[&amp;#34;共享記憶體控制器&amp;#34;] &amp;lt;--&amp;gt; D[&amp;#34;統一記憶體池 (如 192GB)&amp;#34;]
end
&lt;/pre>
&lt;p>這個架構最大的優勢在於，不存在VRAM這樣明確的界限，系統記憶體的幾乎整個區域都可以直接用來載入巨大的LLM。如果是一台擁有192GB統一記憶體的Mac Studio，就可以將70B等級或更巨大的模型（例如Grok-1等）在不經過量化的情況下載入單一設備中，並進行高速推論。以M2 Ultra為例，記憶體存取頻寬也達到了800GB/s，足以媲美消費級獨立顯示卡。這是一種從硬體層面解決「記憶體容量」與「頻寬」兩難的極其強大的方法。&lt;/p>
&lt;hr>
&lt;h1 id="3-訓練微調時的vram節省技巧">3. 訓練（微調）時的VRAM節省技巧
&lt;/h1>&lt;p>在要求比推論更多VRAM的訓練（Training）階段，也出現了許多突破。為了在有限資源下進行微調，結合以下技術是不可或缺的。&lt;/p>
&lt;h2 id="31-梯度檢查點gradient-checkpointing">3.1 梯度檢查點（Gradient Checkpointing）
&lt;/h2>&lt;p>在深度學習的反向傳播（Backward Pass）中，為了計算梯度，必須將前向傳播（Forward Pass）中所有層的中間輸出（Activations）保存在記憶體中。當序列長度或批次大小變大時，這些激勵值記憶體就會開始佔據主導地位。&lt;/p>
&lt;p>**梯度檢查點（Gradient Checkpointing / Activation Recomputation）**是一種利用記憶體容量與計算時間（Compute）進行權衡的天才技巧。
它不將所有中間輸出保存在記憶體中，而是只保存特定層（檢查點）的輸出。當反向傳播需要未被檢查點保存的中間數值時，&lt;strong>就會從已保存的最近檢查點開始，重新計算一次前向傳播以還原該數值&lt;/strong>。&lt;/p>
&lt;p>雖然計算量會增加約20%至30%，整體的訓練時間會拉長，但可以將激勵值所消耗的VRAM從 $O(N)$（$N$為層數）急劇減少至 $O(\sqrt{N})$。在目前大規模模型的訓練中，這可以說是沒有它就無法開始的必要設定項目。&lt;/p>
&lt;h2 id="32-lora-與-qlora-low-rank-adaptation">3.2 LoRA 與 QLoRA (Low-Rank Adaptation)
&lt;/h2>&lt;p>從根本上解決VRAM不足問題的核心技術，是PEFT（Parameter-Efficient Fine-Tuning）的代表作：&lt;strong>LoRA&lt;/strong>。&lt;/p>
&lt;p>它將模型原本巨大的權重矩陣 $W_0 \in \mathbb{R}^{d \times k}$ 凍結（Frozen）且不參與訓練。取而代之的是，並行導入兩個非常小、低秩（Low-Rank）的矩陣 $A \in \mathbb{R}^{r \times k}$ 和 $B \in \mathbb{R}^{d \times r}$，並且只訓練這兩個矩陣 $A$ 和 $B$（這裡的秩 $r$ 是一個極小的值，滿足 $r \ll d, k$）。&lt;/p>
$$ W_{adapted} = W_0 + \Delta W = W_0 + B A $$&lt;p>這樣一來，需要訓練的參數數量將變成原本的不到1%（有時甚至不到0.1%），隨之而來的是，原本大量消耗記憶體的「梯度」和「優化器狀態」也急劇減少到不到1%。&lt;/p>
&lt;p>更進一步將其發揮到極致的是&lt;strong>QLoRA (Quantized LoRA)&lt;/strong>。
在QLoRA中，基礎模型的權重 $W_0$ 被極度量化為4位元（NF4: NormalFloat4格式）並載入VRAM。同時，為了保持計算精度，LoRA的小型矩陣 $A, B$ 則以BF16（16位元）進行訓練。
透過4位元量化，將基礎模型的VRAM大小縮小為原來的四分之一，並搭配使用稱為&lt;strong>Paged Optimizers&lt;/strong>（分頁優化器）的技術，在VRAM快要耗盡時，會暫時將優化器的狀態自動退避（卸載）到CPU RAM。這樣一來，即使是單張24GB VRAM（如RTX 4090等）的GPU，也能對Llama 3 70B等超大模型進行微調。&lt;/p>
&lt;h2 id="33-deepspeed-zero-與-卸載技術">3.3 DeepSpeed ZeRO 與 卸載技術
&lt;/h2>&lt;p>在使用多個GPU（多GPU環境）的情況下，單純的資料平行化（Data Parallelism）無法解決VRAM的問題。因為每個GPU都會保留一份完整模型的副本，這無法突破個別VRAM容量的限制。&lt;/p>
&lt;p>由Microsoft開發的&lt;strong>DeepSpeed&lt;/strong>函式庫中的&lt;strong>ZeRO (Zero Redundancy Optimizer)&lt;/strong>，是一項將模型的參數、梯度和優化器狀態徹底分割（Shard）到多個GPU之間的技術。藉此可以將多個GPU的VRAM「總和」視為一個巨大的記憶體池來使用。&lt;/p>
&lt;pre class="mermaid">
graph TD
subgraph &amp;#34;ZeRO Stage 3 (參數分割)&amp;#34;
A[&amp;#34;GPU 0&amp;#34;] --&amp;gt; D[&amp;#34;分割區 0 (儲存 1/3 的權重/梯度/優化器)&amp;#34;]
B[&amp;#34;GPU 1&amp;#34;] --&amp;gt; E[&amp;#34;分割區 1 (儲存 1/3 的權重/梯度/優化器)&amp;#34;]
C[&amp;#34;GPU 2&amp;#34;] --&amp;gt; F[&amp;#34;分割區 2 (儲存 1/3 的權重/梯度/優化器)&amp;#34;]
end
D[&amp;#34;分割區 0 (儲存 1/3 的權重/梯度/優化器)&amp;#34;] &amp;lt;--&amp;gt; E[&amp;#34;分割區 1 (儲存 1/3 的權重/梯度/優化器)&amp;#34;]
E[&amp;#34;分割區 1 (儲存 1/3 的權重/梯度/優化器)&amp;#34;] &amp;lt;--&amp;gt; F[&amp;#34;分割區 2 (儲存 1/3 的權重/梯度/優化器)&amp;#34;]
&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>ZeRO Stage 1:&lt;/strong> 將優化器狀態分割到各個GPU&lt;/li>
&lt;li>&lt;strong>ZeRO Stage 2:&lt;/strong> 梯度也分割到各個GPU&lt;/li>
&lt;li>&lt;strong>ZeRO Stage 3:&lt;/strong> 模型參數（權重）本身也分割到各個GPU&lt;/li>
&lt;/ul>
&lt;p>此外，若使用 &lt;strong>ZeRO-Offload&lt;/strong> 功能，可以將被ZeRO分割的優化器狀態或梯度更新計算，不再由GPU執行，而是&lt;strong>卸載到CPU記憶體&lt;/strong>，由主機（Host）CPU來執行。藉此能將GPU VRAM的負擔減到最低，即使在有限的GPU環境中也能訓練巨大模型。雖然因為在CPU中進行計算並透過PCIe將結果傳回GPU會導致訓練速度下降，但這可以避免發生「因記憶體不足而導致訓練崩潰」的最糟情況。&lt;/p>
&lt;hr>
&lt;h1 id="4-實作範例hugging-face-accelerate-與-deepspeed">4. 實作範例：Hugging Face Accelerate 與 DeepSpeed
&lt;/h1>&lt;p>最後，我們將展示一個簡單的範例，說明如何在Python程式碼中實際實作CPU卸載和VRAM最佳化。&lt;/p>
&lt;h2 id="41-使用-hugging-face-device_mapauto-的自動卸載">4.1 使用 Hugging Face &lt;code>device_map=&amp;quot;auto&amp;quot;&lt;/code> 的自動卸載
&lt;/h2>&lt;p>當使用Hugging Face的&lt;code>transformers&lt;/code>和&lt;code>accelerate&lt;/code>函式庫載入模型時，它們會自動幫我們在GPU和CPU之間進行層分割。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">transformers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AutoTokenizer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;meta-llama/Llama-2-13b-hf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 透過 device_map=&amp;#34;auto&amp;#34;，無法裝入VRAM的部分會被卸載到CPU RAM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 透過 load_in_8bit=True 將權重進行8位元量化，進一步節省記憶體&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_pretrained&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">device_map&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;auto&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">load_in_8bit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">offload_folder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;offload_dir&amp;#34;&lt;/span> &lt;span class="c1"># 空間不足時甚至可以卸載到磁碟（SSD）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>執行這段程式碼時，背後的&lt;code>accelerate&lt;/code>函式庫會分析系統的VRAM和CPU RAM的可用容量，並以最佳的方式配置（Dispatch）所有的層。&lt;/p>
&lt;h2 id="42-deepspeed-的-cpu-卸載設定-zero-2">4.2 DeepSpeed 的 CPU 卸載設定 (ZeRO-2)
&lt;/h2>&lt;p>這是一個在訓練時啟用DeepSpeed CPU卸載的設定檔（JSON）範例。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fp16&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;enabled&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;zero_optimization&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;stage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;offload_optimizer&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;device&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;cpu&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;pin_memory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;allgather_partitions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;allgather_bucket_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">2e8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;overlap_comm&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;reduce_scatter&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;reduce_bucket_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">2e8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;contiguous_gradients&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;train_batch_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;gradient_accumulation_steps&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在這個設定中，透過將 &lt;code>offload_optimizer&lt;/code> 指定為 &lt;code>&amp;quot;cpu&amp;quot;&lt;/code>，可以讓大量消耗VRAM的優化器（如Adam等）狀態保留與更新計算在系統端的CPU上執行。這樣就能讓GPU的VRAM專心處理模型的前向/反向計算這個最重要的任務。透過設定 &lt;code>pin_memory: true&lt;/code> 可以防止分頁錯誤（Page Fault），盡可能加快CPU-GPU之間的PCIe傳輸。&lt;/p>
&lt;hr>
&lt;h1 id="總結">總結
&lt;/h1>&lt;p>在AI開發中，GPU記憶體不足（Out of Memory）是一個隨著模型規模擴大而將永遠伴隨開發者的課題。然而，透過適當組合本文解說的硬體（架構）的深度理解，以及軟體與演算法層面的最佳化技巧，即使是在看似不可能的本地環境下，也能夠推論或訓練巨大的模型。&lt;/p>
&lt;p>&lt;strong>推論時的對策總結：&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>量化 (INT4 / INT8 / FP8):&lt;/strong> 急劇壓縮模型本身的大小，減少VRAM的佔用量。&lt;/li>
&lt;li>&lt;strong>CPU卸載:&lt;/strong> 將無法裝入VRAM的層轉移到系統記憶體（須衡量PCIe頻寬導致的速度下降之取捨）。&lt;/li>
&lt;li>&lt;strong>KV Cache最佳化:&lt;/strong> 利用分頁（PagedAttention）、快取量化或FlashAttention來確保上下文長度（Context Length）。&lt;/li>
&lt;li>&lt;strong>活用統一記憶體:&lt;/strong> 活用Apple Silicon等UMA架構，將大容量記憶體直接用於推論。&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>訓練時的對策總結：&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>PEFT (LoRA / QLoRA):&lt;/strong> 限定訓練的參數，並將基礎模型量化至極限。&lt;/li>
&lt;li>&lt;strong>梯度檢查點 (Gradient Checkpointing):&lt;/strong> 放棄保留前向傳播的中間輸出，在反向傳播時重新計算，以計算時間換取VRAM消耗的降低。&lt;/li>
&lt;li>&lt;strong>ZeRO &amp;amp; CPU卸載 (DeepSpeed):&lt;/strong> 將優化器狀態或梯度在多個GPU間分割，或是卸載至CPU記憶體，藉以突破VRAM的極限。&lt;/li>
&lt;/ol>
&lt;p>善用這些高階技術，在有限的硬體資源中發揮出最大的AI開發效能吧。在這個日新月異的領域中，未來可以期待更多新的記憶體節省演算法出現。定期關注最新函式庫的動向，並將其導入實作中，將會是成功的關鍵。&lt;/p></description></item></channel></rss>