<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Zhongquan Zhou&#39;s Blog</title>
  
  <subtitle>Computer Science and Technology</subtitle>
  <link href="https://zhouzhq2021.github.io/atom.xml" rel="self"/>
  
  <link href="https://zhouzhq2021.github.io/"/>
  <updated>2026-07-30T04:18:20.517Z</updated>
  <id>https://zhouzhq2021.github.io/</id>
  
  <author>
    <name>Zhongquan Zhou</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>70B 大模型推理服务的容量规划与性能设计</title>
    <link href="https://zhouzhq2021.github.io/2026/07/30/llm-70b-capacity-planning/"/>
    <id>https://zhouzhq2021.github.io/2026/07/30/llm-70b-capacity-planning/</id>
    <published>2026-07-30T02:00:00.000Z</published>
    <updated>2026-07-30T04:18:20.517Z</updated>
    
    <content type="html"><![CDATA[<p>很多 AI Infra 面试题彼此看似独立：计算 KV Cache、解释张量并行、分析 Decode 瓶颈、选择量化方案、设计压测指标、排查 OOM。放到真实部署里，它们其实是同一道综合题的不同步骤。</p><p>本文设定一个具体任务：使用 8 张 H100 80GB 部署一个 70B、GQA、BF16 的 Decoder-only 模型，并满足在线聊天业务的延迟目标。我们不会寻找唯一答案，而是展示一套可以复用的决策过程。</p><p><img src="/images/posts/ai-infra-series/capacity-planning-flow.svg" alt="大模型推理服务容量规划闭环" /></p><p><em>图 1：容量规划不是一次显存除法，而是负载、资源、压测、归因和上线门禁构成的闭环。</em></p><p>这里的“容量”不是一个数字，而是一个约束向量：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>C</mi><mi>a</mi><mi>p</mi><mi>a</mi><mi>c</mi><mi>i</mi><mi>t</mi><mi>y</mi><mo>=</mo><mo stretchy="false">(</mo><mtext>显存</mtext><mo separator="true">,</mo><mtext>HBM 带宽</mtext><mo separator="true">,</mo><mtext>计算吞吐</mtext><mo separator="true">,</mo><mtext>互联</mtext><mo separator="true">,</mo><mtext>并发</mtext><mo separator="true">,</mo><mtext>SLO</mtext><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">Capacity=(\text{显存},\text{HBM 带宽},\text{计算吞吐},\text{互联},\text{并发},\text{SLO})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8777699999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord mathnormal">a</span><span class="mord mathnormal">p</span><span class="mord mathnormal">a</span><span class="mord mathnormal">c</span><span class="mord mathnormal">i</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord text"><span class="mord cjk_fallback">显存</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord text"><span class="mord">HBM </span><span class="mord cjk_fallback">带宽</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord text"><span class="mord cjk_fallback">计算吞吐</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord text"><span class="mord cjk_fallback">互联</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord text"><span class="mord cjk_fallback">并发</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord text"><span class="mord">SLO</span></span><span class="mclose">)</span></span></span></span></span></p><p>某个方案可能容纳更多请求，却因为 TPOT 超标而没有更高有效容量；另一个方案可能单请求延迟很低，却因 KV Cache 太小无法承受流量峰值。本文后续所有比较都以 Goodput 为最终尺度，而不是孤立的显存占用或 Tokens/s。</p><h2 id="一-需求不能只写部署-70b"><a class="markdownIt-Anchor" href="#一-需求不能只写部署-70b"></a> 一、需求不能只写“部署 70B”</h2><p>假设业务提供以下信息：</p><table><thead><tr><th>项目</th><th>假设值</th></tr></thead><tbody><tr><td>模型</td><td>70B Decoder-only，80 层</td></tr><tr><td>隐藏维度</td><td>8192</td></tr><tr><td>Query/KV Heads</td><td>64 / 8</td></tr><tr><td>Head Dimension</td><td>128</td></tr><tr><td>权重精度</td><td>BF16</td></tr><tr><td>GPU</td><td>8 × H100 80GB，单机 NVLink</td></tr><tr><td>Prompt P50/P99</td><td>2K / 16K Token</td></tr><tr><td>平均输出</td><td>500 Token</td></tr><tr><td>TTFT SLO</td><td>P95 小于 2 秒，示例目标</td></tr><tr><td>TPOT SLO</td><td>P95 小于 60 ms，示例目标</td></tr></tbody></table><p>这些 SLO 只是演示。真实项目应根据交互产品、离线任务或 Agent 工作流重新定义。</p><p>还需要补齐：</p><ul><li>到达率和峰值并发；</li><li>Prompt 长度的完整分布；</li><li>系统 Prompt 是否大量重复；</li><li>是否要求多模型或 Multi-LoRA；</li><li>单机故障时是否允许降级；</li><li>成本、功耗和可用区约束。</li></ul><h3 id="11-从到达率估算活跃请求数"><a class="markdownIt-Anchor" href="#11-从到达率估算活跃请求数"></a> 1.1 从到达率估算活跃请求数</h3><p>容量规划经常直接假设“并发 64”，却没有解释这个数字来自哪里。稳定系统中可以先用 Little’s Law 建立近似：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>C</mi><mo>≈</mo><mi>λ</mi><mo>×</mo><mi>E</mi><mo stretchy="false">[</mo><mi>T</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">C\approx\lambda\times E[T]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord mathnormal">λ</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mopen">[</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mclose">]</span></span></span></span></span></p><p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi></mrow><annotation encoding="application/x-tex">\lambda</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">λ</span></span></span></span> 是每秒到达请求数，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi><mo stretchy="false">[</mo><mi>T</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">E[T]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mopen">[</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mclose">]</span></span></span></span> 是请求从进入到完成的平均驻留时间，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>C</mi></mrow><annotation encoding="application/x-tex">C</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span></span></span></span> 是系统中的平均活跃请求数。例如到达率 8 req/s、平均完成时间 6s，平均活跃请求约为 48。突发流量和长尾会让瞬时值更高，因此还需结合 P95/P99 和排队上限。</p><p>把并发与业务流量联系起来后，KV Cache 预算才不只是人为选择的压测参数。</p><h2 id="二-第一步权重能否装下"><a class="markdownIt-Anchor" href="#二-第一步权重能否装下"></a> 二、第一步：权重能否装下</h2><p><img src="/images/posts/ai-infra-series/memory-ledger.svg" alt="训练与推理显存状态模型" /></p><p><em>图 2：部署容量规划只使用右侧推理账本，但量化和并行选择仍要明确权重、KV 与运行时空间的边界。</em></p><p>70B BF16 权重的理论大小约为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>70</mn><mo>×</mo><mn>1</mn><msup><mn>0</mn><mn>9</mn></msup><mo>×</mo><mn>2</mn><mtext> Bytes</mtext><mo>≈</mo><mn>140</mn><mtext> GB</mtext></mrow><annotation encoding="application/x-tex">70\times10^9\times2\text{ Bytes}\approx140\text{ GB}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">7</span><span class="mord">0</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.9474379999999999em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">9</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.8777699999999999em;vertical-align:-0.19444em;"></span><span class="mord">2</span><span class="mord text"><span class="mord"> Bytes</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">4</span><span class="mord">0</span><span class="mord text"><span class="mord"> GB</span></span></span></span></span></span></p><p>使用 TP=8 时，理想情况下每张卡约承担 17.5GB 权重。实际还会包含：</p><ul><li>未均匀切分或复制的参数；</li><li>CUDA Context；</li><li>NCCL Buffer；</li><li>Attention 和 GEMM Workspace；</li><li>CUDA Graph；</li><li>KV Cache；</li><li>框架分配器保留空间。</li></ul><p>因此，权重能装下不是瓶颈终点，而只是说明 TP=8 在容量上可行。</p><h3 id="21-是否需要-int8-或-int4"><a class="markdownIt-Anchor" href="#21-是否需要-int8-或-int4"></a> 2.1 是否需要 INT8 或 INT4</h3><p>如果 BF16 已经为 KV Cache 留出足够空间，量化不一定是第一步。Weight-only 量化可能减少 Decode 读取权重的带宽并提高单机容量，但要确认：</p><ul><li>目标 GPU 和推理框架是否有成熟 Kernel；</li><li>模型精度是否满足业务要求；</li><li>Prefill 是否因反量化或 Kernel 形状变慢；</li><li>TP 下量化元数据如何切分；</li><li>长上下文业务的主要瓶颈究竟是权重还是 KV Cache。</li></ul><p>技术选型应该由 Benchmark 决定，而不是由“位宽更低”直接决定。</p><h2 id="三-第二步kv-cache-才是并发上限的关键"><a class="markdownIt-Anchor" href="#三-第二步kv-cache-才是并发上限的关键"></a> 三、第二步：KV Cache 才是并发上限的关键</h2><p>KV Cache 每 Token 大小为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>2</mn><mi>L</mi><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub><msub><mi>d</mi><mi>h</mi></msub><mi>b</mi></mrow><annotation encoding="application/x-tex">2LH_{kv}d_hb</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord">2</span><span class="mord mathnormal">L</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">b</span></span></span></span></span></p><p>代入 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi><mo>=</mo><mn>80</mn></mrow><annotation encoding="application/x-tex">L=80</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal">L</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">8</span><span class="mord">0</span></span></span></span>、<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub><mo>=</mo><mn>8</mn></mrow><annotation encoding="application/x-tex">H_{kv}=8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">8</span></span></span></span>、<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>d</mi><mi>h</mi></msub><mo>=</mo><mn>128</mn></mrow><annotation encoding="application/x-tex">d_h=128</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">2</span><span class="mord">8</span></span></span></span>、BF16 的 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi><mo>=</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">b=2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span></span></span></span>：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>2</mn><mo>×</mo><mn>80</mn><mo>×</mo><mn>8</mn><mo>×</mo><mn>128</mn><mo>×</mo><mn>2</mn><mo>=</mo><mn>327680</mn><mtext> Bytes</mtext></mrow><annotation encoding="application/x-tex">2\times80\times8\times128\times2=327680\text{ Bytes}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">8</span><span class="mord">0</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">2</span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8777699999999999em;vertical-align:-0.19444em;"></span><span class="mord">3</span><span class="mord">2</span><span class="mord">7</span><span class="mord">6</span><span class="mord">8</span><span class="mord">0</span><span class="mord text"><span class="mord"> Bytes</span></span></span></span></span></span></p><p>即整个模型每个 Token 约 320 KiB KV Cache。</p><p>如果 KV Head 能随 TP=8 均匀切分，每卡约承担八分之一，也就是约 40 KiB/Token。具体布局仍应以引擎和 Attention Backend 的实现为准。</p><h3 id="31-平均负载和最坏负载不同"><a class="markdownIt-Anchor" href="#31-平均负载和最坏负载不同"></a> 3.1 平均负载和最坏负载不同</h3><p>若 64 个并发请求平均已经缓存 2500 Token，总 KV Cache 约为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>64</mn><mo>×</mo><mn>2500</mn><mo>×</mo><mn>320</mn><mtext> KiB</mtext><mo>≈</mo><mn>48.8</mn><mtext> GiB</mtext></mrow><annotation encoding="application/x-tex">64\times2500\times320\text{ KiB}\approx48.8\text{ GiB}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">6</span><span class="mord">4</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">5</span><span class="mord">0</span><span class="mord">0</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">3</span><span class="mord">2</span><span class="mord">0</span><span class="mord text"><span class="mord"> KiB</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">4</span><span class="mord">8</span><span class="mord">.</span><span class="mord">8</span><span class="mord text"><span class="mord"> GiB</span></span></span></span></span></span></p><p>分到 8 张卡约 6.1 GiB/卡，看起来很轻松。</p><p>但如果 64 个请求同时接近 16K Prompt，并继续输出 500 Token，总 KV Cache 会超过 300 GiB，单卡分片也可能超过 40 GiB。再考虑权重、Workspace 和运行时空间，容量压力会明显上升。</p><p>因此不能用 P50 Prompt 推导最大并发，也不能简单用 P99 长度假设所有请求同时达到最坏值。需要用真实分布做 Trace Replay 或概率模型。</p><h2 id="四-第三步确定并行方式"><a class="markdownIt-Anchor" href="#四-第三步确定并行方式"></a> 四、第三步：确定并行方式</h2><p><img src="/images/posts/ai-infra-series/parallel-topology.svg" alt="两节点多维并行拓扑示意" /></p><p><em>图 3：本文场景是单机 8 卡，TP 通信位于 NVLink 域；扩展到多机时，应避免把高频 TP Collective 直接放到慢速跨机链路。</em></p><p>单机 8 卡 NVLink 环境下，TP=8 是直观起点：</p><ul><li>权重均匀分片；</li><li>层内通信留在高速 NVLink 域；</li><li>不引入 Pipeline Bubble；</li><li>一个模型副本可以使用全部 GPU。</li></ul><p>但它也有代价：</p><ul><li>每个 Transformer 层都有 TP 通信；</li><li>小 Batch 下通信和 Launch 占比可能较高；</li><li>单个副本占用全部 GPU，故障域较大；</li><li>扩容粒度是整套 8 卡实例。</li></ul><p>如果量化后模型可以用 TP=4，可能在一台 8 卡机器上部署两个副本，以数据并行方式处理不同请求。此时总吞吐、尾延迟和故障隔离可能更好，但单请求可用 KV Cache、单副本最长上下文和量化精度需要重新评估。</p><p>所以要比较的不是“TP=4 还是 TP=8 哪个更先进”，而是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">一个 TP=8 大副本</span><br><span class="line">vs</span><br><span class="line">两个 TP=4 小副本</span><br></pre></td></tr></table></figure><p>在真实请求分布下，哪个获得更高 Goodput。</p><h2 id="五-第四步配置-kv-cache-与调度器"><a class="markdownIt-Anchor" href="#五-第四步配置-kv-cache-与调度器"></a> 五、第四步：配置 KV Cache 与调度器</h2><h3 id="51-gpu_memory_utilization"><a class="markdownIt-Anchor" href="#51-gpu_memory_utilization"></a> 5.1 <code>gpu_memory_utilization</code></h3><p>这个参数决定引擎可以为权重、KV Cache 和运行时使用多少显存。设得太低会限制并发，设得太高则可能在 CUDA Graph、临时 Workspace 或长请求下 OOM。</p><p>建议从保守值开始，用压测观察：</p><ul><li>实际可分配 KV Block 数；</li><li>峰值显存；</li><li>抢占次数；</li><li>OOM 和碎片情况。</li></ul><h3 id="52-max_model_len"><a class="markdownIt-Anchor" href="#52-max_model_len"></a> 5.2 <code>max_model_len</code></h3><p>如果业务 P99 只有 16K，却把最大序列长度配置为模型理论支持的 128K，可能让引擎为极少出现的请求保留过多规划空间或降低可接纳并发。</p><p>更合理的方式是按产品能力设置上限，并为超长请求设计独立队列、限流或专用实例。</p><h3 id="53-block-size"><a class="markdownIt-Anchor" href="#53-block-size"></a> 5.3 Block Size</h3><ul><li>Block 过大：尾块浪费和细粒度调度能力下降；</li><li>Block 过小：块表、哈希和管理开销增加。</li></ul><p>不要只测固定长度。不同 Prompt 分布对最佳 Block Size 的偏好可能不同。</p><h2 id="六-第五步哪些功能值得开启"><a class="markdownIt-Anchor" href="#六-第五步哪些功能值得开启"></a> 六、第五步：哪些功能值得开启</h2><h3 id="61-prefix-cache"><a class="markdownIt-Anchor" href="#61-prefix-cache"></a> 6.1 Prefix Cache</h3><p>如果大量请求共享系统 Prompt、Few-shot 示例或固定文档前缀，Prefix Cache 可以跳过重复 Prefill，明显降低 TTFT。</p><p>如果 Prompt 开头包含随机 ID、时间戳或用户特定内容，Token 前缀很早就分叉，命中率可能很低。此时缓存会占用显存，却没有带来足够收益。</p><p>开启前至少统计：</p><ul><li>可复用前缀长度；</li><li>请求之间的 Token 级重复率；</li><li>缓存命中率；</li><li>命中后节省的 Prefill 时间；</li><li>缓存块占用和淘汰频率。</li></ul><h3 id="62-chunked-prefill"><a class="markdownIt-Anchor" href="#62-chunked-prefill"></a> 6.2 Chunked Prefill</h3><p>P99 Prompt 达到 16K 时，长 Prefill 很可能干扰正在 Decode 的短请求。Chunked Prefill 可以改善 TPOT 稳定性，但在相同调度预算下可能让长请求 TTFT 增加。</p><p>是否开启应由 SLO 决定：</p><ul><li>聊天业务重视输出流畅度，通常更愿意保护 TPOT；</li><li>离线批处理只关心吞吐，未必需要同样的切块策略；</li><li>超长上下文可以进入独立队列，避免污染普通请求。</li></ul><h3 id="63-cuda-graph-与-torchcompile"><a class="markdownIt-Anchor" href="#63-cuda-graph-与-torchcompile"></a> 6.3 CUDA Graph 与 <code>torch.compile</code></h3><p>Decode 中 Batch Shape 相对稳定时，CUDA Graph 可以减少 CPU Launch 开销。需要同时评估：</p><ul><li>捕获 Shape 覆盖范围；</li><li>Graph 额外占用的显存；</li><li>冷启动时间；</li><li>动态控制流和调试需求；</li><li>捕获外算子是否形成新的同步点。</li></ul><h3 id="64-speculative-decoding"><a class="markdownIt-Anchor" href="#64-speculative-decoding"></a> 6.4 Speculative Decoding</h3><p>投机解码使用 Draft 模型或 Self-Draft 结构提出多个候选，再由目标模型并行验证。收益依赖接受率：</p><ul><li>Draft 太弱：候选经常被拒绝；</li><li>Draft 太大：草稿本身开销过高；</li><li>目标模型 Batch 已经很大：验证收益可能被调度和内存压力抵消。</li></ul><p>它更适合作为单独 Benchmark 变量，而不是默认开启。</p><h2 id="七-第六步是否需要-prefilldecode-解耦"><a class="markdownIt-Anchor" href="#七-第六步是否需要-prefilldecode-解耦"></a> 七、第六步：是否需要 Prefill/Decode 解耦</h2><p>混合部署时，Prefill 和 Decode 共享 GPU：</p><ul><li>Prefill 偏 Compute Bound；</li><li>Decode 偏 Memory Bound；</li><li>长 Prefill 会抬高 Decode TPOT；</li><li>两类阶段的最优 Batch 和并行配置不同。</li></ul><p>PD 解耦将它们放在不同 Worker，并通过网络传输 KV Cache。它带来新的成本：</p><ul><li>KV 传输带宽和延迟；</li><li>Worker 比例规划；</li><li>请求路由；</li><li>故障恢复；</li><li>KV 一致性和生命周期管理。</li></ul><p>只有当混部的相互干扰已经成为主要瓶颈，并且业务规模足以摊销复杂度时，PD 解耦才值得采用。单机 8 卡的第一版系统通常应先把混部调度做好。</p><h2 id="八-第七步设计一套可信-benchmark"><a class="markdownIt-Anchor" href="#八-第七步设计一套可信-benchmark"></a> 八、第七步：设计一套可信 Benchmark</h2><h3 id="81-不要只测固定-prompt"><a class="markdownIt-Anchor" href="#81-不要只测固定-prompt"></a> 8.1 不要只测固定 Prompt</h3><p>至少覆盖：</p><ul><li>短输入、短输出；</li><li>长输入、短输出；</li><li>短输入、长输出；</li><li>P99 长上下文；</li><li>高 Prefix 重复率；</li><li>无 Prefix 重复；</li><li>稳态到达和突发流量。</li></ul><h3 id="82-指标分层"><a class="markdownIt-Anchor" href="#82-指标分层"></a> 8.2 指标分层</h3><table><thead><tr><th>层级</th><th>指标</th></tr></thead><tbody><tr><td>用户体验</td><td>TTFT、TPOT、E2E、P95/P99</td></tr><tr><td>系统吞吐</td><td>Request/s、Input/Output Tokens/s</td></tr><tr><td>SLO</td><td>Goodput、超时率、拒绝率</td></tr><tr><td>GPU</td><td>SM、HBM、显存、Kernel Gap</td></tr><tr><td>调度</td><td>Queue Time、Batch Size、抢占次数</td></tr><tr><td>Cache</td><td>Prefix 命中率、KV Block 使用率</td></tr></tbody></table><h3 id="83-性能回归门禁"><a class="markdownIt-Anchor" href="#83-性能回归门禁"></a> 8.3 性能回归门禁</h3><p>每次修改引擎版本、模型权重、CUDA、Attention Backend 或量化方式后，自动执行相同负载集。门禁不应只限制平均吞吐，还应限制：</p><ul><li>P95/P99 TTFT 和 TPOT；</li><li>峰值显存；</li><li>错误率和 OOM；</li><li>输出正确性或精度回归；</li><li>长上下文稳定性。</li></ul><h2 id="九-常见故障如何定位"><a class="markdownIt-Anchor" href="#九-常见故障如何定位"></a> 九、常见故障如何定位</h2><h3 id="91-启动时-oom"><a class="markdownIt-Anchor" href="#91-启动时-oom"></a> 9.1 启动时 OOM</h3><p>优先检查权重精度、TP 数量、最大序列长度、CUDA Graph 捕获和显存利用率，而不是直接降低 Batch。</p><h3 id="92-运行一段时间后-oom"><a class="markdownIt-Anchor" href="#92-运行一段时间后-oom"></a> 9.2 运行一段时间后 OOM</h3><p>重点检查 KV Cache 增长、长请求比例、缓存块占用、抢占策略和请求是否正确释放。</p><h3 id="93-gpu-利用率低"><a class="markdownIt-Anchor" href="#93-gpu-利用率低"></a> 9.3 GPU 利用率低</h3><p>可能原因包括：</p><ul><li>请求不足，Batch 太小；</li><li>CPU 调度或 Tokenization 跟不上；</li><li>Kernel Launch Gap；</li><li>TP 通信等待；</li><li>大量小 Kernel；</li><li>请求长度差异导致调度低效。</li></ul><p>先用 Nsight Systems 区分 GPU 空闲和 GPU 内部低效，再决定是否进入 Nsight Compute。</p><h3 id="94-吞吐很高但用户仍觉得慢"><a class="markdownIt-Anchor" href="#94-吞吐很高但用户仍觉得慢"></a> 9.4 吞吐很高但用户仍觉得慢</h3><p>平均 Tokens/s 可能掩盖排队和尾延迟。检查 P99 TTFT、TPOT 和 Goodput，并按 Prompt 长度和并发分桶。</p><h3 id="95-nccl-hang"><a class="markdownIt-Anchor" href="#95-nccl-hang"></a> 9.5 NCCL Hang</h3><p>检查所有 Rank 是否按相同顺序进入集合通信、进程是否异常退出、网卡与拓扑是否正确，然后使用 <code>NCCL_DEBUG=INFO</code> 和 <code>nccl-tests</code> 分离代码与基础设施问题。</p><h2 id="十-方案决策链与基线配置"><a class="markdownIt-Anchor" href="#十-方案决策链与基线配置"></a> 十、方案决策链与基线配置</h2><p>一个完整方案不应该只报参数，而应呈现决策链：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">业务负载和 SLO</span><br><span class="line">       ↓</span><br><span class="line">模型权重与 KV 显存账本</span><br><span class="line">       ↓</span><br><span class="line">TP/副本数/量化选择</span><br><span class="line">       ↓</span><br><span class="line">KV Block 与调度配置</span><br><span class="line">       ↓</span><br><span class="line">Prefix、Chunked Prefill、CUDA Graph</span><br><span class="line">       ↓</span><br><span class="line">真实分布压测</span><br><span class="line">       ↓</span><br><span class="line">Goodput 与成本比较</span><br><span class="line">       ↓</span><br><span class="line">上线后的监控和回归门禁</span><br></pre></td></tr></table></figure><p>对于本文场景，一个合理的第一版基线是：</p><ul><li>使用 BF16、TP=8 建立正确性和性能基线；</li><li>使用 PagedAttention 和 Continuous Batching；</li><li>将最大上下文限制在业务真实需要范围；</li><li>对 16K 长 Prompt 开启或评估 Chunked Prefill；</li><li>Prefix Cache 由实际前缀命中率决定；</li><li>用真实请求分布寻找 Token Budget 和并发上限；</li><li>再与量化后 TP=4、双副本方案比较 Goodput；</li><li>只有混部干扰无法满足 SLO 时，再评估 PD 解耦。</li></ul><h2 id="十一-技术-qa"><a class="markdownIt-Anchor" href="#十一-技术-qa"></a> 十一、技术 Q&amp;A</h2><h3 id="q1tp8-后70b-bf16-权重是否严格变成每卡-175gb"><a class="markdownIt-Anchor" href="#q1tp8-后70b-bf16-权重是否严格变成每卡-175gb"></a> Q1：TP=8 后，70B BF16 权重是否严格变成每卡 17.5GB？</h3><p>17.5GB 只是参数均匀分片的理论值。Embedding、LM Head、Norm、小参数和量化元数据不一定都按同一方式分片；某些实现还会复制参数，或为集合通信准备连续 Buffer。容量规划应先根据引擎的实际分片规则计算下界，再用模型启动后的逐卡显存数据核对分片不均衡和额外状态。</p><h3 id="q2为什么不能用-p50-prompt-长度推导最大并发"><a class="markdownIt-Anchor" href="#q2为什么不能用-p50-prompt-长度推导最大并发"></a> Q2：为什么不能用 P50 Prompt 长度推导最大并发？</h3><p>KV Cache 随每个活跃请求的实际缓存长度增长。按 P50 规划会在多个长请求同时出现时低估显存；假设所有请求都是 P99 又可能严重过度配置。更合理的方法是回放真实长度与到达 Trace，或按长度分桶建立概率模型，并为极端长请求设置独立队列、并发上限和 Token 配额。</p><h3 id="q3weight-only-量化为什么更可能加速-decode"><a class="markdownIt-Anchor" href="#q3weight-only-量化为什么更可能加速-decode"></a> Q3：Weight-only 量化为什么更可能加速 Decode？</h3><p>Decode 的 Token 维很小，常受每轮读取大体量权重的 HBM 带宽限制。INT8/INT4 减少权重 Bytes，在具备高效融合反量化 Kernel 时可提高算术强度。Prefill 的大 GEMM 更接近 Compute Bound，其收益取决于低精度 Tensor Core、反量化开销和具体 Shape，因此加速比通常不会简单等于权重压缩比。</p><h3 id="q4何时应引入-chunked-prefill-或-pd-解耦压测结果又如何转化为限流配置"><a class="markdownIt-Anchor" href="#q4何时应引入-chunked-prefill-或-pd-解耦压测结果又如何转化为限流配置"></a> Q4：何时应引入 Chunked Prefill 或 PD 解耦，压测结果又如何转化为限流配置？</h3><p>长 Prefill 阻塞 Decode、导致 TPOT 抖动时，可先用 Chunked Prefill 把计算切片；它会以潜在的 TTFT 增长换取更平滑的 Decode。当混部干扰经过统一调度仍无法满足 SLO，且流量规模足以支撑独立 Worker 池时，才值得用 PD 解耦换取阶段级隔离。压测时应找到满足 TTFT/TPOT SLO 的最高稳定到达率，以单副本 Goodput 乘以安全系数和可用副本数得到服务容量，再按 Prompt 长度或估算 Token 工作量实施 Admission Control；当排队时间、KV Block 水位或 P99 越过拐点时，应排队、拒绝或路由到长上下文池，而不是等到 OOM。</p><h2 id="十二-系列导航"><a class="markdownIt-Anchor" href="#十二-系列导航"></a> 十二、系列导航</h2><p>上一篇：<a href="/2026/07/28/numerical-stability-to-flashattention/" title="Softmax 数值稳定性与 IO-Aware Attention：从在线归一化到 FlashAttention">Softmax 数值稳定性与 IO-Aware Attention</a></p><p>回到开篇：<a href="/2026/07/18/ai-infra-memory-ledger/" title="大模型训练与推理的显存模型：参数、优化器状态与 KV Cache">大模型训练与推理的显存模型</a></p><h2 id="十三-参考资料"><a class="markdownIt-Anchor" href="#十三-参考资料"></a> 十三、参考资料</h2><h3 id="推理系统"><a class="markdownIt-Anchor" href="#推理系统"></a> 推理系统</h3><ul><li><a href="https://github.com/vllm-project/vllm">vLLM</a>：高吞吐 LLM Serving 引擎。</li><li><a href="https://arxiv.org/abs/2309.06180">PagedAttention</a>：KV Cache 分页与内存共享。</li><li><a href="https://www.usenix.org/conference/osdi22/presentation/yu">Orca</a>：Iteration-level Scheduling。</li><li><a href="https://arxiv.org/abs/2308.16369">SARATHI</a>：Chunked Prefill。</li><li><a href="https://arxiv.org/abs/2401.09670">DistServe</a>：Prefill/Decode 解耦。</li><li><a href="https://arxiv.org/abs/2211.17192">Fast Inference from Transformers via Speculative Decoding</a>：投机解码。</li></ul><h3 id="量化与并行"><a class="markdownIt-Anchor" href="#量化与并行"></a> 量化与并行</h3><ul><li><a href="https://arxiv.org/abs/2211.10438">SmoothQuant</a>：W8A8 激活平滑量化。</li><li><a href="https://arxiv.org/abs/2306.00978">AWQ</a>：Activation-aware Weight Quantization。</li><li><a href="https://arxiv.org/abs/2210.17323">GPTQ</a>：训练后权重量化。</li><li><a href="https://arxiv.org/abs/1909.08053">Megatron-LM</a>：张量并行。</li></ul><h3 id="benchmark-与指标"><a class="markdownIt-Anchor" href="#benchmark-与指标"></a> Benchmark 与指标</h3><ul><li><a href="https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html">NVIDIA NIM LLM Benchmarking Metrics</a>：TTFT、ITL 和吞吐指标。</li><li><a href="https://mlcommons.org/benchmarks/inference-datacenter/">MLPerf Inference</a>：标准化推理 Benchmark。</li><li><a href="https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf.html">GenAI-Perf</a>：生成式模型负载测试。</li><li><a href="https://docs.vllm.ai/">vLLM Documentation</a>：引擎参数、Serving 和 Benchmark 工具。</li><li><a href="https://github.com/caomaolufei/AIInfraGuide">AIInfraGuide</a>：显存估算、推理引擎、并行部署与性能指标的中文资料，MIT License。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;很多 AI Infra 面试题彼此看似独立：计算 KV Cache、解释张量并行、分析 Decode 瓶颈、选择量化方案、设计压测指标、排查 OOM。放到真实部署里，它们其实是同一道综合题的不同步骤。&lt;/p&gt;
&lt;p&gt;本文设定一个具体任务：使用 8 张 H100 80GB 部</summary>
      
    
    
    
    <category term="AI Infrastructure" scheme="https://zhouzhq2021.github.io/categories/AI-Infrastructure/"/>
    
    
    <category term="AI Infra Engineering Series" scheme="https://zhouzhq2021.github.io/tags/AI-Infra-Engineering-Series/"/>
    
    <category term="Capacity Planning" scheme="https://zhouzhq2021.github.io/tags/Capacity-Planning/"/>
    
  </entry>
  
  <entry>
    <title>Softmax 数值稳定性与 IO-Aware Attention：从在线归一化到 FlashAttention</title>
    <link href="https://zhouzhq2021.github.io/2026/07/28/numerical-stability-to-flashattention/"/>
    <id>https://zhouzhq2021.github.io/2026/07/28/numerical-stability-to-flashattention/</id>
    <published>2026-07-28T02:00:00.000Z</published>
    <updated>2026-07-30T04:18:20.513Z</updated>
    
    <content type="html"><![CDATA[<p>高性能计算并不等于简单地“少算几步”。很多优秀算法做的事情，是利用数学等价关系改变计算顺序，让数据更适合硬件，同时避免浮点溢出和精度损失。</p><p>Softmax 是最典型的例子：从减最大值、Online Softmax 到 FlashAttention，数学公式没有改变，但数据扫描方式、并行规约方式和 HBM 访问量发生了根本变化。</p><p><img src="/images/posts/ai-infra-series/flashattention-io.jpg" alt="标准 Attention 与 FlashAttention 的 IO 模式" /></p><p><em>图 1：标准实现物化完整 Attention Matrix，FlashAttention 让 Tile 在片上完成归一化与输出累加。</em></p><h2 id="一-浮点数同时受精度和范围限制"><a class="markdownIt-Anchor" href="#一-浮点数同时受精度和范围限制"></a> 一、浮点数同时受精度和范围限制</h2><p>浮点格式可以粗略拆成符号位、指数位和尾数位：</p><ul><li>指数位决定能表示的数量级范围；</li><li>尾数位决定相邻数字之间的精细程度。</li></ul><p>FP16 的尾数精度和动态范围都小于 FP32，最大有限值约为 65504。BF16 的尾数更短，但指数范围与 FP32 接近，因此更不容易在大模型训练中发生溢出。</p><p>这解释了一个常见现象：BF16 的数值精度不一定高于 FP16，但训练稳定性往往更好，因为它保留了更大的指数范围。</p><h3 id="11-浮点加法不满足严格结合律"><a class="markdownIt-Anchor" href="#11-浮点加法不满足严格结合律"></a> 1.1 浮点加法不满足严格结合律</h3><p>数学上：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">(</mo><mi>a</mi><mo>+</mo><mi>b</mi><mo stretchy="false">)</mo><mo>+</mo><mi>c</mi><mo>=</mo><mi>a</mi><mo>+</mo><mo stretchy="false">(</mo><mi>b</mi><mo>+</mo><mi>c</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(a+b)+c=a+(b+c)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">b</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">c</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.66666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">c</span><span class="mclose">)</span></span></span></span></span></p><p>浮点运算中，舍入可能让两边得到不同结果。GPU 并行规约改变了加法顺序，所以同一个 Reduce Kernel 在不同 Block 划分、不同卡数甚至不同算法下，结果可能不逐位一致。</p><p>不逐位一致不等于错误。判断正确性要结合绝对误差、相对误差、数据类型和算法条件数。</p><h2 id="二-朴素-softmax-为什么会溢出"><a class="markdownIt-Anchor" href="#二-朴素-softmax-为什么会溢出"></a> 二、朴素 Softmax 为什么会溢出</h2><p>Softmax 定义为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>p</mi><mi>i</mi></msub><mo>=</mo><mfrac><msup><mi>e</mi><msub><mi>x</mi><mi>i</mi></msub></msup><mrow><munder><mo>∑</mo><mi>j</mi></munder><msup><mi>e</mi><msub><mi>x</mi><mi>j</mi></msub></msup></mrow></mfrac></mrow><annotation encoding="application/x-tex">p_i=\frac{e^{x_i}}{\sum_j e^{x_j}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.46321em;vertical-align:-1.1218180000000002em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.341392em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:-0.0000050000000000050004em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.16195399999999993em;"><span style="top:-2.40029em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.43581800000000004em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6064620000000001em;"><span style="top:-3.0050700000000004em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3280857142857143em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2818857142857143em;"><span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.664392em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3280857142857143em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.1218180000000002em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p>若某个 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">x_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.58056em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 很大，<code>exp(x_i)</code> 可能溢出为无穷；如果输入很小，又可能下溢为 0。</p><h3 id="21-减最大值"><a class="markdownIt-Anchor" href="#21-减最大值"></a> 2.1 减最大值</h3><p>令 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi><mo>=</mo><msub><mo><mi>max</mi><mo>⁡</mo></mo><mi>j</mi></msub><msub><mi>x</mi><mi>j</mi></msub></mrow><annotation encoding="application/x-tex">m=\max_j x_j</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.716668em;vertical-align:-0.286108em;"></span><span class="mop"><span class="mop">max</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.311664em;"><span style="top:-2.5500000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.311664em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span>：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>p</mi><mi>i</mi></msub><mo>=</mo><mfrac><msup><mi>e</mi><mrow><msub><mi>x</mi><mi>i</mi></msub><mo>−</mo><mi>m</mi></mrow></msup><mrow><munder><mo>∑</mo><mi>j</mi></munder><msup><mi>e</mi><mrow><msub><mi>x</mi><mi>j</mi></msub><mo>−</mo><mi>m</mi></mrow></msup></mrow></mfrac></mrow><annotation encoding="application/x-tex">p_i=\frac{e^{x_i-m}}{\sum_j e^{x_j-m}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.5701490000000002em;vertical-align:-1.1218180000000002em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.448331em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:-0.0000050000000000050004em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.16195399999999993em;"><span style="top:-2.40029em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.43581800000000004em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.713401em;"><span style="top:-3.0050700000000004em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3280857142857143em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2818857142857143em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3280857142857143em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.1218180000000002em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p>因为分子分母同时乘以 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>e</mi><mrow><mo>−</mo><mi>m</mi></mrow></msup></mrow><annotation encoding="application/x-tex">e^{-m}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.771331em;vertical-align:0em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span></span></span></span>，结果不变。所有 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mi>i</mi></msub><mo>−</mo><mi>m</mi><mo>≤</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">x_i-m\leq0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.73333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.7719400000000001em;vertical-align:-0.13597em;"></span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">0</span></span></span></span>，最大的指数项变成 1，从而避免正向溢出。</p><p>这不是经验技巧，而是严格的数学等价变换。</p><h2 id="三-safe-softmax-为什么需要三遍扫描"><a class="markdownIt-Anchor" href="#三-safe-softmax-为什么需要三遍扫描"></a> 三、Safe Softmax 为什么需要三遍扫描</h2><p>对一行长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 的输入，常见实现是：</p><ol><li>第一遍求最大值 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi></mrow><annotation encoding="application/x-tex">m</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">m</span></span></span></span>；</li><li>第二遍求 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>l</mi><mo>=</mo><msub><mo>∑</mo><mi>i</mi></msub><msup><mi>e</mi><mrow><msub><mi>x</mi><mi>i</mi></msub><mo>−</mo><mi>m</mi></mrow></msup></mrow><annotation encoding="application/x-tex">l=\sum_i e^{x_i-m}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.0710410000000001em;vertical-align:-0.29971000000000003em;"></span><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:-0.0000050000000000050004em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.16195399999999993em;"><span style="top:-2.40029em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.29971000000000003em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3280857142857143em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span></span></span></span>；</li><li>第三遍输出 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>e</mi><mrow><msub><mi>x</mi><mi>i</mi></msub><mo>−</mo><mi>m</mi></mrow></msup><mi mathvariant="normal">/</mi><mi>l</mi></mrow><annotation encoding="application/x-tex">e^{x_i-m}/l</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.021331em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3280857142857143em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span></span></span></span>。</li></ol><p>即使三遍计算都很简单，也要多次从内存读取同一行。在 GPU 上，Softmax 往往更受访存和规约限制，而不是指数计算本身。</p><p>如果一行较短，可以把输入暂存在寄存器或 Shared Memory；如果一行很长，缓存全部输入又会增加资源压力。</p><h2 id="四-online-softmax维护一个可合并状态"><a class="markdownIt-Anchor" href="#四-online-softmax维护一个可合并状态"></a> 四、Online Softmax：维护一个可合并状态</h2><p>逐个读取元素时，可以维护当前最大值 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>m</mi><mi>k</mi></msub></mrow><annotation encoding="application/x-tex">m_k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.58056em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 和归一化分母 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>l</mi><mi>k</mi></msub></mrow><annotation encoding="application/x-tex">l_k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>m</mi><mrow><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>=</mo><mi>max</mi><mo>⁡</mo><mo stretchy="false">(</mo><msub><mi>m</mi><mi>k</mi></msub><mo separator="true">,</mo><msub><mi>x</mi><mrow><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">m_{k+1}=\max(m_k,x_{k+1})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.638891em;vertical-align:-0.208331em;"></span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.208331em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">max</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.208331em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span></span></p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>l</mi><mrow><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>=</mo><msub><mi>l</mi><mi>k</mi></msub><msup><mi>e</mi><mrow><msub><mi>m</mi><mi>k</mi></msub><mo>−</mo><msub><mi>m</mi><mrow><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub></mrow></msup><mo>+</mo><msup><mi>e</mi><mrow><msub><mi>x</mi><mrow><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>−</mo><msub><mi>m</mi><mrow><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub></mrow></msup></mrow><annotation encoding="application/x-tex">l_{k+1}=l_k e^{m_k-m_{k+1}}+e^{x_{k+1}-m_{k+1}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.902771em;vertical-align:-0.208331em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.208331em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.9713309999999999em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8213309999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15122857142857138em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.34480000000000005em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.21074999999999994em;"><span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.8213309999999999em;vertical-align:0em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8213309999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.34480000000000005em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.21074999999999994em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.34480000000000005em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.21074999999999994em;"><span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></p><p>当遇到更大的元素时，旧的指数和需要乘以 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>e</mi><mrow><msub><mi>m</mi><mi>k</mi></msub><mo>−</mo><msub><mi>m</mi><mrow><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub></mrow></msup></mrow><annotation encoding="application/x-tex">e^{m_k-m_{k+1}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.771331em;vertical-align:0em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15122857142857138em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.34480000000000005em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.21074999999999994em;"><span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>，把它重新缩放到新的最大值基准。</p><h3 id="41-两个局部状态如何合并"><a class="markdownIt-Anchor" href="#41-两个局部状态如何合并"></a> 4.1 两个局部状态如何合并</h3><p>假设两段数据分别得到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><msub><mi>m</mi><mi>a</mi></msub><mo separator="true">,</mo><msub><mi>l</mi><mi>a</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(m_a,l_a)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><msub><mi>m</mi><mi>b</mi></msub><mo separator="true">,</mo><msub><mi>l</mi><mi>b</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(m_b,l_b)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span>，合并状态为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>m</mi><mo>=</mo><mi>max</mi><mo>⁡</mo><mo stretchy="false">(</mo><msub><mi>m</mi><mi>a</mi></msub><mo separator="true">,</mo><msub><mi>m</mi><mi>b</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">m=\max(m_a,m_b)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">max</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span></span></p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>l</mi><mo>=</mo><msub><mi>l</mi><mi>a</mi></msub><msup><mi>e</mi><mrow><msub><mi>m</mi><mi>a</mi></msub><mo>−</mo><mi>m</mi></mrow></msup><mo>+</mo><msub><mi>l</mi><mi>b</mi></msub><msup><mi>e</mi><mrow><msub><mi>m</mi><mi>b</mi></msub><mo>−</mo><mi>m</mi></mrow></msup></mrow><annotation encoding="application/x-tex">l=l_a e^{m_a-m}+l_b e^{m_b-m}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.971331em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.821331em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.16454285714285719em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.971331em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.821331em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15122857142857138em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span></span></span></span></span></p><p>这个合并操作在精确数学中满足结合律，因此可以在线程、Warp 和 Block 层级做树形规约。浮点实现仍可能因为合并顺序不同产生细微误差。</p><p>下面的 Python 实现适合验证合并公式。把一行随机切成多个 Chunk，分别计算状态后反复合并，最终分母应与整行 Safe Softmax 一致：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> math</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">online_state</span>(<span class="params">xs</span>):</span><br><span class="line">    m = -math.inf</span><br><span class="line">    l = <span class="number">0.0</span></span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> xs:</span><br><span class="line">        new_m = <span class="built_in">max</span>(m, x)</span><br><span class="line">        l = l * math.exp(m - new_m) + math.exp(x - new_m)</span><br><span class="line">        m = new_m</span><br><span class="line">    <span class="keyword">return</span> m, l</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">merge_state</span>(<span class="params">a, b</span>):</span><br><span class="line">    ma, la = a</span><br><span class="line">    mb, lb = b</span><br><span class="line">    m = <span class="built_in">max</span>(ma, mb)</span><br><span class="line">    l = la * math.exp(ma - m) + lb * math.exp(mb - m)</span><br><span class="line">    <span class="keyword">return</span> m, l</span><br></pre></td></tr></table></figure><p>并行实现的关键不是把这个循环原样搬到 GPU，而是让每个线程先处理局部元素，再用 Shuffle 和 Shared Memory 执行同一个 <code>merge_state</code>。</p><h3 id="42-gpu-上的层级规约"><a class="markdownIt-Anchor" href="#42-gpu-上的层级规约"></a> 4.2 GPU 上的层级规约</h3><p>一种典型实现是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">每线程处理多个元素并得到局部 (m, l)</span><br><span class="line">             ↓</span><br><span class="line">Warp Shuffle 合并 Warp 内状态</span><br><span class="line">             ↓</span><br><span class="line">Shared Memory 保存各 Warp 结果</span><br><span class="line">             ↓</span><br><span class="line">第一个 Warp 合并 Block 状态</span><br></pre></td></tr></table></figure><p>与普通 Sum Reduce 不同，Online Softmax 规约的不是一个标量，而是带缩放规则的二元状态。</p><h2 id="五-layernorm-与-rmsnorm-的数值问题"><a class="markdownIt-Anchor" href="#五-layernorm-与-rmsnorm-的数值问题"></a> 五、LayerNorm 与 RMSNorm 的数值问题</h2><p>LayerNorm 对每个 Token 的隐藏维做归一化：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>y</mi><mo>=</mo><mfrac><mrow><mi>x</mi><mo>−</mo><mi>μ</mi></mrow><msqrt><mrow><msup><mi>σ</mi><mn>2</mn></msup><mo>+</mo><mi>ϵ</mi></mrow></msqrt></mfrac><mi>γ</mi><mo>+</mo><mi>β</mi></mrow><annotation encoding="application/x-tex">y=\frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}\gamma+\beta</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.1903300000000003em;vertical-align:-0.93em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603300000000002em;"><span style="top:-2.196611em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.913389em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord" style="padding-left:0.833em;"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.740108em;"><span style="top:-2.9890000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathnormal">ϵ</span></span></span><span style="top:-2.873389em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg width='400em' height='1.08em' viewBox='0 0 400000 1080' preserveAspectRatio='xMinYMin slice'><path d='M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.12661100000000003em;"><span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathnormal">μ</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.93em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord mathnormal" style="margin-right:0.05556em;">γ</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.05278em;">β</span></span></span></span></span></p><p>RMSNorm 省略均值中心化：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>y</mi><mo>=</mo><mfrac><mi>x</mi><msqrt><mrow><mi mathvariant="normal">mean</mi><mo>⁡</mo><mo stretchy="false">(</mo><msup><mi>x</mi><mn>2</mn></msup><mo stretchy="false">)</mo><mo>+</mo><mi>ϵ</mi></mrow></msqrt></mfrac><mi>γ</mi></mrow><annotation encoding="application/x-tex">y=\frac{x}{\sqrt{\operatorname{mean}(x^2)+\epsilon}}\gamma</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.23756em;vertical-align:-1.13em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.10756em;"><span style="top:-2.175em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.935em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mop"><span class="mord mathrm">m</span><span class="mord mathrm">e</span><span class="mord mathrm">a</span><span class="mord mathrm">n</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.740108em;"><span style="top:-2.9890000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathnormal">ϵ</span></span></span><span style="top:-2.8950000000000005em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg width='400em' height='1.28em' viewBox='0 0 400000 1296' preserveAspectRatio='xMinYMin slice'><path d='M263,681c0.7,0,18,39.7,52,119c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120c340,-704.7,510.7,-1060.3,512,-1067l0 -0c4.7,-7.3,11,-11,19,-11H40000v40H1012.3s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60zM1001 80h400000v40h-400000z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.30499999999999994em;"><span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.13em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord mathnormal" style="margin-right:0.05556em;">γ</span></span></span></span></span></p><p>这些操作包含平方、求和和开方。若直接以低精度累积，大隐藏维下误差可能明显增加。因此常见 Kernel 会：</p><ul><li>输入和输出使用 BF16/FP16；</li><li>局部统计量使用 FP32 累积；</li><li>采用 Welford 等稳定算法计算方差；</li><li>将 Residual Add、Norm 和类型转换融合，减少 HBM 往返。</li></ul><p>算子融合的前提是数值语义明确。改变类型转换位置或规约顺序，可能让融合结果与拆分实现不再逐位一致。</p><h2 id="六-混合精度训练为什么需要主权重"><a class="markdownIt-Anchor" href="#六-混合精度训练为什么需要主权重"></a> 六、混合精度训练为什么需要主权重</h2><p>混合精度训练常见流程是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">FP32 主权重</span><br><span class="line">   ↓ 转换</span><br><span class="line">BF16/FP16 前向与反向</span><br><span class="line">   ↓ 梯度</span><br><span class="line">FP32 优化器更新</span><br></pre></td></tr></table></figure><p>如果直接用 FP16 权重执行很小的更新，更新量可能因为尾数精度不足被舍入掉。FP32 主权重用于积累这些微小变化。</p><h3 id="61-loss-scaling"><a class="markdownIt-Anchor" href="#61-loss-scaling"></a> 6.1 Loss Scaling</h3><p>FP16 梯度可能下溢为 0。将 Loss 乘以缩放因子 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">s</span></span></span></span>，梯度也会乘以 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">s</span></span></span></span>；在 Optimizer Step 前再除以 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">s</span></span></span></span>，可以把小梯度暂时移入 FP16 可表示范围。</p><p>动态 Loss Scaling 会在检测到 <code>inf</code> 或 <code>nan</code> 时降低缩放因子，在训练稳定时逐步增加。BF16 因为指数范围更大，通常不需要同样的 Loss Scaling，但仍要监控非有限值。</p><h2 id="七-flashattention把-online-softmax-放进-attention-tile"><a class="markdownIt-Anchor" href="#七-flashattention把-online-softmax-放进-attention-tile"></a> 七、FlashAttention：把 Online Softmax 放进 Attention Tile</h2><p><img src="/images/posts/ai-infra-series/flashattention-tiling.png" alt="FlashAttention Tiling" /></p><p><em>图 2：Q、K、V 按片上存储容量切块，每个 Q Tile 通过在线状态依次吸收 K/V Tile 的贡献。</em></p><p>标准 Attention 可以写成：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>S</mi><mo>=</mo><mi>Q</mi><msup><mi>K</mi><mi>T</mi></msup><mo separator="true">,</mo><mspace width="1em"/><mi>P</mi><mo>=</mo><mi mathvariant="normal">softmax</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>S</mi><mo stretchy="false">)</mo><mo separator="true">,</mo><mspace width="1em"/><mi>O</mi><mo>=</mo><mi>P</mi><mi>V</mi></mrow><annotation encoding="application/x-tex">S=QK^T,\quad P=\operatorname{softmax}(S),\quad O=PV</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.0857709999999998em;vertical-align:-0.19444em;"></span><span class="mord mathnormal">Q</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">K</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913309999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:1em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">s</span><span class="mord mathrm">o</span><span class="mord mathrm" style="margin-right:0.07778em;">f</span><span class="mord mathrm">t</span><span class="mord mathrm">m</span><span class="mord mathrm">a</span><span class="mord mathrm">x</span></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mclose">)</span><span class="mpunct">,</span><span class="mspace" style="margin-right:1em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span></span></span></span></span></p><p>如果把完整 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi></mrow><annotation encoding="application/x-tex">P</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span></span></span></span> 写入 HBM，序列长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 时会产生 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><msup><mi>N</mi><mn>2</mn></msup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N^2)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.064108em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141079999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span> 的中间数据。</p><p>FlashAttention 的核心流程是：</p><ol><li>将 Q、K、V 切成可以放入片上存储的 Tile；</li><li>计算一个 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Q</mi><msup><mi>K</mi><mi>T</mi></msup></mrow><annotation encoding="application/x-tex">QK^T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.035771em;vertical-align:-0.19444em;"></span><span class="mord mathnormal">Q</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">K</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8413309999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span></span></span> 小块；</li><li>更新当前行的 Online Softmax 状态；</li><li>按新的最大值重新缩放旧输出累加器；</li><li>累加当前概率块与 V Tile 的乘积；</li><li>不把完整 Attention Matrix 写回 HBM。</li></ol><p>用算法状态表示，一个 Q Tile 的处理过程可以写成：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">初始化：m = -∞，l = 0，O = 0</span><br><span class="line"></span><br><span class="line">for 每个 K/V Tile:</span><br><span class="line">    S_tile = Q_tile @ K_tile^T</span><br><span class="line">    m_new  = max(m, rowmax(S_tile))</span><br><span class="line">    P_tile = exp(S_tile - m_new)</span><br><span class="line">    alpha  = exp(m - m_new)</span><br><span class="line">    l_new  = alpha * l + rowsum(P_tile)</span><br><span class="line">    O      = alpha * O + P_tile @ V_tile</span><br><span class="line">    m, l   = m_new, l_new</span><br><span class="line"></span><br><span class="line">最终：O = O / l</span><br></pre></td></tr></table></figure><p>这里的 <code>alpha</code> 同时重缩放旧分母和旧输出累加器。只更新 <code>l</code> 而忘记缩放 <code>O</code>，会得到数值稳定但数学错误的 Attention。</p><p>对于输出累加器，还需要维护与 Softmax 分母一致的缩放关系。新的 Tile 如果抬高了行最大值，旧输出也要乘以相应缩放因子，然后才能与当前 Tile 的贡献相加。</p><h3 id="71-为什么它仍是精确-attention"><a class="markdownIt-Anchor" href="#71-为什么它仍是精确-attention"></a> 7.1 为什么它仍是精确 Attention</h3><p>FlashAttention 没有丢弃 Token，也没有近似概率矩阵。它只是利用 Online Softmax 的可合并状态，改变了计算和数据搬运顺序。因此在浮点误差允许范围内，它计算的是同一个 Attention。</p><h3 id="72-为什么反向可以重计算"><a class="markdownIt-Anchor" href="#72-为什么反向可以重计算"></a> 7.2 为什么反向可以重计算</h3><p>标准实现可能保存巨大的概率矩阵供反向使用。FlashAttention 保存更少的统计量，反向时重新计算局部分数。</p><p>这看起来增加了 FLOPs，却减少了更昂贵的 HBM 读写。只要重计算发生在高吞吐矩阵运算中，Wall-clock 时间仍可能更短。</p><h2 id="八-v2-为什么继续优化工作划分"><a class="markdownIt-Anchor" href="#八-v2-为什么继续优化工作划分"></a> 八、V2 为什么继续优化工作划分</h2><p>FlashAttention-2 没有推翻 V1 的数学核心，主要改进包括：</p><ul><li>调整 Q 与 K/V 的循环组织，让输出不必反复写回 HBM；</li><li>减少非矩阵乘法相关的缩放和同步；</li><li>改变 Warp 之间的工作分配，减少 Shared Memory 通信；</li><li>对 Causal Mask 的无效块进行跳过。</li></ul><p>这说明算法优化不仅要考虑大 O 复杂度，还要考虑硬件上不同指令的吞吐差异。Tensor Core GEMM 很快，并不代表标量指数、同步和数据搬运同样便宜。</p><h2 id="九-正确性测试应该怎么做"><a class="markdownIt-Anchor" href="#九-正确性测试应该怎么做"></a> 九、正确性测试应该怎么做</h2><p>一个高性能 Kernel 至少需要四类测试：</p><h3 id="91-参考实现"><a class="markdownIt-Anchor" href="#91-参考实现"></a> 9.1 参考实现</h3><p>用 PyTorch 或高精度 CPU 实现作为基准，对比 Forward 和必要的 Gradient。</p><h3 id="92-极端输入"><a class="markdownIt-Anchor" href="#92-极端输入"></a> 9.2 极端输入</h3><ul><li>很大的正数；</li><li>很大的负数；</li><li>全部相等；</li><li>一行只有一个元素；</li><li>非对齐长度和尾块；</li><li>包含 Mask 的边界情况。</li></ul><h3 id="93-误差标准"><a class="markdownIt-Anchor" href="#93-误差标准"></a> 9.3 误差标准</h3><p>同时报告绝对误差和相对误差。容限应根据 FP32、BF16、FP16 以及累积长度设置，不能用一套阈值覆盖所有情况。</p><h3 id="94-性能与精度一起记录"><a class="markdownIt-Anchor" href="#94-性能与精度一起记录"></a> 9.4 性能与精度一起记录</h3><p>只报告速度而不报告误差没有意义。推荐保存：</p><table><thead><tr><th>Shape</th><th>dtype</th><th style="text-align:right">最大绝对误差</th><th style="text-align:right">最大相对误差</th><th style="text-align:right">延迟</th><th style="text-align:right">带宽/TFLOPS</th></tr></thead></table><h2 id="十-技术-qa"><a class="markdownIt-Anchor" href="#十-技术-qa"></a> 十、技术 Q&amp;A</h2><h3 id="q1稳定-softmax-为什么要减去最大值online-softmax-又如何合并局部状态"><a class="markdownIt-Anchor" href="#q1稳定-softmax-为什么要减去最大值online-softmax-又如何合并局部状态"></a> Q1：稳定 Softmax 为什么要减去最大值，Online Softmax 又如何合并局部状态？</h3><p>在 Softmax 的分子和分母中同时乘以 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>e</mi><mrow><mo>−</mo><mi>m</mi></mrow></msup></mrow><annotation encoding="application/x-tex">e^{-m}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.771331em;vertical-align:0em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span></span></span></span> 不会改变结果。选择 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi><mo>=</mo><msub><mo><mi>max</mi><mo>⁡</mo></mo><mi>i</mi></msub><msub><mi>x</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">m=\max_i x_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.58056em;vertical-align:-0.15em;"></span><span class="mop"><span class="mop">max</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 后，所有指数输入都不大于零，从而避免正向溢出。合并两个局部状态时，先取 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi><mo>=</mo><mi>max</mi><mo>⁡</mo><mo stretchy="false">(</mo><msub><mi>m</mi><mi>a</mi></msub><mo separator="true">,</mo><msub><mi>m</mi><mi>b</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">m=\max(m_a,m_b)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">max</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span>，再把两段分母重缩放到同一基准：<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>l</mi><mo>=</mo><msub><mi>l</mi><mi>a</mi></msub><msup><mi>e</mi><mrow><msub><mi>m</mi><mi>a</mi></msub><mo>−</mo><mi>m</mi></mrow></msup><mo>+</mo><msub><mi>l</mi><mi>b</mi></msub><msup><mi>e</mi><mrow><msub><mi>m</mi><mi>b</mi></msub><mo>−</mo><mi>m</mi></mrow></msup></mrow><annotation encoding="application/x-tex">l=l_ae^{m_a-m}+l_be^{m_b-m}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.921331em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.16454285714285719em;"><span style="top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.921331em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.01968em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.771331em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">m</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.3487714285714287em;margin-left:0em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">b</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15122857142857138em;"><span></span></span></span></span></span></span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">m</span></span></span></span></span></span></span></span></span></span></span></span>。局部 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>l</mi></mrow><annotation encoding="application/x-tex">l</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span></span></span></span> 分别对应不同的指数尺度，不能直接相加。</p><h3 id="q2合并公式在数学上满足结合律为什么-gpu-结果仍可能不逐位一致"><a class="markdownIt-Anchor" href="#q2合并公式在数学上满足结合律为什么-gpu-结果仍可能不逐位一致"></a> Q2：合并公式在数学上满足结合律，为什么 GPU 结果仍可能不逐位一致？</h3><p>公式在实数域等价，但浮点乘加、指数和规约的每一步都会舍入。不同 Warp/Block 划分会改变合并树，因此也会改变舍入误差的累积顺序。正确性测试应使用与 dtype 相匹配的误差容限和高精度参考，不能要求所有并行配置得到相同的 bit pattern。</p><h3 id="q3fp16-为什么常需要-loss-scaling而-bf16-通常不需要"><a class="markdownIt-Anchor" href="#q3fp16-为什么常需要-loss-scaling而-bf16-通常不需要"></a> Q3：FP16 为什么常需要 Loss Scaling，而 BF16 通常不需要？</h3><p>FP16 的指数位较少，小梯度更容易下溢成零；Loss Scaling 先放大损失和梯度，更新前再按相同比例缩回。BF16 与 FP32 具有相同宽度的指数部分，动态范围大得多，通常不需要用同样方法避免下溢。不过 BF16 的尾数更短，仍然存在明显的舍入误差，因此“通常不需要 Loss Scaling”并不等于“数值精度等同于 FP32”。</p><h3 id="q4flashattention-为什么仍是精确-attention重计算增加-flops-又为何可能更快"><a class="markdownIt-Anchor" href="#q4flashattention-为什么仍是精确-attention重计算增加-flops-又为何可能更快"></a> Q4：FlashAttention 为什么仍是精确 Attention，重计算增加 FLOPs 又为何可能更快？</h3><p>FlashAttention 没有稀疏化或截断 Attention，而是使用 Online Softmax 的等价缩放公式合并不同 K/V Tile，数学目标没有改变。它避免把巨大的 Attention Probability 写入 HBM 并再次读回；反向时宁可重算部分局部结果，也不保存这些中间张量。由于 GPU 的矩阵计算吞吐远高于 HBM 的数据供给速率，把低带宽的数据搬运替换为高吞吐计算，即使 FLOPs 增加，总时间仍可能下降。</p><h2 id="十一-系列导航"><a class="markdownIt-Anchor" href="#十一-系列导航"></a> 十一、系列导航</h2><p>上一篇：<a href="/2026/07/26/distributed-training-from-allreduce/" title="分布式训练的通信模型：从集合通信到多维并行">分布式训练的通信模型</a></p><p>下一篇：<a href="/2026/07/30/llm-70b-capacity-planning/" title="70B 大模型推理服务的容量规划与性能设计">70B 大模型推理服务的容量规划与性能设计</a></p><h2 id="十二-参考资料"><a class="markdownIt-Anchor" href="#十二-参考资料"></a> 十二、参考资料</h2><h3 id="数值计算与归一化"><a class="markdownIt-Anchor" href="#数值计算与归一化"></a> 数值计算与归一化</h3><ul><li><a href="https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html">What Every Computer Scientist Should Know About Floating-Point Arithmetic</a>：浮点误差、舍入和异常值的经典介绍。</li><li><a href="https://arxiv.org/abs/1710.03740">Mixed Precision Training</a>：主权重和 Loss Scaling。</li><li><a href="https://arxiv.org/abs/1607.06450">Layer Normalization</a>：LayerNorm 原始论文。</li><li><a href="https://arxiv.org/abs/1910.07467">Root Mean Square Layer Normalization</a>：RMSNorm。</li><li><a href="https://arxiv.org/abs/2002.04745">On Layer Normalization in the Transformer Architecture</a>：Pre-Norm 与 Post-Norm。</li></ul><h3 id="softmax-与-attention"><a class="markdownIt-Anchor" href="#softmax-与-attention"></a> Softmax 与 Attention</h3><ul><li><a href="https://arxiv.org/abs/1805.02867">Online Normalizer Calculation for Softmax</a>：Online Softmax 状态及合并公式。</li><li><a href="https://arxiv.org/abs/2205.14135">FlashAttention</a>：IO-Aware Exact Attention。</li><li><a href="https://arxiv.org/abs/2307.08691">FlashAttention-2</a>：并行划分和工作分配优化。</li><li><a href="https://github.com/Dao-AILab/flash-attention">FlashAttention Official Repository</a>：实现、测试和 Benchmark。</li><li><a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#warp-shuffle-functions">CUDA Warp Shuffle Functions</a>：Warp 级状态交换。</li><li><a href="https://github.com/caomaolufei/AIInfraGuide">AIInfraGuide</a>：数学基础、混合精度、Online Softmax 与 FlashAttention 的中文推导和实现资料，MIT License。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;高性能计算并不等于简单地“少算几步”。很多优秀算法做的事情，是利用数学等价关系改变计算顺序，让数据更适合硬件，同时避免浮点溢出和精度损失。&lt;/p&gt;
&lt;p&gt;Softmax 是最典型的例子：从减最大值、Online Softmax 到 FlashAttention，数学公式没有</summary>
      
    
    
    
    <category term="AI Infrastructure" scheme="https://zhouzhq2021.github.io/categories/AI-Infrastructure/"/>
    
    
    <category term="AI Infra Engineering Series" scheme="https://zhouzhq2021.github.io/tags/AI-Infra-Engineering-Series/"/>
    
    <category term="FlashAttention" scheme="https://zhouzhq2021.github.io/tags/FlashAttention/"/>
    
  </entry>
  
  <entry>
    <title>分布式训练的通信模型：从集合通信到多维并行</title>
    <link href="https://zhouzhq2021.github.io/2026/07/26/distributed-training-from-allreduce/"/>
    <id>https://zhouzhq2021.github.io/2026/07/26/distributed-training-from-allreduce/</id>
    <published>2026-07-26T02:00:00.000Z</published>
    <updated>2026-07-30T04:18:20.513Z</updated>
    
    <content type="html"><![CDATA[<p>分布式训练经常被介绍成一串缩写：DP、DDP、FSDP、TP、PP、EP、CP。只记缩写很难形成稳定理解，因为真正决定系统行为的不是名称，而是三个问题：切分了什么、什么时候需要恢复完整数据、恢复时使用什么通信原语。</p><p>本文从 AllReduce 出发，把集合通信、显存分片、模型并行和物理拓扑放进同一个框架。</p><p><img src="/images/posts/ai-infra-series/parallel-topology.svg" alt="两节点 16 GPU 的 TP 与 DP 拓扑映射" /></p><p><em>图 1：高频 TP 通信位于单机 NVLink 域，较低频的数据并行同步跨节点进行。</em></p><p>分析任何并行策略时，可以先写出它的“通信签名”：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(参与者，传输对象，通信原语，单次数据量，调用频率，能否与计算重叠)</span><br></pre></td></tr></table></figure><p>例如 DDP 的核心签名是“所有数据并行 Rank、梯度、AllReduce、约一个模型梯度大小、每训练 Step 一次并按 Bucket 拆分、可以与反向重叠”；TP 则是“同一 TP Group、层内激活或部分和、AllReduce/AllGather、与当前层 Shape 有关、每层多次、位于关键路径”。</p><p>当两个方案的缩写难以比较时，通信签名通常能直接暴露真正差异。</p><h2 id="一-先看硬件通信不是抽象的"><a class="markdownIt-Anchor" href="#一-先看硬件通信不是抽象的"></a> 一、先看硬件：通信不是抽象的</h2><p>同样是 GPU 之间传输数据，不同链路的性能可能相差一个数量级。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">GPU HBM</span><br><span class="line">  │</span><br><span class="line">  ├─ NVLink / NVSwitch：单机 GPU 高带宽互联</span><br><span class="line">  │</span><br><span class="line">  ├─ PCIe：GPU、CPU 与设备互联</span><br><span class="line">  │</span><br><span class="line">  └─ InfiniBand / RoCE：跨节点网络</span><br></pre></td></tr></table></figure><p>实际带宽受 GPU 型号、链路代际、拓扑、NIC、交换机和通信算法影响，不能只看宣传峰值。第一步应该在目标机器执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nvidia-smi topo -m</span><br></pre></td></tr></table></figure><p>然后用 <code>nccl-tests</code> 测量真实 AllReduce、AllGather 和 ReduceScatter 带宽。</p><h3 id="11-延迟带宽模型"><a class="markdownIt-Anchor" href="#11-延迟带宽模型"></a> 1.1 延迟—带宽模型</h3><p>一次通信可以粗略表示为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mo>≈</mo><mi>α</mi><mo>+</mo><mfrac><mi>M</mi><mi>B</mi></mfrac></mrow><annotation encoding="application/x-tex">T\approx\alpha+\frac{M}{B}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.66666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:2.04633em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.36033em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>α</mi></mrow><annotation encoding="application/x-tex">\alpha</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span></span> 是启动延迟，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span> 是数据量，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>B</mi></mrow><annotation encoding="application/x-tex">B</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span></span> 是有效带宽。</p><ul><li>小消息更受启动延迟影响；</li><li>大消息更受带宽影响；</li><li>高频层间通信不适合放在慢速跨机链路上；</li><li>低频大块通信可以通过流水和重叠摊销。</li></ul><p>这就是为什么张量并行通常留在 NVLink 域，而数据并行更容易跨节点扩展。</p><h2 id="二-集合通信是一组积木"><a class="markdownIt-Anchor" href="#二-集合通信是一组积木"></a> 二、集合通信是一组积木</h2><p>假设有 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 个 Rank，每个 Rank 持有一份或一片张量。</p><h3 id="21-broadcast"><a class="markdownIt-Anchor" href="#21-broadcast"></a> 2.1 Broadcast</h3><p>一个 Rank 把数据复制给所有 Rank，常用于同步配置或初始状态。</p><h3 id="22-allreduce"><a class="markdownIt-Anchor" href="#22-allreduce"></a> 2.2 AllReduce</h3><p>所有 Rank 的数据先做规约，每个 Rank 最终都得到相同完整结果。DDP 使用它同步梯度。</p><h3 id="23-reducescatter"><a class="markdownIt-Anchor" href="#23-reducescatter"></a> 2.3 ReduceScatter</h3><p>数据先规约，再把结果切成 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 片，每个 Rank 只保留一片。它适合“规约后本来就只需要自己的分片”的场景。</p><h3 id="24-allgather"><a class="markdownIt-Anchor" href="#24-allgather"></a> 2.4 AllGather</h3><p>每个 Rank 提供一片，最终所有 Rank 都拿到完整张量。参数分片系统在计算前经常需要它临时恢复完整参数。</p><h3 id="25-all-to-all"><a class="markdownIt-Anchor" href="#25-all-to-all"></a> 2.5 All-to-All</h3><p>每个 Rank 给其他 Rank 发送不同数据。MoE 中 Token 需要根据 Router 结果发往不同专家，是典型 All-to-All 场景。</p><p>最重要的恒等关系是：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">AllReduce</mi><mo>⁡</mo><mo>=</mo><mi mathvariant="normal">ReduceScatter</mi><mo>⁡</mo><mo>+</mo><mi mathvariant="normal">AllGather</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\operatorname{AllReduce}=\operatorname{ReduceScatter}+\operatorname{AllGather}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mop"><span class="mord mathrm">A</span><span class="mord mathrm">l</span><span class="mord mathrm">l</span><span class="mord mathrm">R</span><span class="mord mathrm">e</span><span class="mord mathrm">d</span><span class="mord mathrm">u</span><span class="mord mathrm">c</span><span class="mord mathrm">e</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mop"><span class="mord mathrm">R</span><span class="mord mathrm">e</span><span class="mord mathrm">d</span><span class="mord mathrm">u</span><span class="mord mathrm">c</span><span class="mord mathrm">e</span><span class="mord mathrm">S</span><span class="mord mathrm">c</span><span class="mord mathrm">a</span><span class="mord mathrm">t</span><span class="mord mathrm">t</span><span class="mord mathrm">e</span><span class="mord mathrm">r</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord">+</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop"><span class="mord mathrm">A</span><span class="mord mathrm">l</span><span class="mord mathrm">l</span><span class="mord mathrm">G</span><span class="mord mathrm">a</span><span class="mord mathrm">t</span><span class="mord mathrm">h</span><span class="mord mathrm">e</span><span class="mord mathrm">r</span></span></span></span></span></span></p><p>它既解释了 Ring AllReduce，也解释了 ZeRO/FSDP 为什么可以把规约结果直接保留为分片。</p><h2 id="三-ring-allreduce-为什么常用于大消息"><a class="markdownIt-Anchor" href="#三-ring-allreduce-为什么常用于大消息"></a> 三、Ring AllReduce 为什么常用于大消息</h2><p>Ring AllReduce 将 Rank 组织成环，分两个阶段：</p><ol><li>ReduceScatter：每轮传递一个 Chunk，并对收到的数据规约；</li><li>AllGather：把最终 Chunk 沿环传播，让每个 Rank 恢复完整结果。</li></ol><p>若每个 Rank 的张量大小为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span> Bytes，则每个 Rank 的总通信量近似为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>2</mn><mfrac><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow><mi>N</mi></mfrac><mi>M</mi></mrow><annotation encoding="application/x-tex">2\frac{N-1}{N}M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.04633em;vertical-align:-0.686em;"></span><span class="mord">2</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.36033em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span></span></p><p>当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 较大时接近 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mi>M</mi></mrow><annotation encoding="application/x-tex">2M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span>，通信量不会随 GPU 数量线性爆炸。Ring 的优势是带宽利用率高，代价是通信步骤多；Tree 算法步骤更少，更适合延迟敏感的小消息或大规模节点。</p><p>例如 8 张 GPU 同步一个 2GB 梯度张量，每卡传输量约为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>2</mn><mo>×</mo><mfrac><mn>7</mn><mn>8</mn></mfrac><mo>×</mo><mn>2</mn><mtext> GB</mtext><mo>=</mo><mn>3.5</mn><mtext> GB</mtext></mrow><annotation encoding="application/x-tex">2\times\frac{7}{8}\times2\text{ GB}=3.5\text{ GB}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:2.00744em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.32144em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">8</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">7</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">2</span><span class="mord text"><span class="mord"> GB</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">3</span><span class="mord">.</span><span class="mord">5</span><span class="mord text"><span class="mord"> GB</span></span></span></span></span></span></p><p>若通信路径有效带宽为 350GB/s，仅带宽项理论下界约 10ms；如果跨节点有效带宽只有 40GB/s，则约 87.5ms。这里还没有计入每轮启动延迟、拓扑竞争和协议开销。这个数量级差异足以解释为什么同一并行策略在单机和多机表现完全不同。</p><p>NCCL 会结合消息大小和拓扑选择算法，手动强制 Ring 或 Tree 更适合诊断和对比，不应默认当作永久优化项。</p><h2 id="四-ddp每张卡保存完整模型"><a class="markdownIt-Anchor" href="#四-ddp每张卡保存完整模型"></a> 四、DDP：每张卡保存完整模型</h2><p><img src="/images/posts/ai-infra-series/data-parallel-overview.png" alt="数据并行概览" /></p><p><em>图 2：每个 Rank 保存相同模型、消费不同 Micro Batch，并在反向传播阶段同步梯度。</em></p><p>数据并行把 Batch 切给不同 Rank：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">相同参数</span><br><span class="line">不同数据</span><br><span class="line">  ↓</span><br><span class="line">各自 Forward / Backward</span><br><span class="line">  ↓</span><br><span class="line">AllReduce 梯度</span><br><span class="line">  ↓</span><br><span class="line">各自执行相同 Optimizer Step</span><br></pre></td></tr></table></figure><p>只要初始参数相同、梯度同步正确，各 Rank 更新后的参数仍然一致。</p><p>若全局 Batch 均匀切成 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 份，每个 Rank 的局部平均梯度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>g</mi><mi>r</mi></msub></mrow><annotation encoding="application/x-tex">g_r</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，那么全局平均梯度为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>g</mi><mo>=</mo><mfrac><mn>1</mn><mi>N</mi></mfrac><munderover><mo>∑</mo><mrow><mi>r</mi><mo>=</mo><mn>0</mn></mrow><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow></munderover><msub><mi>g</mi><mi>r</mi></msub></mrow><annotation encoding="application/x-tex">g=\frac{1}{N}\sum_{r=0}^{N-1}g_r</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:3.0954490000000003em;vertical-align:-1.267113em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.32144em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.8283360000000004em;"><span style="top:-1.882887em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span><span class="mrel mtight">=</span><span class="mord mtight">0</span></span></span></span><span style="top:-3.050005em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.300005em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.267113em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></p><p>DDP 对梯度执行 AllReduce Sum，再除以 World Size，本质上恢复了单卡全局 Batch 的平均梯度。若各 Rank 有效样本数不相等，例如最后一个 Batch 或过滤后样本数不同，就不能机械地对局部平均梯度再做等权平均，需要按样本数加权。</p><p>DDP 的优点是计算路径简单，通信可以通过 Bucket 与反向传播重叠。缺点是每张卡都保存完整的参数、梯度和优化器状态。当模型本身装不下单卡时，单纯增加 DDP Rank 没有帮助。</p><p><img src="/images/posts/ai-infra-series/ddp-no-overlap.svg" alt="反向计算完成后再统一通信" /></p><p><em>图 3：朴素实现把反向计算和通信串行化，AllReduce 阶段计算单元处于等待。</em></p><p><img src="/images/posts/ai-infra-series/ddp-overlap.svg" alt="梯度同步与反向传播重叠" /></p><p><em>图 4：某个 Bucket 的梯度就绪后即可立即发起 AllReduce，与更早层的反向计算重叠。</em></p><p><img src="/images/posts/ai-infra-series/ddp-bucket.svg" alt="梯度分桶" /></p><p><em>图 5：Bucket 太小会产生大量启动延迟，太大又会推迟第一次通信，调优目标是最大化计算—通信重叠。</em></p><h2 id="五-fsdpzero不再让每张卡保存所有状态"><a class="markdownIt-Anchor" href="#五-fsdpzero不再让每张卡保存所有状态"></a> 五、FSDP/ZeRO：不再让每张卡保存所有状态</h2><p>ZeRO 的核心不是一种新的数学训练方法，而是把冗余训练状态分摊到多个 Rank。</p><table><thead><tr><th>阶段</th><th>分片对象</th></tr></thead><tbody><tr><td>ZeRO-1</td><td>优化器状态</td></tr><tr><td>ZeRO-2</td><td>优化器状态 + 梯度</td></tr><tr><td>ZeRO-3</td><td>优化器状态 + 梯度 + 参数</td></tr></tbody></table><p>FSDP Full Shard 与 ZeRO-3 的核心思想接近：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">计算某个 FSDP Unit 前</span><br><span class="line">    AllGather 参数分片</span><br><span class="line">            ↓</span><br><span class="line">       执行 Forward</span><br><span class="line">            ↓</span><br><span class="line">   不再需要时释放完整参数</span><br><span class="line"></span><br><span class="line">Backward 时再次 AllGather</span><br><span class="line">            ↓</span><br><span class="line">     计算局部梯度</span><br><span class="line">            ↓</span><br><span class="line"> ReduceScatter 梯度并保留分片</span><br></pre></td></tr></table></figure><p>相比 DDP，FSDP 用更多、更细粒度的通信换取显存。Unit 包得太大，峰值显存高；包得太小，集合通信次数和启动开销增加。</p><h2 id="六-张量并行把一次矩阵乘拆到多卡"><a class="markdownIt-Anchor" href="#六-张量并行把一次矩阵乘拆到多卡"></a> 六、张量并行：把一次矩阵乘拆到多卡</h2><p>以线性层 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Y</mi><mo>=</mo><mi>X</mi><mi>W</mi></mrow><annotation encoding="application/x-tex">Y=XW</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">Y</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span></span></span></span> 为例，权重可以按列或按行切分。</p><h3 id="61-column-parallel"><a class="markdownIt-Anchor" href="#61-column-parallel"></a> 6.1 Column Parallel</h3><p>按输出维切分 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>W</mi></mrow><annotation encoding="application/x-tex">W</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span></span></span></span>，每张卡计算一部分输出。Q/K/V 投影和 FFN Up/Gate 常使用这种方式。</p><h3 id="62-row-parallel"><a class="markdownIt-Anchor" href="#62-row-parallel"></a> 6.2 Row Parallel</h3><p>按输入维切分 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>W</mi></mrow><annotation encoding="application/x-tex">W</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">W</span></span></span></span>，每张卡产生部分和，最后需要 AllReduce 或 ReduceScatter。Attention 输出投影和 FFN Down 常使用这种方式。</p><p>Transformer 每层都会执行多次 TP 通信，因此它对带宽和延迟极其敏感。把 TP Group 跨越慢速网络，往往会让 GPU 大量等待通信。</p><h2 id="七-流水线并行把层放到不同-stage"><a class="markdownIt-Anchor" href="#七-流水线并行把层放到不同-stage"></a> 七、流水线并行：把层放到不同 Stage</h2><p>PP 将模型层切成多个 Stage，Stage 之间传递激活和梯度，而不是频繁恢复同一矩阵的完整结果。</p><p>它更适合跨节点，但会产生流水线 Bubble。Micro Batch 越多，Bubble 比例通常越低；同时激活缓存、调度复杂度和端到端延迟也会上升。</p><p>GPipe、1F1B 和 Interleaved 1F1B 的差异，本质上是 Forward、Backward 和激活存储的调度方式不同。</p><h2 id="八-moe-与上下文并行"><a class="markdownIt-Anchor" href="#八-moe-与上下文并行"></a> 八、MoE 与上下文并行</h2><h3 id="81-expert-parallel"><a class="markdownIt-Anchor" href="#81-expert-parallel"></a> 8.1 Expert Parallel</h3><p>MoE 的 Router 为每个 Token 选择专家。专家分布在不同 Rank 时，需要 All-to-All：先把 Token 发给目标专家，计算完成后再发送回来。</p><p>性能风险主要来自：</p><ul><li>Token 路由不均衡；</li><li>All-to-All 对网络拓扑敏感；</li><li>热门专家过载；</li><li>Padding 或 Capacity 限制造成浪费和丢弃。</li></ul><h3 id="82-context-parallel"><a class="markdownIt-Anchor" href="#82-context-parallel"></a> 8.2 Context Parallel</h3><p>超长序列训练可以沿 Sequence 维切分。Ring Attention、Ulysses 等方案通过不同通信方式交换 K/V 或中间结果，避免单卡保存全部长序列激活。</p><h2 id="九-并行策略应该映射到拓扑"><a class="markdownIt-Anchor" href="#九-并行策略应该映射到拓扑"></a> 九、并行策略应该映射到拓扑</h2><p>一个常见原则是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">高频、延迟敏感通信 → 放在单机 NVLink 域</span><br><span class="line">低频、大块通信     → 可以跨机</span><br></pre></td></tr></table></figure><p>因此通常：</p><ul><li>TP 优先放单机；</li><li>EP 尽量放高带宽域；</li><li>PP 可以沿节点边界切分；</li><li>DP/FSDP 负责跨副本扩展；</li><li>CP 根据序列长度和网络能力安排。</li></ul><h3 id="91-两机-16-卡例子"><a class="markdownIt-Anchor" href="#91-两机-16-卡例子"></a> 9.1 两机 16 卡例子</h3><p>若每台机器 8 张 GPU，设置 TP=8、DP=2：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">TP Group 0: rank 0-7</span><br><span class="line">TP Group 1: rank 8-15</span><br><span class="line"></span><br><span class="line">DP Groups : (0,8), (1,9), ... (7,15)</span><br></pre></td></tr></table></figure><p>TP 通信留在各自机器内，DP 梯度同步跨机。若模型层数和延迟允许，也可以使用 TP=4、PP=2、DP=2，让每台机器包含两个 TP Group，并在 Stage 间传递激活。</p><h2 id="十-启动和排障同样属于并行设计"><a class="markdownIt-Anchor" href="#十-启动和排障同样属于并行设计"></a> 十、启动和排障同样属于并行设计</h2><p>分布式代码中必须区分：</p><ul><li><code>rank</code>：全局进程编号；</li><li><code>local_rank</code>：当前节点内进程编号；</li><li><code>world_size</code>：总进程数；</li><li><code>process_group</code>：参与某类集合通信的 Rank 集合。</li></ul><p>一卡一进程时，设备绑定通常使用 <code>local_rank</code>，而不是全局 <code>rank</code>。</p><p>遇到 Hang，可以按下面顺序排查：</p><ol><li>每个 Rank 是否执行了相同顺序的集合通信；</li><li><code>world_size</code>、Rank 和 Rendezvous 参数是否一致；</li><li>GPU 与 <code>local_rank</code> 是否正确绑定；</li><li>网卡和 IB/RoCE 配置是否一致；</li><li>是否有某个 Rank 在进入集合通信前异常退出；</li><li>开启 <code>NCCL_DEBUG=INFO</code> 检查拓扑和连接信息；</li><li>用 <code>nccl-tests</code> 将网络问题与训练代码问题分离。</li></ol><p><code>NCCL_P2P_DISABLE</code> 等开关适合定位问题，不应在没有证据时长期关闭高速路径。</p><h2 id="十一-技术-qa"><a class="markdownIt-Anchor" href="#十一-技术-qa"></a> 十一、技术 Q&amp;A</h2><h3 id="q1为什么-ring-allreduce-每卡通信量接近两倍张量大小"><a class="markdownIt-Anchor" href="#q1为什么-ring-allreduce-每卡通信量接近两倍张量大小"></a> Q1：为什么 Ring AllReduce 每卡通信量接近两倍张量大小？</h3><p>ReduceScatter 阶段每卡进行 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">N-1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">1</span></span></span></span> 轮、每轮传输 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi><mi mathvariant="normal">/</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">M/N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span>，通信量为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>N</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo><mi>M</mi><mi mathvariant="normal">/</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">(N-1)M/N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span>；AllGather 阶段相同，两者相加得到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mo stretchy="false">(</mo><mi>N</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo><mi>M</mi><mi mathvariant="normal">/</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">2(N-1)M/N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">2</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span>。GPU 数增大时系数趋近 2，但通信轮数和延迟项仍会增加。</p><h3 id="q2fsdp-为什么用-reducescatter-返回梯度"><a class="markdownIt-Anchor" href="#q2fsdp-为什么用-reducescatter-返回梯度"></a> Q2：FSDP 为什么用 ReduceScatter 返回梯度？</h3><p>参数和优化器状态本来就是分片保存的，每个 Rank 最终只需要自己负责的梯度分片。AllReduce 会让每卡都得到完整梯度，随后又丢弃大部分，既浪费显存又多做了 AllGather；ReduceScatter 则在完成规约的同时直接把目标分片留在对应 Rank。</p><h3 id="q3tp-为什么比-pp-更依赖低延迟-高带宽链路"><a class="markdownIt-Anchor" href="#q3tp-为什么比-pp-更依赖低延迟-高带宽链路"></a> Q3：TP 为什么比 PP 更依赖低延迟、高带宽链路？</h3><p>TP 在几乎每个 Transformer 层的线性层之间插入集合通信，频率高且直接位于关键路径；PP 主要在 Stage 边界传输激活和梯度，次数与 Stage/Micro Batch 调度相关，通信粒度更大、频率相对较低，也更容易通过流水调度重叠。因此 TP 通常限制在 NVLink 域，而 PP 更适合跨节点扩展。</p><h3 id="q4如何区分集合通信-hang-是代码问题还是网络问题"><a class="markdownIt-Anchor" href="#q4如何区分集合通信-hang-是代码问题还是网络问题"></a> Q4：如何区分集合通信 Hang 是代码问题还是网络问题？</h3><p>先确认各 Rank 是否以相同顺序、相同张量形状进入同一 Collective，并检查是否有 Rank 提前异常退出；再用 <code>NCCL_DEBUG=INFO</code> 查看连接和拓扑。若独立 <code>nccl-tests</code> 在相同节点、网卡和数据规模下也失败，应优先排查驱动、IB/RoCE、端口和网络；若测试正常，问题更可能来自程序控制流或 Process Group 使用方式。</p><h2 id="十二-系列导航"><a class="markdownIt-Anchor" href="#十二-系列导航"></a> 十二、系列导航</h2><p>上一篇：<a href="/2026/07/24/llm-serving-as-operating-system/" title="LLM 推理引擎的内存管理与调度：PagedAttention、Prefix Cache 与 Continuous Batching">LLM 推理引擎的内存管理与调度</a></p><p>下一篇：<a href="/2026/07/28/numerical-stability-to-flashattention/" title="Softmax 数值稳定性与 IO-Aware Attention：从在线归一化到 FlashAttention">Softmax 数值稳定性与 IO-Aware Attention</a></p><h2 id="十三-参考资料"><a class="markdownIt-Anchor" href="#十三-参考资料"></a> 十三、参考资料</h2><h3 id="集合通信与工具"><a class="markdownIt-Anchor" href="#集合通信与工具"></a> 集合通信与工具</h3><ul><li><a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/">NVIDIA NCCL Documentation</a>：NCCL API、算法和环境变量。</li><li><a href="https://github.com/NVIDIA/nccl-tests">NCCL Tests</a>：测量集合通信的实际带宽和延迟。</li><li><a href="https://andrew.gibiansky.com/blog/machine-learning/baidu-allreduce/">Bringing HPC Techniques to Deep Learning</a>：Ring AllReduce 的直观解释。</li><li><a href="https://pytorch.org/docs/stable/distributed.html">PyTorch Distributed</a>：Process Group 与集合通信 API。</li></ul><h3 id="并行训练"><a class="markdownIt-Anchor" href="#并行训练"></a> 并行训练</h3><ul><li><a href="https://arxiv.org/abs/1909.08053">Megatron-LM</a>：Transformer 张量并行。</li><li><a href="https://arxiv.org/abs/1910.02054">ZeRO</a>：训练状态分片。</li><li><a href="https://arxiv.org/abs/2304.11277">PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel</a>：FSDP 工程设计。</li><li><a href="https://arxiv.org/abs/1811.06965">GPipe</a>：Pipeline Parallelism 与 Micro Batch。</li><li><a href="https://arxiv.org/abs/2309.14509">DeepSpeed Ulysses</a>：长序列并行。</li><li><a href="https://arxiv.org/abs/2310.01889">Ring Attention</a>：沿上下文维度分布 Attention。</li><li><a href="https://github.com/caomaolufei/AIInfraGuide">AIInfraGuide</a>：集合通信、DDP/FSDP、ZeRO、TP/PP 与多维并行的中文资料，MIT License。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;分布式训练经常被介绍成一串缩写：DP、DDP、FSDP、TP、PP、EP、CP。只记缩写很难形成稳定理解，因为真正决定系统行为的不是名称，而是三个问题：切分了什么、什么时候需要恢复完整数据、恢复时使用什么通信原语。&lt;/p&gt;
&lt;p&gt;本文从 AllReduce 出发，把集合通信</summary>
      
    
    
    
    <category term="AI Infrastructure" scheme="https://zhouzhq2021.github.io/categories/AI-Infrastructure/"/>
    
    
    <category term="AI Infra Engineering Series" scheme="https://zhouzhq2021.github.io/tags/AI-Infra-Engineering-Series/"/>
    
    <category term="Distributed Training" scheme="https://zhouzhq2021.github.io/tags/Distributed-Training/"/>
    
  </entry>
  
  <entry>
    <title>LLM 推理引擎的内存管理与调度：PagedAttention、Prefix Cache 与 Continuous Batching</title>
    <link href="https://zhouzhq2021.github.io/2026/07/24/llm-serving-as-operating-system/"/>
    <id>https://zhouzhq2021.github.io/2026/07/24/llm-serving-as-operating-system/</id>
    <published>2026-07-24T02:00:00.000Z</published>
    <updated>2026-07-30T04:18:20.513Z</updated>
    
    <content type="html"><![CDATA[<p>高性能 LLM 推理引擎面对的问题，与操作系统出奇地相似：有限内存要服务大量生命周期不同的任务；任务随时到达和退出；共享数据应该复用；长任务不能长期阻塞短任务；吞吐、公平性和延迟无法同时无限提高。</p><p>从这个角度看，PagedAttention 不只是一个 Attention Kernel，Continuous Batching 也不只是“把 Batch 做大”。它们共同构成了一套面向 Token 的内存管理和调度系统。</p><p><img src="/images/posts/ai-infra-series/serving-memory-scheduler.svg" alt="推理引擎中的 KV 内存平面与 Token 调度平面" /></p><p><em>图 1：分页内存管理允许请求细粒度进入和退出，动态调度则把释放出来的块转化为更高有效 Batch。</em></p><h2 id="一-概念映射"><a class="markdownIt-Anchor" href="#一-概念映射"></a> 一、概念映射</h2><table><thead><tr><th>操作系统</th><th>LLM 推理引擎</th></tr></thead><tbody><tr><td>虚拟页</td><td>请求的逻辑 KV Block</td></tr><tr><td>物理页</td><td>GPU 上的物理 KV Block</td></tr><tr><td>页表</td><td>Block Table</td></tr><tr><td>写时复制</td><td>并行采样中的 KV Copy-on-Write</td></tr><tr><td>引用计数</td><td>共享 KV Block 的生命周期</td></tr><tr><td>LRU</td><td>Prefix Cache 淘汰</td></tr><tr><td>进程队列</td><td>Waiting/Running 请求队列</td></tr><tr><td>时间片或预算</td><td>每轮可调度 Token Budget</td></tr><tr><td>抢占</td><td>释放 KV 后重计算或换出</td></tr></tbody></table><p>这个类比不是说 vLLM 内部实现了完整操作系统，而是说明两者面对的是同一类资源分配问题。</p><h2 id="二-连续分配为什么会浪费-kv-cache"><a class="markdownIt-Anchor" href="#二-连续分配为什么会浪费-kv-cache"></a> 二、连续分配为什么会浪费 KV Cache</h2><p>假设系统为每个请求提前预留一段连续 KV Cache。问题很快出现：</p><h3 id="21-内部碎片"><a class="markdownIt-Anchor" href="#21-内部碎片"></a> 2.1 内部碎片</h3><p>系统按照最大输出长度预留，但请求可能提前遇到 EOS。没有使用的预留空间无法立刻服务其他请求。</p><h3 id="22-外部碎片"><a class="markdownIt-Anchor" href="#22-外部碎片"></a> 2.2 外部碎片</h3><p>请求不断到达和结束后，显存中会留下大小不一的空洞。空闲空间总量也许足够，却找不到一段满足新请求的连续区域。</p><h3 id="23-过度预留"><a class="markdownIt-Anchor" href="#23-过度预留"></a> 2.3 过度预留</h3><p>生成长度不可预测。如果不提前预留，Decode 增长时可能搬迁；如果按上限预留，又会浪费大量空间。</p><p>KV Cache 的困难不在于一次分配，而在于它会随着每个请求逐 Token 增长。</p><h2 id="三-pagedattention把连续逻辑空间映射到离散物理块"><a class="markdownIt-Anchor" href="#三-pagedattention把连续逻辑空间映射到离散物理块"></a> 三、PagedAttention：把连续逻辑空间映射到离散物理块</h2><p>PagedAttention 将 KV Cache 切成固定 Token 数的 Block。请求看到的是连续的逻辑块，实际物理块可以分散在显存中。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">请求逻辑块:  [0] [1] [2] [3]</span><br><span class="line">                │   │   │   │</span><br><span class="line">Block Table:   12   3  27   8</span><br><span class="line">                │   │   │   │</span><br><span class="line">物理块池: ... [3] ... [8] ... [12] ... [27] ...</span><br></pre></td></tr></table></figure><p>Attention Kernel 根据逻辑块号查询 Block Table，再计算物理块地址和块内偏移。这样请求不需要一段连续的物理显存。</p><h3 id="31-碎片如何变化"><a class="markdownIt-Anchor" href="#31-碎片如何变化"></a> 3.1 碎片如何变化</h3><ul><li>外部碎片基本被固定大小块消除；</li><li>每个请求只有最后一个未填满 Block 产生有限内部浪费；</li><li>Decode 时按需申请新块，不必按最大长度提前预留；</li><li>请求结束后，物理块可以直接回到空闲池。</li></ul><p>Block Size 仍然需要权衡：</p><ul><li>太大：最后一块浪费增加，细粒度复用下降；</li><li>太小：Block Table 更大，管理和寻址开销增加。</li></ul><h2 id="四-共享-引用计数和-copy-on-write"><a class="markdownIt-Anchor" href="#四-共享-引用计数和-copy-on-write"></a> 四、共享、引用计数和 Copy-on-Write</h2><p>分页之后，不同请求的 Block Table 可以指向同一个物理 KV Block。</p><p>典型场景包括：</p><ul><li>多个请求拥有相同系统 Prompt；</li><li>Beam Search 或并行采样共享同一 Prompt；</li><li>多轮对话复用已经计算过的前缀。</li></ul><p>共享块需要引用计数。只读阶段多个请求共同引用；当某个分支需要修改共享尾块时，再执行 Copy-on-Write：为它分配新块并复制必要内容。</p><p>这与 <code>fork()</code> 后父子进程共享内存页、写入时再复制的思想一致。</p><h2 id="五-prefix-cache把共享从同时发生扩展到先后发生"><a class="markdownIt-Anchor" href="#五-prefix-cache把共享从同时发生扩展到先后发生"></a> 五、Prefix Cache：把共享从同时发生扩展到先后发生</h2><p>PagedAttention 解决了“如何共享物理块”，Prefix Cache 进一步解决“如何发现历史请求已经计算过相同前缀”。</p><h3 id="51-vllm-的块哈希"><a class="markdownIt-Anchor" href="#51-vllm-的块哈希"></a> 5.1 vLLM 的块哈希</h3><p>一个块的哈希不仅取决于当前 Token，还要包含父块哈希。这样同一段 Token 出现在不同上下文中时，不会被错误认为是相同前缀。</p><p>可以把第 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi></mrow><annotation encoding="application/x-tex">i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.65952em;vertical-align:0em;"></span><span class="mord mathnormal">i</span></span></span></span> 个块的键抽象为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>h</mi><mi>i</mi></msub><mo>=</mo><mi>H</mi><mo stretchy="false">(</mo><msub><mi>h</mi><mrow><mi>i</mi><mo>−</mo><mn>1</mn></mrow></msub><mo separator="true">,</mo><mtext>  </mtext><mi>t</mi><mi>o</mi><mi>k</mi><mi>e</mi><mi>n</mi><msub><mi>s</mi><mi>i</mi></msub><mo separator="true">,</mo><mtext>  </mtext><mi>m</mi><mi>o</mi><mi>d</mi><mi>e</mi><mi>l</mi><mi mathvariant="normal">_</mi><mi>i</mi><mi>d</mi><mo separator="true">,</mo><mtext>  </mtext><mi>a</mi><mi>d</mi><mi>a</mi><mi>p</mi><mi>t</mi><mi>e</mi><mi>r</mi><mi mathvariant="normal">_</mi><mi>i</mi><mi>d</mi><mo separator="true">,</mo><mtext>  </mtext><mi>e</mi><mi>x</mi><mi>t</mi><mi>r</mi><mi>a</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">h_i=H(h_{i-1},\;tokens_i,\;model\_id,\;adapter\_id,\;extra)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">h</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.06em;vertical-align:-0.31em;"></span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">h</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.311664em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.208331em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">m</span><span class="mord mathnormal">o</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathnormal">i</span><span class="mord mathnormal">d</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">a</span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">p</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathnormal">i</span><span class="mord mathnormal">d</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">e</span><span class="mord mathnormal">x</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">a</span><span class="mclose">)</span></span></span></span></span></p><p><code>tokens_i</code> 相同但父哈希不同，说明它们之前的上下文不同，不能共享同一份 KV。模型、LoRA Adapter、Cache Salt 等会改变计算语义的信息也必须进入键空间。实现通常只缓存已经填满的块，因为未填满尾块还会在 Decode 中继续写入，不适合作为稳定的共享对象。</p><p>新请求到来后，系统逐块查询缓存：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">命中块 → 增加引用计数 → 跳过对应 Prefill</span><br><span class="line">未命中 → 分配新块       → 正常计算</span><br></pre></td></tr></table></figure><p>Prefix Cache 主要降低重复前缀的 Prefill，因此最直接改善 TTFT，而不是 Decode 的 TPOT。</p><h3 id="52-lru-为什么还要结合引用计数"><a class="markdownIt-Anchor" href="#52-lru-为什么还要结合引用计数"></a> 5.2 LRU 为什么还要结合引用计数</h3><p>正在被请求使用的块不能淘汰，即使它很久没有被“新请求”命中。引用计数为 0 的缓存块才进入可淘汰集合，然后按 LRU 等策略回收。</p><h3 id="53-radixattention"><a class="markdownIt-Anchor" href="#53-radixattention"></a> 5.3 RadixAttention</h3><p>SGLang 使用 Radix Tree 表示 Token 前缀。公共前缀天然共享树路径，适合结构化、多轮和前缀复用明显的工作负载。</p><p>Hash Block 与 Radix Tree 没有绝对优劣：前者容易与分页块池结合，后者能更直接表示变长前缀结构。实际收益最终取决于 Prompt 是否真的重复。</p><h2 id="六-static-batching-的槽位空洞与队头阻塞"><a class="markdownIt-Anchor" href="#六-static-batching-的槽位空洞与队头阻塞"></a> 六、Static Batching 的槽位空洞与队头阻塞</h2><p><img src="/images/posts/ai-infra-series/continuous-batching-timeline.svg" alt="Static Batching 与 Continuous Batching 时间线" /></p><p><em>图 2：Static Batching 中短请求完成后的槽位保持空闲；Continuous Batching 在迭代边界立即补入新请求。</em></p><p>假设一个 Batch 有四个请求，分别需要生成 20、50、200、500 个 Token。Static Batching 通常要等最慢的请求完成，短请求结束后的槽位无法立即加入新任务。</p><p>结果是：</p><ul><li>短请求被长请求拖累；</li><li>已完成请求占据 Batch 位置；</li><li>GPU 有效 Batch 逐渐缩小；</li><li>尾延迟和吞吐同时恶化。</li></ul><p>这类似让一组任务组成固定班级，必须等所有成员完成才能接收下一批。</p><h2 id="七-continuous-batching每个-token-都是一个调度点"><a class="markdownIt-Anchor" href="#七-continuous-batching每个-token-都是一个调度点"></a> 七、Continuous Batching：每个 Token 都是一个调度点</h2><p>Continuous Batching 在每次模型迭代后重新安排请求：</p><ol><li>移除已经结束的请求；</li><li>释放或保留它们的 KV Block；</li><li>从等待队列选择新请求；</li><li>为本轮请求分配 Token 和 KV 预算；</li><li>组成新的 Batch 执行。</li></ol><p>这是一种 Iteration-level Scheduling。调度单位不再是“完整请求 Batch”，而是“一轮需要处理的 Token”。</p><p>PagedAttention 与 Continuous Batching 互相依赖：如果 KV Cache 只能整段连续分配，请求频繁进入退出会带来昂贵搬迁；如果没有动态调度，分页节省的空间也难以转化成更高并发。</p><p>调度循环可以简化为下面的伪代码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">schedule_step</span>(<span class="params">waiting, running, token_budget, block_pool</span>):</span><br><span class="line">    batch = []</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Decode 请求优先获得本轮所需 Token，保护输出流畅度。</span></span><br><span class="line">    <span class="keyword">for</span> req <span class="keyword">in</span> running:</span><br><span class="line">        <span class="keyword">if</span> req.finished:</span><br><span class="line">            block_pool.release(req.blocks)</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">if</span> token_budget &gt;= <span class="number">1</span> <span class="keyword">and</span> block_pool.can_append(req):</span><br><span class="line">            batch.append((req, <span class="number">1</span>))</span><br><span class="line">            token_budget -= <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 剩余预算用于新 Prefill 或尚未完成的 Prefill Chunk。</span></span><br><span class="line">    <span class="keyword">while</span> waiting <span class="keyword">and</span> token_budget &gt; <span class="number">0</span>:</span><br><span class="line">        req = waiting.peek()</span><br><span class="line">        computable = <span class="built_in">min</span>(req.remaining_prompt_tokens, token_budget)</span><br><span class="line">        allocatable = block_pool.max_allocatable_tokens(req)</span><br><span class="line">        scheduled = <span class="built_in">min</span>(computable, allocatable)</span><br><span class="line">        <span class="keyword">if</span> scheduled == <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        batch.append((req, scheduled))</span><br><span class="line">        token_budget -= scheduled</span><br><span class="line">        update_request_state(req, scheduled, waiting, running)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> batch</span><br></pre></td></tr></table></figure><p>真实 vLLM V1 的策略比这段伪代码复杂，且版本会持续变化；这里想表达的稳定结构是：请求状态、Token 预算和 KV Block 预算必须在一次决策中共同满足。</p><h2 id="八-长-prefill-为什么会干扰-decode"><a class="markdownIt-Anchor" href="#八-长-prefill-为什么会干扰-decode"></a> 八、长 Prefill 为什么会干扰 Decode</h2><p>Decode 请求每轮只需要少量 Token，希望稳定、频繁地获得 GPU 时间。一个超长 Prompt 的 Prefill 如果一次执行完，会占据 GPU 较长时间，导致同批 Decode 请求的 TPOT 突然升高。</p><h3 id="81-chunked-prefill"><a class="markdownIt-Anchor" href="#81-chunked-prefill"></a> 8.1 Chunked Prefill</h3><p>Chunked Prefill 把长 Prompt 切成多个片段：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">长 Prefill: [========================]</span><br><span class="line"></span><br><span class="line">切块后:     [====] [====] [====] [====]</span><br><span class="line">Decode:        |      |      |      |</span><br></pre></td></tr></table></figure><p>它可能用更长一些的 TTFT，换取更平滑的 Decode TPOT 和更好的调度公平性；实际变化取决于 Token Budget、队列状态和请求长度分布。</p><h3 id="82-token-budget"><a class="markdownIt-Anchor" href="#82-token-budget"></a> 8.2 Token Budget</h3><p>统一调度器可以把每轮处理能力表示为 Token Budget：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">本轮预算 = Decode Token + 新请求 Prefill Token + Chunked Prefill Token</span><br></pre></td></tr></table></figure><p>当长 Prefill 超过剩余预算时，只处理可以容纳的部分，下轮从 <code>num_computed_tokens</code> 继续。这比硬编码“先 Prefill 还是先 Decode”更容易统一不同请求状态。</p><h2 id="九-抢占显存不够时让谁退场"><a class="markdownIt-Anchor" href="#九-抢占显存不够时让谁退场"></a> 九、抢占：显存不够时让谁退场</h2><p>当物理 KV Block 用尽，调度器需要释放资源。常见恢复方式有：</p><ul><li>重计算：丢弃某请求的 KV，之后重新 Prefill；</li><li>换出：将 KV 搬到 CPU 内存，需要时再搬回；</li><li>延迟新请求：保持运行请求不变，让新请求继续等待。</li></ul><p>选择取决于 PCIe 带宽、Prompt 长度、重计算成本和延迟 SLO。重计算看似浪费算力，但可能比大规模 CPU–GPU KV 交换更稳定。</p><h2 id="十-吞吐-公平性与-slo"><a class="markdownIt-Anchor" href="#十-吞吐-公平性与-slo"></a> 十、吞吐、公平性与 SLO</h2><p>调度器至少要同时考虑：</p><ul><li>Batch 越大，权重读取的摊销通常越好；</li><li>Batch 太大，单轮时间和 TPOT 上升；</li><li>长 Prompt 有利于填满计算，却可能阻塞 Decode；</li><li>高缓存命中可以降低 TTFT，但缓存本身占用显存；</li><li>抢占能避免 OOM，但会引入重计算或传输。</li></ul><p>生产环境真正应该优化的是 Goodput：在 TTFT、TPOT 和错误率满足 SLO 的前提下，系统完成了多少有效请求。</p><h2 id="十一-源码阅读路线"><a class="markdownIt-Anchor" href="#十一-源码阅读路线"></a> 十一、源码阅读路线</h2><p>阅读 vLLM 时，可以围绕三个问题定位，而不是从仓库第一行开始：</p><ol><li>KV Block 如何申请、引用和释放；</li><li>Scheduler 每轮如何决定哪些请求处理多少 Token；</li><li>Attention Backend 如何根据 Block Table 读取 KV。</li></ol><p>源码会持续演进，类名和文件路径可能变化，但“块池—请求状态—调度预算—执行后端”这四个角色比较稳定。</p><h2 id="十二-技术-qa"><a class="markdownIt-Anchor" href="#十二-技术-qa"></a> 十二、技术 Q&amp;A</h2><h3 id="q1pagedattention-是否完全消除了-kv-cache-浪费"><a class="markdownIt-Anchor" href="#q1pagedattention-是否完全消除了-kv-cache-浪费"></a> Q1：PagedAttention 是否完全消除了 KV Cache 浪费？</h3><p>它通过固定块和逻辑—物理映射基本消除了连续分配造成的外部碎片，也避免按最大长度过度预留；但每个请求的最后一个 Block 仍可能未填满，Block Table、引用计数和间接寻址也有管理成本。Block 越大尾块浪费越高，越小元数据和调度开销越高。</p><h3 id="q2prefix-cache-为什么主要改善-ttft且块哈希必须包含父块哈希"><a class="markdownIt-Anchor" href="#q2prefix-cache-为什么主要改善-ttft且块哈希必须包含父块哈希"></a> Q2：Prefix Cache 为什么主要改善 TTFT，且块哈希必须包含父块哈希？</h3><p>命中缓存后，引擎可以跳过公共前缀的 Prefill，因此首 Token 更早产生；后续 Decode 仍要逐轮读取权重和历史 KV，所以 TPOT 通常不会直接缩短。由于 Transformer 的 KV 取决于当前 Token 之前的完整上下文，相同 Token Block 出现在不同父前缀后会产生不同 KV。将父块哈希纳入链式哈希，才能确保复用的是从起点到当前块都相同的完整前缀。</p><h3 id="q3continuous-batching-为什么依赖分页式-kv-管理"><a class="markdownIt-Anchor" href="#q3continuous-batching-为什么依赖分页式-kv-管理"></a> Q3：Continuous Batching 为什么依赖分页式 KV 管理？</h3><p>迭代级调度要求请求可以每轮加入、退出和增长。若每个请求必须拥有一段按最大长度预留的连续 KV，新请求补位和旧请求释放会造成大量碎片或数据搬迁。固定块把请求生命周期的变化转化为块的申请与释放，使动态补位和显存回收的成本可控。</p><h3 id="q4chunked-prefill-与-token-budget-共同决定了什么权衡"><a class="markdownIt-Anchor" href="#q4chunked-prefill-与-token-budget-共同决定了什么权衡"></a> Q4：Chunked Prefill 与 Token Budget 共同决定了什么权衡？</h3><p>Chunked Prefill 把长 Prefill 拆成较短片段，让 Decode 在片段之间获得 GPU 时间，从而降低 TPOT 抖动和 Head-of-Line Blocking；代价是长请求需要更多调度轮才能完成 Prefill，TTFT 可能增加。较大的 Token Budget 有利于形成大 Batch、提高吞吐并减少切块次数，但也会拉长单轮执行时间。两者需要结合请求长度分布和 TTFT/TPOT SLO 进行并发扫描，而不是独立地取极值。</p><h2 id="十三-系列导航"><a class="markdownIt-Anchor" href="#十三-系列导航"></a> 十三、系列导航</h2><p>上一篇：<a href="/2026/07/22/ai-infra-roofline-profiling/" title="GPU 性能分析方法：Roofline 模型、CUDA 内存层次与 Nsight">GPU 性能分析方法</a></p><p>下一篇：<a href="/2026/07/26/distributed-training-from-allreduce/" title="分布式训练的通信模型：从集合通信到多维并行">分布式训练的通信模型</a></p><h2 id="十四-参考资料"><a class="markdownIt-Anchor" href="#十四-参考资料"></a> 十四、参考资料</h2><h3 id="论文与系统"><a class="markdownIt-Anchor" href="#论文与系统"></a> 论文与系统</h3><ul><li><a href="https://arxiv.org/abs/2309.06180">Efficient Memory Management for LLM Serving with PagedAttention</a>：分页式 KV Cache 管理。</li><li><a href="https://www.usenix.org/conference/osdi22/presentation/yu">Orca</a>：Iteration-level Scheduling。</li><li><a href="https://arxiv.org/abs/2312.07104">SGLang: Efficient Execution of Structured Language Model Programs</a>：RadixAttention 和结构化生成执行。</li><li><a href="https://arxiv.org/abs/2308.16369">SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked Prefills</a>：Chunked Prefill。</li><li><a href="https://arxiv.org/abs/2401.09670">DistServe</a>：Prefill/Decode 解耦与 Goodput。</li></ul><h3 id="文档与源码"><a class="markdownIt-Anchor" href="#文档与源码"></a> 文档与源码</h3><ul><li><a href="https://docs.vllm.ai/en/latest/design/paged_attention.html">vLLM PagedAttention Design</a>：Kernel 和块寻址设计。</li><li><a href="https://docs.vllm.ai/en/latest/design/prefix_caching.html">vLLM Automatic Prefix Caching</a>：前缀块哈希和缓存语义。</li><li><a href="https://blog.vllm.ai/2025/01/27/v1-alpha-release.html">vLLM V1 Alpha Release</a>：统一调度、Prefix Cache 和 CUDA Graph。</li><li><a href="https://github.com/vllm-project/vllm/tree/main/vllm/v1/core">vLLM V1 Core Source</a>：KV Cache Manager、Block Pool 和 Scheduler。</li><li><a href="https://github.com/caomaolufei/AIInfraGuide">AIInfraGuide</a>：PagedAttention、Continuous Batching、Prefix Cache 与 Chunked Prefill 的中文推导和源码导读，MIT License。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;高性能 LLM 推理引擎面对的问题，与操作系统出奇地相似：有限内存要服务大量生命周期不同的任务；任务随时到达和退出；共享数据应该复用；长任务不能长期阻塞短任务；吞吐、公平性和延迟无法同时无限提高。&lt;/p&gt;
&lt;p&gt;从这个角度看，PagedAttention 不只是一个 Att</summary>
      
    
    
    
    <category term="AI Infrastructure" scheme="https://zhouzhq2021.github.io/categories/AI-Infrastructure/"/>
    
    
    <category term="AI Infra Engineering Series" scheme="https://zhouzhq2021.github.io/tags/AI-Infra-Engineering-Series/"/>
    
    <category term="LLM Serving" scheme="https://zhouzhq2021.github.io/tags/LLM-Serving/"/>
    
  </entry>
  
  <entry>
    <title>GPU 性能分析方法：Roofline 模型、CUDA 内存层次与 Nsight</title>
    <link href="https://zhouzhq2021.github.io/2026/07/22/ai-infra-roofline-profiling/"/>
    <id>https://zhouzhq2021.github.io/2026/07/22/ai-infra-roofline-profiling/</id>
    <published>2026-07-22T02:00:00.000Z</published>
    <updated>2026-07-30T04:18:20.513Z</updated>
    
    <content type="html"><![CDATA[<p>看到 GPU 利用率不高，常见反应是增大 Block、提高 Occupancy、做算子融合或者直接换成 Triton。但这些动作如果没有性能模型支撑，很可能只是把瓶颈从一个地方搬到另一个地方。</p><p>可靠的优化过程应该像故障诊断：先建立理论上限，再用工具验证瓶颈，最后用实验确认收益。本文用同一套方法串起 Vector Add、Reduce、GEMM、Softmax、FlashAttention 和 LLM Decode。</p><p><img src="/images/posts/ai-infra-series/gpu-hardware-architecture.png" alt="GPU 硬件结构" /></p><p><em>图 1：GPU 性能分析的对象不是抽象的“核”，而是 SM、执行单元和多级存储共同组成的系统。</em></p><h2 id="一-性能问题先写成两个数字"><a class="markdownIt-Anchor" href="#一-性能问题先写成两个数字"></a> 一、性能问题先写成两个数字</h2><p>对一个 Kernel，先估算：</p><ul><li>总计算量：FLOPs；</li><li>最少数据搬运量：Bytes。</li></ul><p>两者之比称为算术强度：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo>=</mo><mfrac><mtext>FLOPs</mtext><mtext>Bytes</mtext></mfrac></mrow><annotation encoding="application/x-tex">I=\frac{\text{FLOPs}}{\text{Bytes}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.2407700000000004em;vertical-align:-0.8804400000000001em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.36033em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Bytes</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">FLOPs</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804400000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p>如果一个操作只做很少计算，却要从 HBM 读取大量数据，它通常受带宽限制；如果每次读取的数据会被重复使用很多次，它更可能受计算吞吐限制。</p><h3 id="11-vector-add"><a class="markdownIt-Anchor" href="#11-vector-add"></a> 1.1 Vector Add</h3><p>计算 <code>C[i] = A[i] + B[i]</code>，每个元素读取两个 FP32、写回一个 FP32，共 12 Bytes，只执行一次加法：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo>≈</mo><mfrac><mn>1</mn><mn>12</mn></mfrac><mtext> FLOPs/Byte</mtext></mrow><annotation encoding="application/x-tex">I\approx\frac{1}{12}\text{ FLOPs/Byte}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.00744em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.32144em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span><span class="mord">2</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord text"><span class="mord"> FLOPs/Byte</span></span></span></span></span></span></p><p>这是典型的 Memory Bound 操作。继续增加算术单元不会明显加速，关键是合并访问、足够并发和接近峰值显存带宽。</p><h3 id="12-gemm"><a class="markdownIt-Anchor" href="#12-gemm"></a> 1.2 GEMM</h3><p>矩阵乘法包含大量乘加。如果通过 Shared Memory 和寄存器 Tiling 让数据被多次复用，算术强度可以大幅提高，最终接近 Compute Bound。</p><p>这两类操作构成了 Roofline 图上的两个极端。</p><h2 id="二-roofline先判断优化方向"><a class="markdownIt-Anchor" href="#二-roofline先判断优化方向"></a> 二、Roofline：先判断优化方向</h2><p><img src="/images/posts/ai-infra-series/cuda-memory-hierarchy.png" alt="CUDA 内存层次" /></p><p><em>图 2：Roofline 中的“Bytes”来自不同存储层次；减少 HBM 流量和提高片上复用往往比减少一条算术指令更重要。</em></p><p>Roofline 模型给出的性能上限为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>P</mi><mo>≤</mo><mi>min</mi><mo>⁡</mo><mo stretchy="false">(</mo><msub><mi>P</mi><mrow><mi>p</mi><mi>e</mi><mi>a</mi><mi>k</mi></mrow></msub><mo separator="true">,</mo><mi>I</mi><mo>×</mo><mi>B</mi><msub><mi>W</mi><mrow><mi>p</mi><mi>e</mi><mi>a</mi><mi>k</mi></mrow></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">P\leq\min(P_{peak}, I\times BW_{peak})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8193em;vertical-align:-0.13597em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.036108em;vertical-align:-0.286108em;"></span><span class="mop">min</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.036108em;vertical-align:-0.286108em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span></span></p><p>其中 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mrow><mi>p</mi><mi>e</mi><mi>a</mi><mi>k</mi></mrow></msub></mrow><annotation encoding="application/x-tex">P_{peak}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span> 是峰值计算吞吐，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>B</mi><msub><mi>W</mi><mrow><mi>p</mi><mi>e</mi><mi>a</mi><mi>k</mi></mrow></msub></mrow><annotation encoding="application/x-tex">BW_{peak}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span> 是峰值带宽。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">性能</span><br><span class="line">  ^                    ───────── 计算上限</span><br><span class="line">  |                 /</span><br><span class="line">  |              /</span><br><span class="line">  |           /      转折点</span><br><span class="line">  |        /</span><br><span class="line">  |     / 带宽上限</span><br><span class="line">  +----------------------------&gt; 算术强度</span><br></pre></td></tr></table></figure><p>它不能直接告诉我们哪一行代码有问题，但能排除错误方向：</p><ul><li>位于斜线区域：优先减少 HBM 访问、提高合并率和数据复用；</li><li>位于水平区域：优先提高 Tensor Core 使用率、指令并行和计算占用；</li><li>两个上限都远未达到：可能存在同步、分支、Launch、负载不均或数据准备问题。</li></ul><h3 id="21-一个带宽核算例子"><a class="markdownIt-Anchor" href="#21-一个带宽核算例子"></a> 2.1 一个带宽核算例子</h3><p>对长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> 的 FP32 Vector Add，有效数据量为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>12</mn><mi>N</mi></mrow><annotation encoding="application/x-tex">12N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span> Bytes。如果 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>=</mo><msup><mn>2</mn><mn>28</mn></msup></mrow><annotation encoding="application/x-tex">N=2^{28}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8141079999999999em;vertical-align:0em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141079999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mtight">8</span></span></span></span></span></span></span></span></span></span></span></span>，Kernel 用时 2.4ms，则有效带宽约为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>B</mi><msub><mi>W</mi><mrow><mi>e</mi><mi>f</mi><mi>f</mi></mrow></msub><mo>=</mo><mfrac><mrow><mn>12</mn><mo>×</mo><msup><mn>2</mn><mn>28</mn></msup></mrow><mrow><mn>2.4</mn><mo>×</mo><mn>1</mn><msup><mn>0</mn><mrow><mo>−</mo><mn>3</mn></mrow></msup></mrow></mfrac><mo>≈</mo><mn>1.34</mn><mtext> TB/s</mtext></mrow><annotation encoding="application/x-tex">BW_{eff}=\frac{12\times2^{28}}{2.4\times10^{-3}}\approx1.34\text{ TB/s}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.260438em;vertical-align:-0.7693300000000001em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.491108em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord">.</span><span class="mord">4</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.740108em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">3</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141079999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mtight">8</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.7693300000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">.</span><span class="mord">3</span><span class="mord">4</span><span class="mord text"><span class="mord"> TB/s</span></span></span></span></span></span></p><p>这个数字必须与目标 GPU 在同一访问模式下的可达带宽比较，而不是简单除以产品手册峰值。如果向量起始地址未对齐、线程访问跨越更多内存事务或时钟频率受限，实际可达上限会下降。</p><p>类似地，GEMM 更适合报告 TFLOPS：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>P</mi><mrow><mi>g</mi><mi>e</mi><mi>m</mi><mi>m</mi></mrow></msub><mo>=</mo><mfrac><mrow><mn>2</mn><mi>M</mi><mi>N</mi><mi>K</mi></mrow><mi>t</mi></mfrac></mrow><annotation encoding="application/x-tex">P_{gemm}=\frac{2MNK}{t}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">m</span><span class="mord mathnormal mtight">m</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.04633em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.36033em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">t</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord mathnormal" style="margin-right:0.07153em;">K</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p>统一用“完成了多少必要工作/消耗了多少时间”描述性能，才能跨实现比较。</p><h2 id="三-建立可信基线"><a class="markdownIt-Anchor" href="#三-建立可信基线"></a> 三、建立可信基线</h2><p>优化前至少要保证四件事：</p><ol><li>与参考实现比较正确性；</li><li>预热，排除首次初始化和编译；</li><li>使用 CUDA Event 测量 GPU 时间；</li><li>多次执行并报告中位数或分位数。</li></ol><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cudaEventRecord</span>(start);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; repeat; ++i) &#123;</span><br><span class="line">    kernel&lt;&lt;&lt;grid, block&gt;&gt;&gt;(...);</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">cudaEventRecord</span>(stop);</span><br><span class="line"><span class="built_in">cudaEventSynchronize</span>(stop);</span><br></pre></td></tr></table></figure><p>如果每轮都调用 <code>cudaDeviceSynchronize()</code>，测到的可能不再是实际流水执行方式。端到端延迟和纯 Kernel 延迟也应分开报告。</p><h2 id="四-同一套方法看五类算子"><a class="markdownIt-Anchor" href="#四-同一套方法看五类算子"></a> 四、同一套方法看五类算子</h2><h3 id="41-reduce并行度和同步的平衡"><a class="markdownIt-Anchor" href="#41-reduce并行度和同步的平衡"></a> 4.1 Reduce：并行度和同步的平衡</h3><p><img src="/images/posts/ai-infra-series/reduce-warp-shuffle.png" alt="Warp Shuffle 规约" /></p><p><em>图 3：Warp Shuffle 将 Warp 内数据交换留在寄存器路径中，再用少量 Shared Memory 合并多个 Warp 的结果。</em></p><p>Reduce 的输入只读取一次，计算量很低，通常受带宽和规约开销影响。典型优化路径是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">全局原子加</span><br><span class="line">→ Shared Memory 树形规约</span><br><span class="line">→ 消除 Warp Divergence</span><br><span class="line">→ 每线程处理多个元素</span><br><span class="line">→ Warp Shuffle</span><br><span class="line">→ 向量化加载</span><br></pre></td></tr></table></figure><p>每一步都应该对应一个可验证的假设。例如使用 Warp Shuffle 的目的，不是“它更高级”，而是减少 Shared Memory 访问和 Block 内同步。</p><h3 id="42-gemm用空间换数据复用"><a class="markdownIt-Anchor" href="#42-gemm用空间换数据复用"></a> 4.2 GEMM：用空间换数据复用</h3><p><img src="/images/posts/ai-infra-series/gemm-thread-tiling.png" alt="GEMM Thread Tiling" /></p><p><em>图 4：Thread Tiling 让一个线程计算多个输出元素，提高从 Shared Memory 到寄存器的数据复用。</em></p><p>朴素 GEMM 中，每个线程独立从全局内存读取一行和一列，数据重复读取严重。Block Tiling 把 A、B 的 Tile 搬入 Shared Memory；Thread Tiling 再把局部数据放入寄存器。</p><p>优化的代价也很明确：</p><ul><li>Tile 太小：复用不足；</li><li>Tile 太大：Shared Memory 占用上升；</li><li>每线程输出太多：寄存器压力增大；</li><li>寄存器溢出：Local Memory 访问反而拖慢程序；</li><li>Occupancy 降低：可能无法隐藏访存延迟。</li></ul><p>因此 Occupancy 不是目标，而是资源预算的一部分。</p><p><img src="/images/posts/ai-infra-series/coalesced-access.png" alt="合并访存与非合并访存" /></p><p><em>图 5：线程编号连续并不自动保证合并访存；关键是一个 Warp 请求的地址能否落入尽量少的内存事务。</em></p><h3 id="43-softmax多遍扫描与规约"><a class="markdownIt-Anchor" href="#43-softmax多遍扫描与规约"></a> 4.3 Softmax：多遍扫描与规约</h3><p>数值稳定的 Softmax 通常要完成：</p><ol><li>求最大值；</li><li>计算指数和；</li><li>归一化输出。</li></ol><p>这意味着多次读取同一行。Online Softmax 把最大值和指数和表示为可合并状态，减少扫描次数，并为 FlashAttention 的分块计算奠定基础。</p><h3 id="44-flashattention优化的不是-flops"><a class="markdownIt-Anchor" href="#44-flashattention优化的不是-flops"></a> 4.4 FlashAttention：优化的不是 FLOPs</h3><p><img src="/images/posts/ai-infra-series/flashattention-io.jpg" alt="标准 Attention 与 FlashAttention 的 IO 差异" /></p><p><em>图 6：FlashAttention 的主要收益来自避免将完整 Attention 中间矩阵写回 HBM。</em></p><p>标准 Attention 会产生并写回 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi><mo>×</mo><mi>S</mi></mrow><annotation encoding="application/x-tex">S\times S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 的分数或概率矩阵。FlashAttention 通过 Tiling 和 Online Softmax，让中间块停留在片上存储中。</p><p>它并没有改变精确 Attention 的数学定义，核心收益来自降低 HBM IO。这个例子非常适合说明：理论 FLOPs 相同的两个算法，真实速度可以完全不同。</p><h3 id="45-decodekernel-很快系统仍可能很慢"><a class="markdownIt-Anchor" href="#45-decodekernel-很快系统仍可能很慢"></a> 4.5 Decode：Kernel 很快，系统仍可能很慢</h3><p>LLM Decode 中的单个 Kernel 规模较小，CPU Launch、Python 调度和框架开销可能占据明显比例。此时只看 Kernel 内部指标是不够的，需要用 Nsight Systems 检查 GPU 时间线中的空洞。</p><p>CUDA Graph 可以录制并重放一组 Kernel，减少重复 Launch 开销。但它通常要求地址和形状相对稳定，也会增加捕获时间、显存占用和调试复杂度。</p><h2 id="五-nsight-systems先看全局时间线"><a class="markdownIt-Anchor" href="#五-nsight-systems先看全局时间线"></a> 五、Nsight Systems：先看全局时间线</h2><p>常用命令示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nsys profile --trace=cuda,nvtx,osrt -o report ./app</span><br></pre></td></tr></table></figure><p>重点检查：</p><ul><li>Kernel 之间是否存在明显 GPU Idle Gap；</li><li>CPU 是否在数据预处理、Python 或锁等待中停顿；</li><li>Host-to-Device 拷贝能否与计算重叠；</li><li>NCCL 通信是否与反向计算重叠；</li><li>是否存在大量极短 Kernel；</li><li>多个 CUDA Stream 是否真正并行。</li></ul><p>Nsight Systems 适合回答“时间花在哪个阶段”，但不能完整解释某个 Kernel 为什么没有达到峰值。</p><h2 id="六-nsight-compute下钻到单个-kernel"><a class="markdownIt-Anchor" href="#六-nsight-compute下钻到单个-kernel"></a> 六、Nsight Compute：下钻到单个 Kernel</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ncu --<span class="built_in">set</span> full -o kernel-report ./app</span><br></pre></td></tr></table></figure><p>建议按下面顺序阅读：</p><h3 id="61-speed-of-light"><a class="markdownIt-Anchor" href="#61-speed-of-light"></a> 6.1 Speed of Light</h3><p>先比较计算吞吐和内存吞吐相对于设备峰值的比例。如果 Memory 接近上限而 Compute 很低，通常是 Memory Bound；反之则可能是 Compute Bound。</p><h3 id="62-memory-workload-analysis"><a class="markdownIt-Anchor" href="#62-memory-workload-analysis"></a> 6.2 Memory Workload Analysis</h3><p>检查：</p><ul><li>Global Load/Store 是否合并；</li><li>请求量与实际内存事务量是否相符；</li><li>L1/L2 命中率；</li><li>Shared Memory Bank Conflict；</li><li>DRAM 吞吐是否接近有效上限。</li></ul><h3 id="63-occupancy"><a class="markdownIt-Anchor" href="#63-occupancy"></a> 6.3 Occupancy</h3><p>确认限制 Active Warp 的资源：</p><ul><li>每线程寄存器；</li><li>每 Block Shared Memory；</li><li>Block 大小；</li><li>架构上的最大 Block 和 Warp 数。</li></ul><p>高 Occupancy 可以帮助隐藏延迟，但不会自动提高算术强度或消除低效访存。</p><h3 id="64-warp-stall"><a class="markdownIt-Anchor" href="#64-warp-stall"></a> 6.4 Warp Stall</h3><p>不同架构和报告版本的指标名称可能变化，但常见信号包括：</p><ul><li>Long Scoreboard：Warp 长时间等待内存相关依赖；</li><li>MIO Throttle：内存输入输出相关指令管线压力较大；</li><li>Barrier：等待同步；</li><li>Not Selected：有足够就绪 Warp，但当前没有被选中；</li><li>Branch Resolving：分支控制流造成等待。</li></ul><p>不要看到一个 Stall 指标高就直接下结论。它必须和访存事务、吞吐、Occupancy 以及源码位置一起解释。</p><h2 id="七-一个可复用的优化闭环"><a class="markdownIt-Anchor" href="#七-一个可复用的优化闭环"></a> 七、一个可复用的优化闭环</h2><h3 id="第一步定义目标"><a class="markdownIt-Anchor" href="#第一步定义目标"></a> 第一步：定义目标</h3><p>明确优化的是：</p><ul><li>单 Kernel 延迟；</li><li>训练 Step Time；</li><li>TTFT；</li><li>TPOT；</li><li>Tokens/s；</li><li>每美元吞吐。</li></ul><h3 id="第二步建立模型"><a class="markdownIt-Anchor" href="#第二步建立模型"></a> 第二步：建立模型</h3><p>计算 FLOPs、Bytes、算术强度、理论带宽时间和理论计算时间。</p><h3 id="第三步获取证据"><a class="markdownIt-Anchor" href="#第三步获取证据"></a> 第三步：获取证据</h3><p>用 Systems 判断阶段，用 Compute 判断 Kernel，用框架 Profiler 补充算子和调用栈信息。</p><h3 id="第四步一次只修改一个变量"><a class="markdownIt-Anchor" href="#第四步一次只修改一个变量"></a> 第四步：一次只修改一个变量</h3><p>例如只改变：</p><ul><li>Block Size；</li><li>Tile 大小；</li><li>向量化宽度；</li><li>是否融合；</li><li>是否使用 CUDA Graph。</li></ul><h3 id="第五步记录收益和代价"><a class="markdownIt-Anchor" href="#第五步记录收益和代价"></a> 第五步：记录收益和代价</h3><p>至少记录正确性误差、延迟、吞吐、显存和适用输入范围。一个只在固定 Shape 上更快的 Kernel，不一定适合动态生产负载。</p><h2 id="八-技术-qa"><a class="markdownIt-Anchor" href="#八-技术-qa"></a> 八、技术 Q&amp;A</h2><h3 id="q1occupancy-达到-100为什么-kernel-仍可能很慢"><a class="markdownIt-Anchor" href="#q1occupancy-达到-100为什么-kernel-仍可能很慢"></a> Q1：Occupancy 达到 100%，为什么 Kernel 仍可能很慢？</h3><p>Occupancy 只描述 SM 上驻留 Warp 的比例，不描述每个 Warp 的访存是否合并、数据是否复用、指令是否有效或 Tensor Core 是否被使用。一个高 Occupancy Kernel 可以同时具有低算术强度、严重 Bank Conflict 和大量无效线程。只有当瓶颈是“没有足够就绪 Warp 隐藏延迟”时，提高 Occupancy 才直接有效。</p><h3 id="q2shared-memory-tiling-为什么能提高-gemm-算术强度"><a class="markdownIt-Anchor" href="#q2shared-memory-tiling-为什么能提高-gemm-算术强度"></a> Q2：Shared Memory Tiling 为什么能提高 GEMM 算术强度？</h3><p>A、B 的一个 Tile 从 HBM 读取一次后，被 Block 内多个线程重复用于多个输出元素。必要 FLOPs 不变，而 HBM Bytes 减少，所以算术强度提高。Tile 继续增大时会消耗更多 Shared Memory 和寄存器，最终受 Occupancy、指令调度或片上带宽限制。</p><h3 id="q3nsight-systems-和-nsight-compute-应该按什么顺序使用"><a class="markdownIt-Anchor" href="#q3nsight-systems-和-nsight-compute-应该按什么顺序使用"></a> Q3：Nsight Systems 和 Nsight Compute 应该按什么顺序使用？</h3><p>先用 Systems 判断端到端时间花在 CPU、数据拷贝、NCCL、Kernel 还是 GPU Idle；只有确认某个 Kernel 占据主要时间且内部效率不足后，再用 Compute 分析其内存事务、吞吐、Stall 和 Occupancy。直接对任意 Kernel 跑完整 NCU 容易得到大量指标，却没有优化优先级。</p><h3 id="q4flashattention-的-flops-相近为何仍能加速而-decode-的短-idle-gap-又该如何处理"><a class="markdownIt-Anchor" href="#q4flashattention-的-flops-相近为何仍能加速而-decode-的短-idle-gap-又该如何处理"></a> Q4：FlashAttention 的 FLOPs 相近为何仍能加速，而 Decode 的短 Idle Gap 又该如何处理？</h3><p>标准 Attention 会把 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi><mo>×</mo><mi>S</mi></mrow><annotation encoding="application/x-tex">S\times S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 分数或概率矩阵写入 HBM，再读回用于后续计算；FlashAttention 在 SRAM 中按 Tile 完成分数、在线归一化和与 V 的累加，避免物化大中间矩阵，因此通过减少 IO 获得加速。若 Decode 时间线的主要问题是 Kernel 之间存在大量短 Idle Gap，则瓶颈在 CPU 调度、同步或 Launch，此时应优先考虑 CUDA Graph、算子融合和减少框架往返，而不是继续优化已经很短的单个 Kernel。</p><h2 id="九-系列导航"><a class="markdownIt-Anchor" href="#九-系列导航"></a> 九、系列导航</h2><p>上一篇：<a href="/2026/07/20/llm-token-journey/" title="LLM 自回归推理的执行路径：从 Tokenization 到 Continuous Batching">LLM 自回归推理的执行路径</a></p><p>下一篇：<a href="/2026/07/24/llm-serving-as-operating-system/" title="LLM 推理引擎的内存管理与调度：PagedAttention、Prefix Cache 与 Continuous Batching">LLM 推理引擎的内存管理与调度</a></p><h2 id="十-参考资料"><a class="markdownIt-Anchor" href="#十-参考资料"></a> 十、参考资料</h2><h3 id="性能模型与硬件文档"><a class="markdownIt-Anchor" href="#性能模型与硬件文档"></a> 性能模型与硬件文档</h3><ul><li><a href="https://dl.acm.org/doi/10.1145/1498765.1498785">Roofline: An Insightful Visual Performance Model</a>：算术强度与性能上限。</li><li><a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/">NVIDIA CUDA C++ Programming Guide</a>：CUDA 执行与内存模型。</li><li><a href="https://docs.nvidia.com/deeplearning/performance/">NVIDIA Deep Learning Performance Guide</a>：深度学习工作负载性能分析。</li><li><a href="https://docs.nvidia.com/nsight-systems/UserGuide/">NVIDIA Nsight Systems User Guide</a>：系统级时间线分析。</li><li><a href="https://docs.nvidia.com/nsight-compute/">NVIDIA Nsight Compute</a>：Kernel 级性能指标。</li></ul><h3 id="算子优化"><a class="markdownIt-Anchor" href="#算子优化"></a> 算子优化</h3><ul><li><a href="https://github.com/NVIDIA/cutlass">CUTLASS</a>：高性能 GEMM 模板和多级流水设计。</li><li><a href="https://siboehm.com/articles/22/CUDA-MMM">How to Optimize a CUDA Matmul Kernel</a>：从朴素实现到接近 cuBLAS 的优化过程。</li><li><a href="https://arxiv.org/abs/1805.02867">Online Normalizer Calculation for Softmax</a>：Online Softmax 的递推状态。</li><li><a href="https://arxiv.org/abs/2205.14135">FlashAttention</a>：IO-Aware Attention。</li><li><a href="https://arxiv.org/abs/2307.08691">FlashAttention-2</a>：并行划分和非 GEMM 操作优化。</li><li><a href="https://developer.nvidia.com/blog/cuda-graphs/">Getting Started with CUDA Graphs</a>：减少重复 Kernel Launch 开销。</li><li><a href="https://github.com/caomaolufei/AIInfraGuide">AIInfraGuide</a>：CUDA 内存模型、Reduce/GEMM/Softmax 优化及 Nsight 工具链资料，MIT License。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;看到 GPU 利用率不高，常见反应是增大 Block、提高 Occupancy、做算子融合或者直接换成 Triton。但这些动作如果没有性能模型支撑，很可能只是把瓶颈从一个地方搬到另一个地方。&lt;/p&gt;
&lt;p&gt;可靠的优化过程应该像故障诊断：先建立理论上限，再用工具验证瓶颈，最</summary>
      
    
    
    
    <category term="AI Infrastructure" scheme="https://zhouzhq2021.github.io/categories/AI-Infrastructure/"/>
    
    
    <category term="AI Infra Engineering Series" scheme="https://zhouzhq2021.github.io/tags/AI-Infra-Engineering-Series/"/>
    
    <category term="CUDA Performance" scheme="https://zhouzhq2021.github.io/tags/CUDA-Performance/"/>
    
  </entry>
  
  <entry>
    <title>LLM 自回归推理的执行路径：从 Tokenization 到 Continuous Batching</title>
    <link href="https://zhouzhq2021.github.io/2026/07/20/llm-token-journey/"/>
    <id>https://zhouzhq2021.github.io/2026/07/20/llm-token-journey/</id>
    <published>2026-07-20T02:00:00.000Z</published>
    <updated>2026-07-30T04:18:20.509Z</updated>
    
    <content type="html"><![CDATA[<p>用户在聊天框里输入一句话，几秒后屏幕开始逐字出现回答。看起来像一次普通的函数调用，实际上这段旅程跨越了分词器、Transformer、CUDA Kernel、KV Cache、请求调度器和网络服务层。</p><p>本文不按模型组件逐个介绍，而是跟随一个 Token 从进入系统到离开 GPU。沿着这条数据流，可以自然解释 Prefill 与 Decode、KV Cache、Continuous Batching，以及 TTFT 和 TPOT 为什么会成为推理系统最重要的指标。</p><p><img src="/images/posts/ai-infra-series/token-lifecycle.svg" alt="LLM 自回归推理中的 Token 执行路径" /></p><p><em>图 1：Prefill 与 Decode 共享模型结构，却具有完全不同的执行形状和性能瓶颈。</em></p><p>理解整条链路时，最好把系统看成三个相互耦合的状态机，而不是一条只会向前的流水线：</p><table><thead><tr><th>状态机</th><th>保存什么</th><th>何时变化</th></tr></thead><tbody><tr><td>模型状态</td><td>权重、编译图、并行配置</td><td>加载或热更新时</td></tr><tr><td>请求状态</td><td>Token 序列、采样参数、KV Cache、停止条件</td><td>每生成一个 Token</td></tr><tr><td>调度状态</td><td>Waiting/Running 队列、Token Budget、Block 水位</td><td>每个推理迭代</td></tr></tbody></table><p>模型前向只负责把输入状态推进一步；生产推理的复杂性来自数百个请求状态机共享同一份模型状态，并由调度状态决定谁在下一轮获得计算和显存。</p><h2 id="一-tokenization-与离散输入表示"><a class="markdownIt-Anchor" href="#一-tokenization-与离散输入表示"></a> 一、Tokenization 与离散输入表示</h2><p>模型不能直接处理字符串。请求首先经过 Tokenizer，被转换为一串整数 ID：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;AI Infra 很有意思&quot;</span><br><span class="line">        ↓ Tokenizer</span><br><span class="line">[2334, 6121,  ...]</span><br></pre></td></tr></table></figure><p>现代 LLM 通常使用子词或字节级分词。Tokenizer 会影响：</p><ul><li>同一句话被切成多少 Token；</li><li>上下文窗口实际能容纳多少文字；</li><li>推理计费和吞吐统计；</li><li>多语言场景中的输入效率；</li><li>Prefix Cache 能否命中完全一致的 Token 前缀。</li></ul><p>因此，服务端判断两个 Prompt 是否拥有相同前缀时，比较的不是肉眼看到的字符串，而是分词后的 Token 序列。</p><h2 id="二-embedding-位置编码与输入张量"><a class="markdownIt-Anchor" href="#二-embedding-位置编码与输入张量"></a> 二、Embedding、位置编码与输入张量</h2><p>每个 Token ID 都会在 Embedding 表中查到一个 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">d</span></span></span></span> 维向量。假设 Batch 为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>B</mi></mrow><annotation encoding="application/x-tex">B</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span></span>，序列长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span>，隐藏维度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">d</span></span></span></span>，进入 Transformer 前的张量形状是：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">(</mo><mi>B</mi><mo separator="true">,</mo><mi>S</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(B,S,d)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span></span></span></span></span></p><p>Embedding 本身不包含顺序信息，所以还需要位置编码。当前 Decoder-only 模型常使用 RoPE，将位置信息施加到 Q、K 上。这样 Attention 的内积不仅与内容有关，也能感知相对位置。</p><p>这一阶段看似简单，却决定了后续所有 Kernel 的形状。如果不能熟练跟踪 <code>(Batch, Sequence, Hidden)</code>，就很难分析 Attention 的显存、GEMM 的尺寸或张量并行的切分位置。</p><h2 id="三-decoder-block-的计算路径"><a class="markdownIt-Anchor" href="#三-decoder-block-的计算路径"></a> 三、Decoder Block 的计算路径</h2><p><img src="/images/posts/ai-infra-series/decoder-only-architecture.png" alt="Decoder-only Transformer 架构" /></p><p><em>图 2：Decoder-only 模型从 Embedding 开始，经过重复的 Decoder Block，最终由 LM Head 产生 Logits。</em></p><p>一个典型的 Pre-Norm Decoder Block 可以抽象成：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x</span><br><span class="line">├─ RMSNorm → Causal Self-Attention → Residual Add</span><br><span class="line">└─ RMSNorm → SwiGLU FFN          → Residual Add</span><br></pre></td></tr></table></figure><h3 id="31-q-k-v-从哪里来"><a class="markdownIt-Anchor" href="#31-q-k-v-从哪里来"></a> 3.1 Q、K、V 从哪里来</h3><p>输入 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi><mo>∈</mo><msup><mi mathvariant="double-struck">R</mi><mrow><mi>B</mi><mo>×</mo><mi>S</mi><mo>×</mo><mi>d</mi></mrow></msup></mrow><annotation encoding="application/x-tex">X\in\mathbb{R}^{B\times S\times d}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72243em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8491079999999999em;vertical-align:0em;"></span><span class="mord"><span class="mord"><span class="mord mathbb">R</span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8491079999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.05017em;">B</span><span class="mbin mtight">×</span><span class="mord mathnormal mtight" style="margin-right:0.05764em;">S</span><span class="mbin mtight">×</span><span class="mord mathnormal mtight">d</span></span></span></span></span></span></span></span></span></span></span></span> 分别乘以投影矩阵：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>Q</mi><mo>=</mo><mi>X</mi><msub><mi>W</mi><mi>Q</mi></msub><mo separator="true">,</mo><mspace width="1em"/><mi>K</mi><mo>=</mo><mi>X</mi><msub><mi>W</mi><mi>K</mi></msub><mo separator="true">,</mo><mspace width="1em"/><mi>V</mi><mo>=</mo><mi>X</mi><msub><mi>W</mi><mi>V</mi></msub></mrow><annotation encoding="application/x-tex">Q=XW_Q,\quad K=XW_K,\quad V=XW_V</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8777699999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal">Q</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.328331em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">Q</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:1em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">K</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8777699999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.32833099999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.07153em;">K</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:1em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.32833099999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.22222em;">V</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></p><p>拆成多头后，Q 的形状通常是：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">(</mo><mi>B</mi><mo separator="true">,</mo><msub><mi>H</mi><mi>q</mi></msub><mo separator="true">,</mo><mi>S</mi><mo separator="true">,</mo><msub><mi>d</mi><mi>h</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(B,H_q,S,d_h)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.036108em;vertical-align:-0.286108em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span></span></p><p>GQA 模型的 K、V 则是：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">(</mo><mi>B</mi><mo separator="true">,</mo><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub><mo separator="true">,</mo><mi>S</mi><mo separator="true">,</mo><msub><mi>d</mi><mi>h</mi></msub><mo stretchy="false">)</mo><mo separator="true">,</mo><mspace width="1em"/><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub><mo>&lt;</mo><msub><mi>H</mi><mi>q</mi></msub></mrow><annotation encoding="application/x-tex">(B,H_{kv},S,d_h),\quad H_{kv}&lt;H_q</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span><span class="mpunct">,</span><span class="mspace" style="margin-right:1em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span></span></p><p>Attention 的核心公式为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">softmax</mi><mo>⁡</mo><mrow><mo fence="true">(</mo><mfrac><mrow><mi>Q</mi><msup><mi>K</mi><mi>T</mi></msup></mrow><msqrt><msub><mi>d</mi><mi>h</mi></msub></msqrt></mfrac><mo>+</mo><mi>M</mi><mo fence="true">)</mo></mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">\operatorname{softmax}\left(\frac{QK^T}{\sqrt{d_h}}+M\right)V</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.468361em;vertical-align:-0.95003em;"></span><span class="mop"><span class="mord mathrm">s</span><span class="mord mathrm">o</span><span class="mord mathrm" style="margin-right:0.07778em;">f</span><span class="mord mathrm">t</span><span class="mord mathrm">m</span><span class="mord mathrm">a</span><span class="mord mathrm">x</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.5183309999999999em;"><span style="top:-2.25278em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85722em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord" style="padding-left:0.833em;"><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.81722em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg width='400em' height='1.08em' viewBox='0 0 400000 1080' preserveAspectRatio='xMinYMin slice'><path d='M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.18278000000000005em;"><span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">Q</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">K</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8413309999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.93em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span></span></span></span></span></p><p>其中 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span> 是 Causal Mask，它让当前位置只能看到自己和之前的 Token。</p><h3 id="32-ffn-为什么不能忽略"><a class="markdownIt-Anchor" href="#32-ffn-为什么不能忽略"></a> 3.2 FFN 为什么不能忽略</h3><p>Attention 负责 Token 之间的信息混合，FFN 则对每个 Token 独立做非线性变换。SwiGLU 通常包含 Gate、Up 和 Down 三个大矩阵乘，是参数量和计算量的重要组成部分。</p><p>从系统角度看，一个 Decoder Block 不是单个大 Kernel，而是一组 GEMM、Norm、激活、位置编码和 Attention Kernel。推理框架需要尽量减少中间张量读写和 Kernel Launch 次数。</p><h2 id="四-prefill-与-decode-两阶段执行模型"><a class="markdownIt-Anchor" href="#四-prefill-与-decode-两阶段执行模型"></a> 四、Prefill 与 Decode 两阶段执行模型</h2><p>自回归模型每次只生成一个新 Token，但处理输入 Prompt 和生成后续 Token 是两种完全不同的工作负载。</p><h3 id="41-prefill一次处理完整-prompt"><a class="markdownIt-Anchor" href="#41-prefill一次处理完整-prompt"></a> 4.1 Prefill：一次处理完整 Prompt</h3><p>假设 Prompt 有 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 个 Token，Prefill 会让这 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 个位置一起通过所有 Transformer 层。</p><p>特点是：</p><ul><li>GEMM 的矩阵尺寸较大；</li><li>GPU 并行度高；</li><li>计算量随 Prompt 长度快速增长；</li><li>通常更接近 Compute Bound；</li><li>结束时生成所有历史 Token 的 K、V。</li></ul><p>用户最关心的 Prefill 指标是 TTFT，也就是从请求进入系统到收到第一个输出 Token 的时间。</p><h3 id="42-decode每轮只新增一个-token"><a class="markdownIt-Anchor" href="#42-decode每轮只新增一个-token"></a> 4.2 Decode：每轮只新增一个 Token</h3><p>Decode 第 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.61508em;vertical-align:0em;"></span><span class="mord mathnormal">t</span></span></span></span> 轮只计算一个新位置的 Q、K、V。新 Q 要与之前所有 Token 的 K 做 Attention，然后产生下一个 Token 的概率分布。</p><p>此时矩阵乘的 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span> 维通常很小，GPU 每轮却仍要读取大量模型权重和历史 KV。Decode 因而常表现为 Memory Bound：算术单元还没吃饱，显存带宽已经成为瓶颈。</p><table><thead><tr><th>特性</th><th>Prefill</th><th>Decode</th></tr></thead><tbody><tr><td>每次处理 Token 数</td><td>整个 Prompt</td><td>每请求通常 1 个</td></tr><tr><td>主要瓶颈</td><td>计算</td><td>权重与 KV 读取</td></tr><tr><td>用户指标</td><td>TTFT</td><td>TPOT/ITL</td></tr><tr><td>优化重点</td><td>高效 Attention、Chunking</td><td>Batching、量化、CUDA Graph</td></tr></tbody></table><h2 id="五-为什么需要-kv-cache"><a class="markdownIt-Anchor" href="#五-为什么需要-kv-cache"></a> 五、为什么需要 KV Cache</h2><p>如果没有 KV Cache，第 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.61508em;vertical-align:0em;"></span><span class="mord mathnormal">t</span></span></span></span> 轮生成时必须重新计算前面 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">t-1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69841em;vertical-align:-0.08333em;"></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">1</span></span></span></span> 个 Token 的 K 和 V。整个生成过程会产生大量重复工作。</p><p>KV Cache 的策略很直接：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Prefill: 计算 Prompt 所有 K/V → 写入缓存</span><br><span class="line">Decode : 只计算新 Token 的 K/V → 追加到缓存</span><br><span class="line">         新 Q 读取全部历史 K/V</span><br></pre></td></tr></table></figure><p>为什么不缓存 Q？因为历史 Token 的 Q 只用于它们各自产生输出。生成当前 Token 时，需要的是当前 Q 与全部历史 K、V，历史 Q 不再参与计算。</p><p>KV Cache 用显存换取了计算，但也带来了新的系统问题：</p><ul><li>长上下文让缓存线性增长；</li><li>并发请求的长度不同；</li><li>请求随时到达和结束；</li><li>连续预留容易产生碎片；</li><li>相同系统提示词会被反复计算。</li></ul><p>这些问题分别催生了 PagedAttention、Continuous Batching 和 Prefix Cache。</p><h2 id="六-logits-处理与采样解码"><a class="markdownIt-Anchor" href="#六-logits-处理与采样解码"></a> 六、Logits 处理与采样解码</h2><p>最后一层隐藏状态经过 Final Norm 和 LM Head，得到词表大小的 Logits。Softmax 将其转换为概率分布，但系统不一定选择概率最大的 Token。</p><h3 id="61-temperature"><a class="markdownIt-Anchor" href="#61-temperature"></a> 6.1 Temperature</h3><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>p</mi><mi>i</mi></msub><mo>=</mo><mi mathvariant="normal">softmax</mi><mo>⁡</mo><mo stretchy="false">(</mo><msub><mi>z</mi><mi>i</mi></msub><mi mathvariant="normal">/</mi><mi>T</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">p_i=\operatorname{softmax}(z_i/T)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop"><span class="mord mathrm">s</span><span class="mord mathrm">o</span><span class="mord mathrm" style="margin-right:0.07778em;">f</span><span class="mord mathrm">t</span><span class="mord mathrm">m</span><span class="mord mathrm">a</span><span class="mord mathrm">x</span></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.04398em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mclose">)</span></span></span></span></span></p><ul><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mo>&lt;</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">T&lt;1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72243em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">1</span></span></span></span>：分布更尖锐，输出更确定；</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mo>&gt;</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">T&gt;1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72243em;vertical-align:-0.0391em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">&gt;</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">1</span></span></span></span>：分布更平坦，输出更多样。</li></ul><h3 id="62-top-k"><a class="markdownIt-Anchor" href="#62-top-k"></a> 6.2 Top-K</h3><p>只保留概率最高的 K 个候选，再归一化采样。它限制候选数量，但没有考虑概率质量。</p><h3 id="63-top-p"><a class="markdownIt-Anchor" href="#63-top-p"></a> 6.3 Top-P</h3><p>按概率从高到低排序，保留累计概率达到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>p</mi></mrow><annotation encoding="application/x-tex">p</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathnormal">p</span></span></span></span> 的最小集合。分布很确定时候选少，分布不确定时候选会自动增多。</p><p>采样出新 Token 后，它会被追加到请求序列，重新进入下一轮 Decode，直到遇到结束符、长度限制或停止条件。</p><h3 id="64-一个最小自回归循环"><a class="markdownIt-Anchor" href="#64-一个最小自回归循环"></a> 6.4 一个最小自回归循环</h3><p>下面的伪代码刻意省略推理框架的调度细节，只保留模型状态如何跨轮次延续：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@torch.inference_mode()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate</span>(<span class="params">model, input_ids, max_new_tokens, temperature=<span class="number">0.8</span></span>):</span><br><span class="line">    <span class="comment"># 首轮输入完整 Prompt，模型返回首 Token 所需 logits 和每层 KV。</span></span><br><span class="line">    output = model(input_ids=input_ids, use_cache=<span class="literal">True</span>)</span><br><span class="line">    past_key_values = output.past_key_values</span><br><span class="line">    next_token = sample(output.logits[:, -<span class="number">1</span>], temperature)</span><br><span class="line">    generated = [next_token]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(max_new_tokens - <span class="number">1</span>):</span><br><span class="line">        <span class="comment"># 后续每轮只输入上轮产生的一个 Token。</span></span><br><span class="line">        output = model(</span><br><span class="line">            input_ids=next_token[:, <span class="literal">None</span>],</span><br><span class="line">            past_key_values=past_key_values,</span><br><span class="line">            use_cache=<span class="literal">True</span>,</span><br><span class="line">        )</span><br><span class="line">        past_key_values = output.past_key_values</span><br><span class="line">        next_token = sample(output.logits[:, -<span class="number">1</span>], temperature)</span><br><span class="line">        generated.append(next_token)</span><br><span class="line">        <span class="keyword">if</span> reached_stop_condition(next_token):</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> torch.stack(generated, dim=<span class="number">1</span>)</span><br></pre></td></tr></table></figure><p>这段代码展示了 Prefill/Decode 的语义边界，但不适合直接作为生产服务：每个请求单独循环会产生小 Kernel 和频繁 Launch，也无法让完成的请求即时退出 Batch。vLLM、SGLang 等引擎优化的正是“多个这样的状态机如何共享一次模型执行”。</p><h2 id="七-多请求推理与迭代级调度"><a class="markdownIt-Anchor" href="#七-多请求推理与迭代级调度"></a> 七、多请求推理与迭代级调度</h2><p>如果 GPU 每次只为一个请求生成一个 Token，Decode 的小矩阵很难利用完整算力。推理引擎会把多个请求的当前 Token 拼成一个 Batch，让一次权重读取服务更多请求。</p><p>传统 Static Batching 要等整个 Batch 中所有请求都完成，短请求会被长请求拖住。Continuous Batching 则在每个 Decode 迭代重新组织 Batch：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">完成的请求退出</span><br><span class="line">        ↓</span><br><span class="line">释放对应 KV Block</span><br><span class="line">        ↓</span><br><span class="line">从等待队列补入新请求</span><br><span class="line">        ↓</span><br><span class="line">执行下一轮 Token</span><br></pre></td></tr></table></figure><p>Batch 越大，通常吞吐越高，但每个请求等待调度和执行的时间也可能增加。因此推理系统不是只追求 Tokens/s，而是在吞吐、TPOT 和尾延迟之间做平衡。</p><h2 id="八-用指标描述这段旅程"><a class="markdownIt-Anchor" href="#八-用指标描述这段旅程"></a> 八、用指标描述这段旅程</h2><h3 id="81-ttft"><a class="markdownIt-Anchor" href="#81-ttft"></a> 8.1 TTFT</h3><p>Time To First Token，包括排队、Tokenization、调度和 Prefill。长 Prompt、排队拥塞和冷启动都会抬高 TTFT。</p><h3 id="82-tpot-itl"><a class="markdownIt-Anchor" href="#82-tpot-itl"></a> 8.2 TPOT / ITL</h3><p>Time Per Output Token 或 Inter-Token Latency，反映回答逐字输出是否流畅，主要受 Decode 阶段影响。</p><h3 id="83-e2e-latency"><a class="markdownIt-Anchor" href="#83-e2e-latency"></a> 8.3 E2E Latency</h3><p>一个近似关系是：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>T</mi><mrow><mi>E</mi><mn>2</mn><mi>E</mi></mrow></msub><mo>≈</mo><mi>T</mi><mi>T</mi><mi>F</mi><mi>T</mi><mo>+</mo><mo stretchy="false">(</mo><msub><mi>N</mi><mrow><mi>o</mi><mi>u</mi><mi>t</mi></mrow></msub><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo><mo>×</mo><mi>T</mi><mi>P</mi><mi>O</mi><mi>T</mi></mrow><annotation encoding="application/x-tex">T_{E2E}\approx TTFT+(N_{out}-1)\times TPOT</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.32833099999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.05764em;">E</span><span class="mord mtight">2</span><span class="mord mathnormal mtight" style="margin-right:0.05764em;">E</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2805559999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span></span></p><p>短回答可能由 TTFT 主导，长回答则更容易由 TPOT 主导。</p><h3 id="84-throughput-和-goodput"><a class="markdownIt-Anchor" href="#84-throughput-和-goodput"></a> 8.4 Throughput 和 Goodput</h3><p>Throughput 统计单位时间完成的请求或生成的 Token。Goodput 则只统计满足延迟 SLO 的有效工作。一个系统可能吞吐很高，但大量请求超过 TPOT 或 TTFT 限制，此时 Goodput 仍然很低。</p><h2 id="九-建议实验"><a class="markdownIt-Anchor" href="#九-建议实验"></a> 九、建议实验</h2><p>可以用同一模型做四组测试：</p><ol><li>固定输出长度，改变 Prompt 长度，观察 TTFT；</li><li>固定 Prompt，改变输出长度，观察 E2E 延迟；</li><li>改变并发数，记录吞吐和 P99 TPOT；</li><li>对比单请求 Hugging Face 推理与 vLLM Continuous Batching。</li></ol><p>记录结果时不要只写平均值，至少保留 P50、P95、P99 和实际 Prompt 长度分布。</p><h2 id="十-技术-qa"><a class="markdownIt-Anchor" href="#十-技术-qa"></a> 十、技术 Q&amp;A</h2><h3 id="q1为什么历史-token-的-q-不需要进入-kv-cache"><a class="markdownIt-Anchor" href="#q1为什么历史-token-的-q-不需要进入-kv-cache"></a> Q1：为什么历史 Token 的 Q 不需要进入 KV Cache？</h3><p>某个历史位置的 Q 只用于计算该位置自己的 Attention 输出。生成新 Token 时，需要的是“当前新位置的 Q”与“所有历史位置的 K/V”。历史 Q 不会再次参与后续位置的计算，缓存它只会增加显存而不会减少必要计算。</p><h3 id="q2prefill-和-decode-使用同一个模型为什么瓶颈不同"><a class="markdownIt-Anchor" href="#q2prefill-和-decode-使用同一个模型为什么瓶颈不同"></a> Q2：Prefill 和 Decode 使用同一个模型，为什么瓶颈不同？</h3><p>Prefill 同时处理 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 个位置，线性层形成较大的矩阵乘，数据复用和 Tensor Core 利用率较高；Decode 每请求每轮只有一个新位置，GEMM 的 Token 维很小，却仍要读取几乎全部层权重和历史 KV，因此算术强度低，通常更受 HBM 带宽和 Launch 延迟限制。</p><h3 id="q3为什么增大-batch-能提高-decode-吞吐却可能恶化-tpot"><a class="markdownIt-Anchor" href="#q3为什么增大-batch-能提高-decode-吞吐却可能恶化-tpot"></a> Q3：为什么增大 Batch 能提高 Decode 吞吐，却可能恶化 TPOT？</h3><p>单请求 Decode 读取一次模型权重只服务一个 Token；把多个请求组成 Batch 后，同一轮权重读取可以为多个 Token 计算摊销，GEMM 的有效尺寸也变大。但 Batch 过大使单轮执行时间增加，新请求和已经处于 Decode 阶段的请求都要等待更久，所以吞吐上升不保证 TPOT 和尾延迟同步改善。</p><h3 id="q4应如何用-ttft-tpot-和-goodput-评价推理引擎"><a class="markdownIt-Anchor" href="#q4应如何用-ttft-tpot-和-goodput-评价推理引擎"></a> Q4：应如何用 TTFT、TPOT 和 Goodput 评价推理引擎？</h3><p>TTFT 描述排队和 Prefill 后用户多久看到首 Token，TPOT 描述后续输出的流畅度，吞吐量描述系统总产出。Goodput 只统计同时满足 TTFT/TPOT SLO 的请求，可避免系统通过无限扩大 Batch 换取表面吞吐。测试时还应使用输入与输出长度分桶、并发扫描和真实到达 Trace；固定 Shape 无法暴露长 Prefill 干扰、动态补位、KV 碎片和 P99 排队时间。</p><h2 id="十一-系列导航"><a class="markdownIt-Anchor" href="#十一-系列导航"></a> 十一、系列导航</h2><p>上一篇：<a href="/2026/07/18/ai-infra-memory-ledger/" title="大模型训练与推理的显存模型：参数、优化器状态与 KV Cache">大模型训练与推理的显存模型</a></p><p>下一篇：<a href="/2026/07/22/ai-infra-roofline-profiling/" title="GPU 性能分析方法：Roofline 模型、CUDA 内存层次与 Nsight">GPU 性能分析方法</a></p><h2 id="十二-参考资料"><a class="markdownIt-Anchor" href="#十二-参考资料"></a> 十二、参考资料</h2><h3 id="论文"><a class="markdownIt-Anchor" href="#论文"></a> 论文</h3><ul><li><a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>：Transformer 和 Scaled Dot-Product Attention。</li><li><a href="https://arxiv.org/abs/2307.09288">LLaMA 2</a>：现代 Decoder-only 模型的结构实例。</li><li><a href="https://arxiv.org/abs/2305.13245">GQA: Training Generalized Multi-Query Transformer Models</a>：Query Head 与 KV Head 解耦。</li><li><a href="https://arxiv.org/abs/2309.06180">Efficient Memory Management for LLM Serving with PagedAttention</a>：KV Cache 管理和 vLLM。</li><li><a href="https://www.usenix.org/conference/osdi22/presentation/yu">Orca: A Distributed Serving System for Transformer-Based Generative Models</a>：Iteration-level Scheduling。</li><li><a href="https://arxiv.org/abs/1904.09751">The Curious Case of Neural Text Degeneration</a>：Top-P 采样。</li><li><a href="https://arxiv.org/abs/2211.17192">Fast Inference from Transformers via Speculative Decoding</a>：并行验证多个候选 Token。</li></ul><h3 id="文档"><a class="markdownIt-Anchor" href="#文档"></a> 文档</h3><ul><li><a href="https://docs.vllm.ai/">vLLM Documentation</a>：离线推理、在线服务和引擎配置。</li><li><a href="https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html">NVIDIA NIM LLM Benchmarking Metrics</a>：TTFT、ITL、吞吐等指标定义。</li><li><a href="https://jalammar.github.io/illustrated-transformer/">The Illustrated Transformer</a>：建立 Transformer 数据流直觉。</li><li><a href="https://github.com/caomaolufei/AIInfraGuide">AIInfraGuide</a>：Tokenization、Decoder-only、Prefill/Decode 与 KV Cache 的中文资料，MIT License。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;用户在聊天框里输入一句话，几秒后屏幕开始逐字出现回答。看起来像一次普通的函数调用，实际上这段旅程跨越了分词器、Transformer、CUDA Kernel、KV Cache、请求调度器和网络服务层。&lt;/p&gt;
&lt;p&gt;本文不按模型组件逐个介绍，而是跟随一个 Token 从进入</summary>
      
    
    
    
    <category term="AI Infrastructure" scheme="https://zhouzhq2021.github.io/categories/AI-Infrastructure/"/>
    
    
    <category term="AI Infra Engineering Series" scheme="https://zhouzhq2021.github.io/tags/AI-Infra-Engineering-Series/"/>
    
    <category term="LLM Inference" scheme="https://zhouzhq2021.github.io/tags/LLM-Inference/"/>
    
  </entry>
  
  <entry>
    <title>大模型训练与推理的显存模型：参数、优化器状态与 KV Cache</title>
    <link href="https://zhouzhq2021.github.io/2026/07/18/ai-infra-memory-ledger/"/>
    <id>https://zhouzhq2021.github.io/2026/07/18/ai-infra-memory-ledger/</id>
    <published>2026-07-18T02:00:00.000Z</published>
    <updated>2026-07-30T04:18:20.509Z</updated>
    
    <content type="html"><![CDATA[<p>很多 AI Infra 问题，最后都会回到一句非常朴素的话：显存里到底放了什么？如果这笔账没有算清，讨论 ZeRO、量化、PagedAttention 或多卡并行，很容易变成技术名词的堆砌。</p><p>本文不按“模型结构、训练优化、推理优化”的教材顺序展开，而是从一张统一的显存账本出发，把 Transformer 参数、优化器状态、激活值和 KV Cache 放进同一个分析框架。目标不是背住某个模型需要多少 GB，而是拿到任意配置后，都能判断它是否装得下、为什么装不下，以及应该用什么代价换取空间。</p><p><img src="/images/posts/ai-infra-series/memory-ledger.svg" alt="训练与推理显存账本" /></p><p><em>图 1：训练显存和推理显存应使用两套状态模型，但最终都要回到统一的资源预算。</em></p><h2 id="一-先区分两个问题装得下和跑得好"><a class="markdownIt-Anchor" href="#一-先区分两个问题装得下和跑得好"></a> 一、先区分两个问题：装得下和跑得好</h2><p>“一张 H100 能不能运行 7B 模型”不是一个完整问题。至少还要补充四个条件：</p><ul><li>训练还是推理；</li><li>使用 BF16、FP16、FP8、INT8 还是 INT4；</li><li>Batch Size、序列长度和并发量是多少；</li><li>是否使用张量并行、FSDP、量化或 CPU Offload。</li></ul><p>模型勉强装进显存，也不代表系统可以稳定运行。CUDA Context、通信 Buffer、临时 Workspace、CUDA Graph 和框架缓存都需要空间。生产环境通常不会把显存利用率推到理论上的 100%，否则一次较长的请求或稍大的临时张量就可能触发 OOM。</p><p>因此容量规划要依次回答：</p><ol><li>权重和长期状态占多少；</li><li>随输入变化的动态状态占多少；</li><li>运行时还需要留下多少安全空间；</li><li>达到目标吞吐时，动态状态会增长到什么程度。</li></ol><h3 id="11-用生命周期-副本数统一不同状态"><a class="markdownIt-Anchor" href="#11-用生命周期-副本数统一不同状态"></a> 1.1 用“生命周期 × 副本数”统一不同状态</h3><p>仅按“权重、梯度、激活”分类还不够。更稳健的模型是同时标注状态的生命周期和复制方式：</p><table><thead><tr><th>状态</th><th>生命周期</th><th>主要增长变量</th><th>是否适合分片</th></tr></thead><tbody><tr><td>模型权重</td><td>服务或训练进程全程</td><td>参数量、精度</td><td>TP/FSDP 可分片</td></tr><tr><td>优化器状态</td><td>整个训练任务</td><td>参数量、优化器</td><td>ZeRO/FSDP 可分片</td></tr><tr><td>激活</td><td>一个 Micro Batch 的前反向</td><td>Batch、Sequence、层数</td><td>SP/CP 或重计算</td></tr><tr><td>KV Cache</td><td>一个请求的存活时间</td><td>并发、已缓存 Token</td><td>TP 分片、分页管理</td></tr><tr><td>Workspace</td><td>一次算子或一张计算图</td><td>Kernel、Shape、后端</td><td>通常不可简单均分</td></tr><tr><td>通信 Buffer</td><td>Collective 执行期间</td><td>Bucket、并行组</td><td>与通信实现相关</td></tr></tbody></table><p>对于任意长期状态，可以用下面的抽象检查是否重复计算：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>M</mi><mrow><mi>s</mi><mi>t</mi><mi>a</mi><mi>t</mi><mi>e</mi></mrow></msub><mo>=</mo><mtext>元素数</mtext><mo>×</mo><mtext>每元素字节数</mtext><mo>×</mo><mfrac><mtext>副本因子</mtext><mtext>分片因子</mtext></mfrac></mrow><annotation encoding="application/x-tex">M_{state}=\text{元素数}\times\text{每元素字节数}\times\frac{\text{副本因子}}{\text{分片因子}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2805559999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord text"><span class="mord cjk_fallback">元素数</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord text"><span class="mord cjk_fallback">每元素字节数</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:2.04633em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.36033em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord cjk_fallback">分片因子</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord cjk_fallback">副本因子</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p>这个公式比“参数量除以 GPU 数”多了一个关键变量：副本因子。某个张量即使理论上可分片，也可能因为计算或通信需要，在特定阶段临时恢复完整副本。FSDP 的峰值 AllGather、TP 中未切分的小参数以及 CUDA Graph 私有池都属于这类情况。</p><h2 id="二-从配置文件估算参数量"><a class="markdownIt-Anchor" href="#二-从配置文件估算参数量"></a> 二、从配置文件估算参数量</h2><p>设隐藏维度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">d</span></span></span></span>，层数为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi></mrow><annotation encoding="application/x-tex">L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal">L</span></span></span></span>，词表大小为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">V</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span></span></span></span>，FFN 中间维度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>d</mi><mrow><mi>f</mi><mi>f</mi></mrow></msub></mrow><annotation encoding="application/x-tex">d_{ff}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.980548em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span>。</p><h3 id="21-embedding-和-lm-head"><a class="markdownIt-Anchor" href="#21-embedding-和-lm-head"></a> 2.1 Embedding 和 LM Head</h3><p>输入 Embedding 的参数量为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>V</mi><mo>×</mo><mi>d</mi></mrow><annotation encoding="application/x-tex">V \times d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">d</span></span></span></span></span></p><p>如果模型使用 Weight Tying，让输入 Embedding 和输出 LM Head 共享权重，这部分只存一份；否则还要再加一个 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi><mo>×</mo><mi>d</mi></mrow><annotation encoding="application/x-tex">V \times d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">d</span></span></span></span>。</p><p><img src="/images/posts/ai-infra-series/decoder-block-stack.png" alt="Decoder-only 模型由多个 Decoder Block 堆叠而成" /></p><p><em>图 2：参数账本必须覆盖 Embedding、每层 Attention/FFN 以及最终输出层，而不能只按隐藏维度粗略估计。</em></p><h3 id="22-attention"><a class="markdownIt-Anchor" href="#22-attention"></a> 2.2 Attention</h3><p>标准 Multi-Head Attention 包含 Q、K、V 和输出投影，忽略 Bias 时约为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>4</mn><msup><mi>d</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">4d^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8641079999999999em;vertical-align:0em;"></span><span class="mord">4</span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></p><p>GQA 或 MQA 会减少 K、V 投影的输出维度。设 Query Head 数为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>H</mi><mi>q</mi></msub></mrow><annotation encoding="application/x-tex">H_q</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span>，KV Head 数为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub></mrow><annotation encoding="application/x-tex">H_{kv}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，单头维度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>d</mi><mi>h</mi></msub><mo>=</mo><mi>d</mi><mi mathvariant="normal">/</mi><msub><mi>H</mi><mi>q</mi></msub></mrow><annotation encoding="application/x-tex">d_h=d/H_q</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.036108em;vertical-align:-0.286108em;"></span><span class="mord mathnormal">d</span><span class="mord">/</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.15139200000000003em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">q</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span>，则每层 Attention 参数量约为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>d</mi><mn>2</mn></msup><mo>+</mo><mn>2</mn><mi>d</mi><mo stretchy="false">(</mo><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub><msub><mi>d</mi><mi>h</mi></msub><mo stretchy="false">)</mo><mo>+</mo><msup><mi>d</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">d^2 + 2d(H_{kv}d_h) + d^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9474379999999999em;vertical-align:-0.08333em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">2</span><span class="mord mathnormal">d</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.8641079999999999em;vertical-align:0em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></p><p>GQA 对总参数量的影响通常没有对 KV Cache 那么显著，但会直接改变推理时每个 Token 的缓存大小。</p><h3 id="23-swiglu-ffn"><a class="markdownIt-Anchor" href="#23-swiglu-ffn"></a> 2.3 SwiGLU FFN</h3><p>当前很多 Decoder-only 模型使用 SwiGLU。它包含 Gate、Up 和 Down 三个矩阵，因此每层约为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>3</mn><mi>d</mi><mo>×</mo><msub><mi>d</mi><mrow><mi>f</mi><mi>f</mi></mrow></msub></mrow><annotation encoding="application/x-tex">3d \times d_{ff}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">3</span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.980548em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span></span></span></span></span></p><p>这也是为什么大模型中 FFN 往往比 Attention 占用更多参数。</p><h3 id="24-一个可复用的近似式"><a class="markdownIt-Anchor" href="#24-一个可复用的近似式"></a> 2.4 一个可复用的近似式</h3><p>忽略 Norm 和 Bias 等小项，一个 Decoder-only 模型可以近似写成：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>N</mi><mo>≈</mo><mi>V</mi><mi>d</mi><mo>+</mo><mi>L</mi><mrow><mo fence="true">(</mo><msub><mi>P</mi><mrow><mi>a</mi><mi>t</mi><mi>t</mi><mi>n</mi></mrow></msub><mo>+</mo><mn>3</mn><mi>d</mi><msub><mi>d</mi><mrow><mi>f</mi><mi>f</mi></mrow></msub><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">N \approx Vd + L\left(P_{attn}+3dd_{ff}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.036108em;vertical-align:-0.286108em;"></span><span class="mord mathnormal">L</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2805559999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.13889em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">n</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">3</span><span class="mord mathnormal">d</span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;">)</span></span></span></span></span></span></p><p>手算时不必追求个位数精度。容量规划更关心 7B、13B、70B 这种数量级，以及不同模块在总参数中的比例。</p><h2 id="三-训练显存为什么常见估算是每参数-16-字节"><a class="markdownIt-Anchor" href="#三-训练显存为什么常见估算是每参数-16-字节"></a> 三、训练显存：为什么常见估算是每参数 16 字节</h2><p>使用 BF16 参数和 AdamW 训练时，一种常见的静态显存账本如下：</p><table><thead><tr><th>项目</th><th style="text-align:right">每参数字节数</th><th>说明</th></tr></thead><tbody><tr><td>BF16 参数</td><td style="text-align:right">2 B</td><td>前向与反向使用</td></tr><tr><td>BF16 梯度</td><td style="text-align:right">2 B</td><td>实现不同可能有所变化</td></tr><tr><td>FP32 主权重</td><td style="text-align:right">4 B</td><td>保证更新精度</td></tr><tr><td>Adam 一阶动量</td><td style="text-align:right">4 B</td><td>梯度指数移动平均</td></tr><tr><td>Adam 二阶动量</td><td style="text-align:right">4 B</td><td>梯度平方指数移动平均</td></tr><tr><td>合计</td><td style="text-align:right">16 B</td><td>不含激活和临时 Buffer</td></tr></tbody></table><p>因此，静态训练状态常用下面的数量级估算：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>M</mi><mrow><mi>s</mi><mi>t</mi><mi>a</mi><mi>t</mi><mi>i</mi><mi>c</mi></mrow></msub><mo>≈</mo><mn>16</mn><mi>N</mi></mrow><annotation encoding="application/x-tex">M_{static} \approx 16N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">c</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">6</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span></span></p><p>一个 7B 模型仅静态状态就约为 112 GB。即使不考虑激活值，它也无法直接放进一张 80GB GPU 中进行标准 AdamW 训练。</p><p>需要特别注意：<code>16N</code> 是便于做决策的工程近似，不是所有框架都严格一致。梯度精度、优化器实现、参数 Flatten、通信 Bucket 和分配器行为都会影响真实结果。</p><h3 id="31-激活值为什么更难估算"><a class="markdownIt-Anchor" href="#31-激活值为什么更难估算"></a> 3.1 激活值为什么更难估算</h3><p>参数和优化器状态主要由模型规模决定，激活值则还受到以下因素影响：</p><ul><li>Micro Batch Size；</li><li>序列长度；</li><li>层数和隐藏维度；</li><li>Attention 是否保存 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi><mo>×</mo><mi>S</mi></mrow><annotation encoding="application/x-tex">S\times S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 中间矩阵；</li><li>是否启用 FlashAttention；</li><li>是否启用 Activation Checkpointing；</li><li>Tensor Parallel 和 Sequence Parallel 的切分方式。</li></ul><p>激活显存通常可以抽象为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>M</mi><mrow><mi>a</mi><mi>c</mi><mi>t</mi></mrow></msub><mo>∝</mo><mi>B</mi><mo>×</mo><mi>S</mi><mo>×</mo><mi>d</mi><mo>×</mo><mi>L</mi></mrow><annotation encoding="application/x-tex">M_{act} \propto B \times S \times d \times L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2805559999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight">c</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∝</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal">L</span></span></span></span></span></p><p>不同实现的常数项差异很大，所以最可靠的方法是先做理论上界，再用框架的显存快照和 Profiler 验证。</p><h2 id="四-推理显存权重之外真正会增长的是-kv-cache"><a class="markdownIt-Anchor" href="#四-推理显存权重之外真正会增长的是-kv-cache"></a> 四、推理显存：权重之外，真正会增长的是 KV Cache</h2><p>推理时不再保存梯度和优化器状态，显存的主体变为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>M</mi><mrow><mi>i</mi><mi>n</mi><mi>f</mi><mi>e</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>c</mi><mi>e</mi></mrow></msub><mo>=</mo><msub><mi>M</mi><mrow><mi>w</mi><mi>e</mi><mi>i</mi><mi>g</mi><mi>h</mi><mi>t</mi><mi>s</mi></mrow></msub><mo>+</mo><msub><mi>M</mi><mrow><mi>K</mi><mi>V</mi></mrow></msub><mo>+</mo><msub><mi>M</mi><mrow><mi>r</mi><mi>u</mi><mi>n</mi><mi>t</mi><mi>i</mi><mi>m</mi><mi>e</mi></mrow></msub></mrow><annotation encoding="application/x-tex">M_{inference}=M_{weights}+M_{KV}+M_{runtime}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight">c</span><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.969438em;vertical-align:-0.286108em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361079999999999em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.02691em;">w</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span><span class="mord mathnormal mtight">h</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">s</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.286108em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.32833099999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.07153em;">K</span><span class="mord mathnormal mtight" style="margin-right:0.22222em;">V</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">m</span><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></p><h3 id="41-权重显存"><a class="markdownIt-Anchor" href="#41-权重显存"></a> 4.1 权重显存</h3><p>权重部分可以直接近似为：</p><table><thead><tr><th>精度</th><th style="text-align:right">每参数字节数</th><th style="text-align:right">70B 权重量级</th></tr></thead><tbody><tr><td>FP32</td><td style="text-align:right">4 B</td><td style="text-align:right">280 GB</td></tr><tr><td>BF16/FP16</td><td style="text-align:right">2 B</td><td style="text-align:right">140 GB</td></tr><tr><td>INT8</td><td style="text-align:right">1 B</td><td style="text-align:right">70 GB，加上量化元数据</td></tr><tr><td>INT4</td><td style="text-align:right">0.5 B</td><td style="text-align:right">35 GB，加上量化元数据</td></tr></tbody></table><p>这张表解释了为什么 70B BF16 模型至少需要两张 80GB GPU，而 INT4 版本可能装入单张大显存 GPU。但“装入”不等于吞吐一定更高：量化 Kernel、反量化开销和硬件支持同样重要。</p><h3 id="42-kv-cache-通用公式"><a class="markdownIt-Anchor" href="#42-kv-cache-通用公式"></a> 4.2 KV Cache 通用公式</h3><p>每层都要为历史 Token 保存 K 和 V。设层数为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi></mrow><annotation encoding="application/x-tex">L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal">L</span></span></span></span>，KV Head 数为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub></mrow><annotation encoding="application/x-tex">H_{kv}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，单头维度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>d</mi><mi>h</mi></msub></mrow><annotation encoding="application/x-tex">d_h</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，缓存精度字节数为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi></mrow><annotation encoding="application/x-tex">b</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">b</span></span></span></span>，则每个 Token 的 KV Cache 为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>M</mi><mrow><mi>K</mi><mi>V</mi><mi mathvariant="normal">/</mi><mi>t</mi><mi>o</mi><mi>k</mi><mi>e</mi><mi>n</mi></mrow></msub><mo>=</mo><mn>2</mn><mi>L</mi><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub><msub><mi>d</mi><mi>h</mi></msub><mi>b</mi></mrow><annotation encoding="application/x-tex">M_{KV/token}=2LH_{kv}d_hb</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.03853em;vertical-align:-0.3551999999999999em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.34480000000000005em;"><span style="top:-2.5198em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.07153em;">K</span><span class="mord mathnormal mtight" style="margin-right:0.22222em;">V</span><span class="mord mtight">/</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">n</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.3551999999999999em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord">2</span><span class="mord mathnormal">L</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">b</span></span></span></span></span></p><p>总 KV Cache 为：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>M</mi><mrow><mi>K</mi><mi>V</mi></mrow></msub><mo>=</mo><mi>B</mi><mo>×</mo><mi>S</mi><mo>×</mo><mn>2</mn><mi>L</mi><msub><mi>H</mi><mrow><mi>k</mi><mi>v</mi></mrow></msub><msub><mi>d</mi><mi>h</mi></msub><mi>b</mi></mrow><annotation encoding="application/x-tex">M_{KV}=B\times S\times 2LH_{kv}d_hb</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.32833099999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.10903em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.07153em;">K</span><span class="mord mathnormal mtight" style="margin-right:0.22222em;">V</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord">2</span><span class="mord mathnormal">L</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:-0.08125em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.33610799999999996em;"><span style="top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">h</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">b</span></span></span></span></span></p><p>其中 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>B</mi></mrow><annotation encoding="application/x-tex">B</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span></span> 是并发序列数，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi></mrow><annotation encoding="application/x-tex">S</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span> 是已经缓存的平均序列长度。这里最容易被忽略的是：KV Cache 不只随上下文长度增长，也随并发数线性增长。</p><p>以 32 层、32 个 KV Head、Head Dimension 为 128 的 BF16 模型为例：</p><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>2</mn><mo>×</mo><mn>32</mn><mo>×</mo><mn>32</mn><mo>×</mo><mn>128</mn><mo>×</mo><mn>2</mn><mo>=</mo><mn>524288</mn><mtext> Bytes</mtext></mrow><annotation encoding="application/x-tex">2\times32\times32\times128\times2=524288\text{ Bytes}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">3</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">3</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">2</span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8777699999999999em;vertical-align:-0.19444em;"></span><span class="mord">5</span><span class="mord">2</span><span class="mord">4</span><span class="mord">2</span><span class="mord">8</span><span class="mord">8</span><span class="mord text"><span class="mord"> Bytes</span></span></span></span></span></span></p><p>即每个 Token、每个请求约 0.5 MiB。当并发为 16、序列长度为 4096 时，KV Cache 约为 32 GiB。这还没有计入模型权重和运行时空间。</p><h3 id="43-gqa-为什么对推理系统格外重要"><a class="markdownIt-Anchor" href="#43-gqa-为什么对推理系统格外重要"></a> 4.3 GQA 为什么对推理系统格外重要</h3><p>如果 Query Head 为 64，而 KV Head 只有 8，那么相对于同样使用 64 个 KV Head 的 MHA，KV Cache 可以缩小到原来的八分之一。</p><p>这说明一个架构改动可能同时影响三个层面：</p><ul><li>模型层：多个 Query Head 共享 K/V；</li><li>Kernel 层：Attention 的读取和线程映射发生变化；</li><li>系统层：同一张 GPU 可以容纳更多并发请求。</li></ul><h2 id="五-把显存优化理解为交换"><a class="markdownIt-Anchor" href="#五-把显存优化理解为交换"></a> 五、把显存优化理解为交换</h2><p>几乎所有显存优化都不是免费的。</p><table><thead><tr><th>技术</th><th>主要节省对象</th><th>付出的代价</th></tr></thead><tbody><tr><td>混合精度</td><td>权重、梯度、激活</td><td>数值稳定性管理</td></tr><tr><td>INT8/INT4 量化</td><td>推理权重与带宽</td><td>精度风险、专用 Kernel</td></tr><tr><td>Activation Checkpointing</td><td>训练激活</td><td>反向时重计算</td></tr><tr><td>ZeRO/FSDP</td><td>参数、梯度、优化器状态</td><td>更频繁的集合通信</td></tr><tr><td>CPU/NVMe Offload</td><td>GPU 静态状态</td><td>PCIe/存储传输延迟</td></tr><tr><td>GQA/MQA</td><td>KV Cache</td><td>模型表达能力与架构约束</td></tr><tr><td>PagedAttention</td><td>KV 碎片和预留浪费</td><td>块表间接寻址与管理成本</td></tr><tr><td>Prefix Cache</td><td>重复前缀的 KV 与 Prefill</td><td>哈希、淘汰和命中率管理</td></tr></tbody></table><p>做技术选型时，应该把问题改写成：“当前最稀缺的是显存、带宽、计算还是延迟？我愿意用什么资源交换？”</p><h2 id="六-训练与推理的容量规划顺序"><a class="markdownIt-Anchor" href="#六-训练与推理的容量规划顺序"></a> 六、训练与推理的容量规划顺序</h2><h3 id="61-训练"><a class="markdownIt-Anchor" href="#61-训练"></a> 6.1 训练</h3><ol><li>根据配置估算参数量；</li><li>按优化器和精度计算静态状态；</li><li>估算激活上界；</li><li>判断是否必须进行参数分片；</li><li>根据机器拓扑安排 TP、FSDP/DP 和 PP；</li><li>留出通信 Buffer 与分配器碎片空间；</li><li>用一个较小 Batch 实测峰值显存，再反推可用配置。</li></ol><h3 id="62-推理"><a class="markdownIt-Anchor" href="#62-推理"></a> 6.2 推理</h3><ol><li>计算权重显存；</li><li>计算目标并发和上下文长度下的 KV Cache；</li><li>为 CUDA Graph、Workspace 和框架预留空间；</li><li>决定 TP 数量与量化格式；</li><li>决定 <code>max_model_len</code>、并发上限和显存利用率；</li><li>用真实 Prompt 长度分布压测，而不是只测固定短输入。</li></ol><h3 id="63-把公式固化成容量估算器"><a class="markdownIt-Anchor" href="#63-把公式固化成容量估算器"></a> 6.3 把公式固化成容量估算器</h3><p>理论公式适合白板推导，工程上更适合把假设写入一个可以审计的小工具。下面的函数故意不调用任何深度学习框架，便于检查单位和输入：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> dataclasses <span class="keyword">import</span> dataclass</span><br><span class="line"></span><br><span class="line">GIB = <span class="number">1024</span> ** <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@dataclass</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ModelConfig</span>:</span><br><span class="line">    layers: <span class="built_in">int</span></span><br><span class="line">    hidden_size: <span class="built_in">int</span></span><br><span class="line">    query_heads: <span class="built_in">int</span></span><br><span class="line">    kv_heads: <span class="built_in">int</span></span><br><span class="line">    head_dim: <span class="built_in">int</span></span><br><span class="line">    parameters: <span class="built_in">int</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">weight_gib</span>(<span class="params">parameters: <span class="built_in">int</span>, bytes_per_weight: <span class="built_in">float</span></span>) -&gt; <span class="built_in">float</span>:</span><br><span class="line">    <span class="keyword">return</span> parameters * bytes_per_weight / GIB</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">training_static_gib</span>(<span class="params">parameters: <span class="built_in">int</span>, bytes_per_parameter: <span class="built_in">float</span> = <span class="number">16</span></span>) -&gt; <span class="built_in">float</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;BF16 参数/梯度 + FP32 主权重 + Adam m/v 的常用近似。&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> parameters * bytes_per_parameter / GIB</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">kv_cache_gib</span>(<span class="params"></span></span><br><span class="line"><span class="params">    cfg: ModelConfig,</span></span><br><span class="line"><span class="params">    batch_size: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">    cached_tokens_per_request: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">    bytes_per_element: <span class="built_in">int</span> = <span class="number">2</span>,</span></span><br><span class="line"><span class="params"></span>) -&gt; <span class="built_in">float</span>:</span><br><span class="line">    elements = (</span><br><span class="line">        <span class="number">2</span></span><br><span class="line">        * cfg.layers</span><br><span class="line">        * cfg.kv_heads</span><br><span class="line">        * cfg.head_dim</span><br><span class="line">        * batch_size</span><br><span class="line">        * cached_tokens_per_request</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> elements * bytes_per_element / GIB</span><br></pre></td></tr></table></figure><p>这个估算器还没有考虑 TP 切分、量化 Scale、对齐、块内碎片和运行时 Workspace。它的意义不是取代框架，而是把“我们假设了什么”显式保存下来。实测峰值与理论值不一致时，就可以逐项寻找缺失状态。</p><h2 id="七-技术-qa"><a class="markdownIt-Anchor" href="#七-技术-qa"></a> 七、技术 Q&amp;A</h2><h3 id="q1为什么-7b-模型的-bf16-权重约-14gb标准-adamw-训练却可能超过-100gb"><a class="markdownIt-Anchor" href="#q1为什么-7b-模型的-bf16-权重约-14gb标准-adamw-训练却可能超过-100gb"></a> Q1：为什么 7B 模型的 BF16 权重约 14GB，标准 AdamW 训练却可能超过 100GB？</h3><p>14GB 只计算了 <code>7B × 2 Bytes</code> 的模型权重。混合精度 AdamW 训练还要保存 BF16 梯度、FP32 主权重、FP32 一阶动量和 FP32 二阶动量，常用静态近似为 16 Bytes/参数，即约 112GB。实际训练还需要激活、临时张量、通信 Bucket 和 CUDA Runtime，所以 112GB 仍不是完整峰值。</p><h3 id="q2为什么-gqa-对参数量影响有限却能显著降低-kv-cache"><a class="markdownIt-Anchor" href="#q2为什么-gqa-对参数量影响有限却能显著降低-kv-cache"></a> Q2：为什么 GQA 对参数量影响有限，却能显著降低 KV Cache？</h3><p>GQA 只缩小 K、V 两个投影以及对应缓存，Attention 中 Q 和输出投影、FFN、Embedding 都没有同比缩小，所以总参数量下降有限。KV Cache 只保存每层 K/V，它与 <code>kv_heads</code> 成正比；KV Head 从 64 减到 8 时，这部分缓存理论上直接缩小八倍。</p><h3 id="q3zero-3-activation-checkpointing-和-int4-量化分别解决什么状态"><a class="markdownIt-Anchor" href="#q3zero-3-activation-checkpointing-和-int4-量化分别解决什么状态"></a> Q3：ZeRO-3、Activation Checkpointing 和 INT4 量化分别解决什么状态？</h3><p>ZeRO-3 分片训练期的参数、梯度和优化器状态；Activation Checkpointing 减少训练期保存的激活，用反向重计算交换显存；INT4 量化主要压缩推理权重，并降低权重读取带宽。三者作用对象不同，不能互相替代，但可以组合。</p><h3 id="q4理论容量估算应该如何与运行时测量闭环"><a class="markdownIt-Anchor" href="#q4理论容量估算应该如何与运行时测量闭环"></a> Q4：理论容量估算应该如何与运行时测量闭环？</h3><p>先用公式得到静态下界和动态上界，再在目标硬件上分别测启动后、Prefill 峰值、Decode 稳态和压力场景峰值。PyTorch 可结合 <code>memory_allocated</code>、<code>memory_reserved</code>、Memory Snapshot 与 Profiler；推理引擎还应记录 KV Block 数、抢占次数和缓存命中率。理论模型负责解释，运行测量负责校准。</p><h2 id="八-系列导航"><a class="markdownIt-Anchor" href="#八-系列导航"></a> 八、系列导航</h2><p>下一篇：<a href="/2026/07/20/llm-token-journey/" title="LLM 自回归推理的执行路径：从 Tokenization 到 Continuous Batching">LLM 自回归推理的执行路径</a></p><h2 id="九-参考资料"><a class="markdownIt-Anchor" href="#九-参考资料"></a> 九、参考资料</h2><h3 id="原始论文与技术报告"><a class="markdownIt-Anchor" href="#原始论文与技术报告"></a> 原始论文与技术报告</h3><ul><li><a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>：Transformer 参数结构和计算路径的起点。</li><li><a href="https://arxiv.org/abs/2307.09288">LLaMA 2: Open Foundation and Fine-Tuned Chat Models</a>：用于对照真实 Decoder-only 模型配置。</li><li><a href="https://arxiv.org/abs/1910.02054">ZeRO: Memory Optimizations Toward Training Trillion Parameter Models</a>：训练状态分片的核心论文。</li><li><a href="https://arxiv.org/abs/2304.11277">PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel</a>：FSDP 的工程经验与扩展性分析。</li><li><a href="https://arxiv.org/abs/2305.13245">GQA: Training Generalized Multi-Query Transformer Models</a>：理解 KV Head 数量与 KV Cache 的关系。</li><li><a href="https://arxiv.org/abs/2309.06180">Efficient Memory Management for Large Language Model Serving with PagedAttention</a>：推理阶段 KV Cache 分页管理。</li></ul><h3 id="文档与进一步阅读"><a class="markdownIt-Anchor" href="#文档与进一步阅读"></a> 文档与进一步阅读</h3><ul><li><a href="https://blog.eleuther.ai/transformer-math/">Transformer Math 101</a>：参数量、FLOPs 和训练资源估算。</li><li><a href="https://docs.pytorch.org/docs/stable/fsdp.html">PyTorch FSDP Documentation</a>：分片策略和运行时行为。</li><li><a href="https://docs.vllm.ai/">vLLM Documentation</a>：推理显存参数与实际部署选项。</li><li><a href="https://resources.nvidia.com/en-us-tensor-core">NVIDIA H100 Tensor Core GPU Architecture</a>：硬件显存、带宽和互联规格。</li><li><a href="https://github.com/caomaolufei/AIInfraGuide">AIInfraGuide</a>：Transformer 参数、训练显存、ZeRO/FSDP 与 KV Cache 的中文技术资料，MIT License。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;很多 AI Infra 问题，最后都会回到一句非常朴素的话：显存里到底放了什么？如果这笔账没有算清，讨论 ZeRO、量化、PagedAttention 或多卡并行，很容易变成技术名词的堆砌。&lt;/p&gt;
&lt;p&gt;本文不按“模型结构、训练优化、推理优化”的教材顺序展开，而是从一张统</summary>
      
    
    
    
    <category term="AI Infrastructure" scheme="https://zhouzhq2021.github.io/categories/AI-Infrastructure/"/>
    
    
    <category term="AI Infra Engineering Series" scheme="https://zhouzhq2021.github.io/tags/AI-Infra-Engineering-Series/"/>
    
    <category term="GPU Memory" scheme="https://zhouzhq2021.github.io/tags/GPU-Memory/"/>
    
  </entry>
  
  <entry>
    <title>Kubernetes 实践：集群安装与应用部署</title>
    <link href="https://zhouzhq2021.github.io/2024/06/29/kubernetes-lab-notes/"/>
    <id>https://zhouzhq2021.github.io/2024/06/29/kubernetes-lab-notes/</id>
    <published>2024-06-28T16:00:00.000Z</published>
    <updated>2026-06-08T05:00:50.544Z</updated>
    
    <content type="html"><![CDATA[<p>这篇记录整理一次 Kubernetes 环境搭建和应用部署流程，内容覆盖 Docker、cri-dockerd、kubeadm、Calico、Metrics Server、Pod、Deployment、Service、LoadBalancer、Ingress、NFS、Helm、Rancher 和 WordPress。</p><h2 id="一-目标"><a class="markdownIt-Anchor" href="#一-目标"></a> 一、目标</h2><p>Kubernetes(简称K8S) 是容器和集群管理的标准。Kubernetes 集群是由多个计算机（可以是物理机、云主机或虚拟机）组成的一个独立系统，通过 Kubernetes 容器管理系统，实现部署、运维和伸缩 Docker 容器等功能。本次搭建主要覆盖以下内容：</p><p>建立一个 Kubernetes 集群，包含一个主节点和两个工作节点，在集群上练习 kubectl 命令的基本操作。</p><p>练习通过命令行和 YAML 创建 Pod、Deployment 和 Job，并掌握 HPA。</p><p>练习为 Deployment 创建 Service，并通过 NodePort 暴露服务，再从客户端访问该服务。</p><p>为集群安装 LoadBalancer 软件，准备可用子网，并通过 LoadBalancer 方式暴露服务。</p><p>为集群安装 ingress-nginx 并验证。</p><p>为集群准备 NFS Server，安装 NFS Subdir External Provisioner，然后创建 PersistentVolumeClaim 并验证。</p><p>下载安装 Helm，用它安装 Rancher Dashboard，然后在 Dashboard 中管理集群并创建 WordPress 应用。</p><p>说明：部分镜像如 busybox、perl 等通过其他服务器提前拉取并打包到本地，再导入本地虚拟机使用。</p><h2 id="二-安装并配置docker"><a class="markdownIt-Anchor" href="#二-安装并配置docker"></a> 二、安装并配置Docker</h2><p>先配置 Docker 的 apt repository。</p><p>添加 Docker 官方 GPG key：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt-get update</span><br><span class="line">$ sudo apt-get install ca-certificates curl</span><br><span class="line">$ sudo install -m 0755 -d /etc/apt/keyrings</span><br><span class="line">$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc</span><br><span class="line">$ sudo chmod a+r /etc/apt/keyrings/docker.asc</span><br><span class="line">Add the repository to Apt sources:</span><br><span class="line">$ echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release &amp;&amp; echo &quot;$VERSION_CODENAME&quot;) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null</span><br><span class="line">$ sudo apt-get update</span><br><span class="line"># step 1: 安装必要的一些系统工具</span><br><span class="line">sudo apt-get update</span><br><span class="line">sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common</span><br><span class="line"># step 2: 安装GPG证书</span><br><span class="line">curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -</span><br><span class="line"># Step 3: 写入软件源信息</span><br><span class="line">sudo add-apt-repository &quot;deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable&quot;</span><br><span class="line"># Step 4: 更新并安装Docker-CE</span><br><span class="line">sudo apt-get -y update</span><br><span class="line">sudo apt-get -y install docker-ce</span><br></pre></td></tr></table></figure><p>或者安装制定版本：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">$ apt-cache madison docker-ce | awk &#x27;&#123; print $3 &#125;&#x27;</span><br><span class="line">$ VERSION_STRING=5:20.10.24~3-0~ubuntu-jammy</span><br><span class="line">$ sudo apt-get install docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING</span><br><span class="line"># apt list --installed | grep docker</span><br><span class="line">Set version lock</span><br><span class="line"># apt-mark hold docker-*  containerd.io</span><br><span class="line">List version locked software</span><br><span class="line"># apt-mark showhold</span><br><span class="line">Unlock the software</span><br><span class="line"># apt-mark unhold docker-*</span><br><span class="line"># vi /etc/docker/daemon.json</span><br><span class="line">&#123;</span><br><span class="line">&quot;oom-score-adjust&quot;: -1000,</span><br><span class="line">&quot;log-driver&quot;: &quot;json-file&quot;,</span><br><span class="line">&quot;log-opts&quot;: &#123;</span><br><span class="line">&quot;max-size&quot;: &quot;100m&quot;,</span><br><span class="line">&quot;max-file&quot;: &quot;5&quot;</span><br><span class="line">&#125;,</span><br><span class="line">&quot;exec-opts&quot;: [&quot;native.cgroupdriver=systemd&quot;],</span><br><span class="line">&quot;max-concurrent-downloads&quot;: 10,</span><br><span class="line">&quot;max-concurrent-uploads&quot;: 10,</span><br><span class="line">&quot;bip&quot;: &quot;169.254.123.1/24&quot;,</span><br><span class="line">&quot;dns&quot;: [&quot;202.201.0.133&quot;],</span><br><span class="line">&quot;storage-driver&quot;: &quot;overlay2&quot;,</span><br><span class="line">&quot;storage-opts&quot;: [&quot;overlay2.override_kernel_check=true&quot;]</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"># systemctl daemon-reload</span><br><span class="line"># systemctl restart docker</span><br><span class="line"># mkdir /etc/systemd/system/docker.service.d</span><br><span class="line"># cd /etc/systemd/system/docker.service.d/</span><br><span class="line"># vi http_proxy.conf</span><br><span class="line">[Service]</span><br><span class="line">Environment=&quot;HTTP_PROXY=http://noc:8912027@cg.lzu.edu.cn:8080&quot;</span><br><span class="line">Environment=&quot;HTTPS_PROXY=http://noc:8912027@cg.lzu.edu.cn:8080&quot;</span><br><span class="line">Environment=&quot;NO_PROXY=localhost,127.0.0.1,harbor.lzu.edu.cn&quot;</span><br><span class="line"># systemctl daemon-reload</span><br><span class="line"># systemctl restart docker</span><br></pre></td></tr></table></figure><p>这部分和 Docker 单独安装流程基本一致，下面保留最终安装效果，然后继续 Kubernetes 相关配置。</p><p><img src="/images/posts/kubernetes-lab-notes/image-003.png" alt="操作截图 3" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-004.png" alt="操作截图 4" /></p><p>Linux全局代理添加</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">export HTTP_PROXY=http://noc:8912027@cg.lzu.edu.cn:8080</span><br><span class="line">export HTTPS_PROXY=http://noc:8912027@cg.lzu.edu.cn:8080</span><br><span class="line">export NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.lzu.edu.cn</span><br><span class="line">export http_proxy=http://noc:8912027@cg.lzu.edu.cn:8080</span><br><span class="line">export https_proxy=http://noc:8912027@cg.lzu.edu.cn:8080</span><br><span class="line">export no_proxy=localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.lzu.edu.cn</span><br></pre></td></tr></table></figure><p>安装cri-dockered</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># wget http://bigdata.cg.lzu.edu.cn/kubernetes/cri-dockerd_0.3.13.3-0.ubuntu-jammy_amd64.deb</span><br><span class="line"># dpkg -i cri-dockerd_0.3.13.3-0.ubuntu-jammy_amd64.deb</span><br><span class="line"># systemctl daemon-reload</span><br><span class="line"># systemctl start cri-docker</span><br></pre></td></tr></table></figure><p>关闭swap</p><p>命令行swapoff –a直接关闭</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/fstab  注释掉swap引导项</span><br></pre></td></tr></table></figure><p>查看br_netfilter模块和overlay是否被内核加载</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># lsmod | grep br_netfilter</span><br><span class="line"># lsmod | grep overlay</span><br><span class="line"># modprobe br_netfilter</span><br><span class="line"># modprobe overlay</span><br><span class="line"># cat &lt;&lt;EOF |  tee /etc/modules-load.d/k8s.conf</span><br><span class="line">overlay</span><br><span class="line">br_netfilter</span><br><span class="line">EOF</span><br><span class="line"># cat &lt;&lt;EOF | sudo tee /etc/sysctl.d/k8s.conf</span><br><span class="line">net.bridge.bridge-nf-call-iptables  = 1</span><br><span class="line">net.bridge.bridge-nf-call-ip6tables = 1</span><br><span class="line">net.ipv4.ip_forward                 = 1</span><br><span class="line">EOF</span><br><span class="line"># sysctl --system</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-005.png" alt="操作截图 5" /></p><p>set apt source of aliyun for kubernetes</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"># curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add</span><br><span class="line"># echo &quot;deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main&quot; &gt; /etc/apt/sources.list.d/k8s.list</span><br><span class="line"># apt-get update</span><br><span class="line"># apt-cache madison kubelet</span><br><span class="line"># apt install kubeadm=1.26.8-00  kubelet=1.26.8-00  kubectl=1.26.8-00 -y</span><br><span class="line"># systemctl enable kubelet</span><br><span class="line"># apt-mark hold kubelet kubeadm kubectl</span><br><span class="line"># source &lt;(kubectl completion bash)</span><br><span class="line"># echo &#x27;source &lt;(kubectl completion bash)&#x27; &gt;&gt;~/.bashrc</span><br><span class="line"># vi /etc/docker/daemon.json</span><br><span class="line">&#123;</span><br><span class="line">&quot;exec-opts&quot;: [&quot;native.cgroupdriver=systemd&quot;],</span><br><span class="line">&#125;</span><br><span class="line"># vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf</span><br><span class="line">[Service]</span><br><span class="line">Environment=&quot;KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --cgroup-driver=systemd&quot;</span><br><span class="line"># vi /usr/lib/systemd/system/cri-docker.service</span><br><span class="line">[Service]</span><br><span class="line">ExecStart=/usr/bin/cri-dockerd --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9 --container-runtime-endpoint fd://</span><br><span class="line"># containerd config default &gt; /etc/containerd/config.toml</span><br><span class="line">Reboot the service:</span><br><span class="line">systemctl daemon-reload</span><br><span class="line">systemctl restart containerd.service</span><br><span class="line">systemctl restart docker.service</span><br><span class="line">systemctl restart cri-docker</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-006.png" alt="操作截图 6" /></p><h2 id="三-安装kubernetes"><a class="markdownIt-Anchor" href="#三-安装kubernetes"></a> 三、安装kubernetes</h2><p>control node:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubeadm version</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-007.png" alt="操作截图 7" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># kubeadm init --apiserver-advertise-address=192.168.111.135 --control-plane-endpoint=192.168.111.135 \</span><br><span class="line">--pod-network-cidr=10.244.0.0/16 --service-cidr=10.245.0.0/16  \</span><br><span class="line">--image-repository registry.aliyuncs.com/google_containers \</span><br><span class="line">--ignore-preflight-errors=Swap  \</span><br><span class="line">--kubernetes-version=v1.26.8  \</span><br><span class="line">--cri-socket unix:///var/run/cri-dockerd.sock</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-008.png" alt="操作截图 8" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># mkdir -p $HOME/.kube</span><br><span class="line"># cp -i /etc/kubernetes/admin.conf $HOME/.kube/config</span><br><span class="line"># chown $(id -u):$(id -g) $HOME/.kube/config</span><br><span class="line"># kubectl get node</span><br></pre></td></tr></table></figure><p>这里显示了一个控制节点docker，因为我使用的是docker主机名</p><p><img src="/images/posts/kubernetes-lab-notes/image-009.png" alt="操作截图 9" /></p><p>worker node 加入集群（需要将ip地址改为对应的worker节点虚拟机）:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubeadm join 192.168.101.100:6443 --token 7k09zz.6ogjsk85qsqbc8wx \</span><br><span class="line">--discovery-token-ca-cert-hash sha256:391c3cb957ec1a9e2f67b1516768d69029e712f4ca98e5b49d9cd8b651dde93d \</span><br><span class="line">--control-plane  \</span><br><span class="line">--cri-socket unix:///var/run/cri-dockerd.sock</span><br></pre></td></tr></table></figure><p>由于当前设备只能启动一台虚拟机，这里没有继续展示 worker 节点加入过程，后续操作在单节点控制平面上完成。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl describe nodes docker</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-010.png" alt="操作截图 10" /></p><p>Delete the tain for control node in single node environment:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl taint node docker node-role.kubernetes.io/control-plane-</span><br><span class="line"># kubectl taint node docker node.kubernetes.io/not-ready-</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-011.png" alt="操作截图 11" /></p><p>安装calico</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># wget https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/tigera-operator.yaml</span><br><span class="line"># wget https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/custom-resources.yaml</span><br><span class="line"># vi custom-resources.yaml</span><br><span class="line">ipPools:</span><br><span class="line">- blockSize: 24</span><br><span class="line">cidr: 10.244.0.0/16</span><br><span class="line"># kubectl create -f tigera-operator.yaml</span><br><span class="line"># kubectl create -f custom-resources.yaml</span><br><span class="line"># kubectl get ns</span><br><span class="line"># kubectl get node -o wide</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-012.png" alt="操作截图 12" /></p><p>安装metric-server</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># wget http://bigdata.cg.lzu.edu.cn/kubernetes/metrics-server-v0.7.1.tar</span><br><span class="line"># docker image load -i metrics-server-v0.7.1.tar</span><br><span class="line"># vi ./metrics-server/manifests/base/deployment.yaml</span><br><span class="line">containers:</span><br><span class="line">- name: metrics-server</span><br><span class="line">image: gcr.io/k8s-staging-metrics-server/metrics-server:v0.7.1</span><br><span class="line">imagePullPolicy: IfNotPresent</span><br><span class="line">command:</span><br><span class="line">- /metrics-server</span><br><span class="line">- --kubelet-insecure-tls</span><br><span class="line">- --kubelet-preferred-address-types=InternalIP</span><br><span class="line"># kubectl apply -k ./metrics-server/manifests/base/</span><br><span class="line"># kubectl get pod -n kube-system | grep metric</span><br><span class="line"># kubectl get service -A</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-013.png" alt="操作截图 13" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get pod -A</span><br><span class="line"># kubectl get pods -n kube-system -o wide</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-014.png" alt="操作截图 14" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl top nodes</span><br><span class="line"># kubectl top pod -n kube-system</span><br></pre></td></tr></table></figure><p>可以看到所有namespace都成功运行，配置成功。</p><p><img src="/images/posts/kubernetes-lab-notes/image-015.png" alt="操作截图 15" /></p><h2 id="四-练习kubectl基本命令"><a class="markdownIt-Anchor" href="#四-练习kubectl基本命令"></a> 四、练习kubectl基本命令</h2><h3 id="41-pod-基本操作"><a class="markdownIt-Anchor" href="#41-pod-基本操作"></a> 4.1 pod 基本操作</h3><p>查看kubernetes集群信息</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl cluster-info</span><br></pre></td></tr></table></figure><p>查看kubernetes版本</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl version</span><br><span class="line"># kubectl version --short</span><br></pre></td></tr></table></figure><p>查看kubernetes所支持的api-version</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl api-versions</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-016.png" alt="操作截图 16" /></p><p>删除节点及重新加入</p><p>步骤1、通过kubeadm drain命令把节点设置为维护模式，会把已经在该节点上运行的业务pod驱逐到其他节点运行</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl drain node03 --delete-emptydir-data --force --ignore-daemonsets</span><br><span class="line"># kubectl get node</span><br></pre></td></tr></table></figure><p>步骤2、删除这个节点</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete nodes node03</span><br></pre></td></tr></table></figure><p>步骤3、清空节点上的配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubeadm reset（不管是master还是worker节点清空kubernetes配置都使用该命令）</span><br></pre></td></tr></table></figure><p>v1.24以后使用docker-ce时需添加–cri-socket unix:///var/run/cri-dockerd.sock</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubeadm reset --cri-socket unix:///var/run/cri-dockerd.sock</span><br></pre></td></tr></table></figure><p>步骤4、重新加入集群</p><p>配置namespace练习</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get namespaces</span><br><span class="line"># kubectl get ns</span><br><span class="line"># kubectl create namespace ns1</span><br><span class="line"># kubectl delete namespaces ns1</span><br><span class="line"># kubectl config set-context --current --namespace=default</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-017.png" alt="操作截图 17" /></p><p>查看节点以及服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get service -A</span><br><span class="line"># kubectl get node</span><br><span class="line"># kubectl get service -A</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-018.png" alt="操作截图 18" /></p><h2 id="五-使用命令行和yaml文件创建pod"><a class="markdownIt-Anchor" href="#五-使用命令行和yaml文件创建pod"></a> 五、使用命令行和yaml文件创建pod</h2><h3 id="51-pod创建"><a class="markdownIt-Anchor" href="#51-pod创建"></a> 5.1 Pod创建</h3><p>查看pod</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get pod</span><br><span class="line"># kubectl get pod -n kube-system</span><br><span class="line"># kubectl get pod --all-namespaces</span><br><span class="line"># kubectl get pod –A</span><br></pre></td></tr></table></figure><p>创建pod，创建pod时可带诸多参数，运行kubectl run –h查看更多即可</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">kubectl run NAME --image=image  [--env=&quot;key=value&quot;]  [--port=port]  [--labels=label=value]  [--dry-run=server|client] [--overrides=inline-json] [--command] -- [COMMAND] [args...] [options]</span><br><span class="line"># kubectl run pod1 --image=nginx</span><br></pre></td></tr></table></figure><p>删除pod，增加参–force可加快删除速度</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete pod pod1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-019.png" alt="操作截图 19" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl run pod1 --image=nginx --port=80</span><br><span class="line"># 或kubectl run pod1 --image=nginx --image-pull-policy=IfNotPresent</span><br></pre></td></tr></table></figure><p>集群内每一个pod都会获得一个地址(pod地址是相对临时的，service地址才是相对稳定的)</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get pod -o wide</span><br></pre></td></tr></table></figure><p>通过IP访问pod</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># curl http://10.244.239.9</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-020.png" alt="操作截图 20" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-021.png" alt="操作截图 21" /></p><p>在pod里执行命令</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl exec  pod1 -- ls /usr/share/nginx/html</span><br></pre></td></tr></table></figure><p>物理机和pod互拷文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl cp /etc/hosts pod1:/usr/share/nginx/html/</span><br><span class="line"># kubectl cp pod1:/usr/share/nginx/html/hosts /root/hosts</span><br><span class="line"># kubectl cp pod1:/usr/share/nginx/html/ /opt/</span><br></pre></td></tr></table></figure><p>进入pod里并获取bash</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl exec -it pod1 -- bash</span><br><span class="line">root@pod1:/#</span><br><span class="line">root@pod1:/# exit</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-022.png" alt="操作截图 22" /></p><p>如果一个pod有多个容器默认进入的是第一个容器，想进入指定容器用-c指定容器名。</p><p>查看pod里的输出，如果一个pod有多个容器，需要使用-c指定查看哪个容器的输出。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl logs pod1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-023.png" alt="操作截图 23" /></p><p>使用yaml的方式来创建pod可以在文件里指定更多的属性。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># mkdir pod ; cd pod</span><br><span class="line"># kubectl run pod2 --image=nginx --dry-run=client -o yaml &gt; pod2.yaml</span><br><span class="line"># cat pod2.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-024.png" alt="操作截图 24" /></p><p>*YAML文件写法是分级的，子级和父级之间要缩进2个空格(不能按Tab键)，子级的第一个位置可以以“-”开头，这个“-”和父级对齐。</p><p>YAML文件里第一级常见参数主要有以下四个：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">apiVersion: 指定pod的apiVersion，固定值v1</span><br><span class="line">kind: 指定当前yaml要创建的类型是pod，与apiVersion值是对应的</span><br><span class="line">metadata:用于指定pod的元数据信息，包括pod名、pod标签和所在命名空间等信息</span><br><span class="line">spec:定义容器及各种策略</span><br><span class="line"># vi pod3.yaml</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Pod</span><br><span class="line">metadata:</span><br><span class="line">creationTimestamp: null</span><br><span class="line">labels:</span><br><span class="line">run: pod3</span><br><span class="line">name: pod3</span><br><span class="line">spec:</span><br><span class="line">containers:</span><br><span class="line">- command: [&quot;sh&quot;,&quot;-c&quot;,&quot;echo aa;sleep 1000&quot;]</span><br><span class="line">image: nginx</span><br><span class="line">imagePullPolicy: IfNotPresent</span><br><span class="line">name: c1</span><br><span class="line">resources: &#123;&#125;</span><br><span class="line">- image: nginx</span><br><span class="line">imagePullPolicy: IfNotPresent</span><br><span class="line">name: c2</span><br><span class="line">dnsPolicy: ClusterFirst</span><br><span class="line">restartPolicy: Always</span><br><span class="line">status: &#123;&#125;</span><br></pre></td></tr></table></figure><p>使用YAML文件创建pod：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f pod3.yaml</span><br><span class="line"># kubectl get pod</span><br><span class="line"># kubectl logs pod3 -c c1</span><br><span class="line"># kubectl logs pod3 -c c2</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-025.png" alt="操作截图 25" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-026.png" alt="操作截图 26" /></p><p>查看pod属性</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl describe pod pod3</span><br></pre></td></tr></table></figure><p>进入pod并获取bash</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl exec -it pod3 -- bash</span><br><span class="line"># kubectl exec -it pod3 -c c2 -- bash</span><br></pre></td></tr></table></figure><p>删除pod</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete -f pod3.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-027.png" alt="操作截图 27" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-028.png" alt="操作截图 28" /></p><p>创建deployment</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create deployment nginx --image=nginx --replicas=2</span><br></pre></td></tr></table></figure><p>查看deployment</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get deployments.apps</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-029.png" alt="操作截图 29" /></p><p>修改镜像下载策略以提高速度</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl edit deployments.apps nginx</span><br><span class="line">imagePullPolicy: Always</span><br></pre></td></tr></table></figure><p>修改副本数</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl scale deployment nginx --replicas=4</span><br></pre></td></tr></table></figure><p>删除deployment</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete deployments.apps nginx</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-030.png" alt="操作截图 30" /></p><p>通过yaml方式创建depoyment</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create deployment nginx --image=nginx --dry-run=client -o yaml &gt; d1.yaml</span><br><span class="line"># vi d1.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-031.png" alt="操作截图 31" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f d1.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-032.png" alt="操作截图 32" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get deployments.apps</span><br><span class="line"># kubectl get deployments.apps -o wide</span><br><span class="line"># kubectl get pod</span><br><span class="line"># kubectl get pod -o wide</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-033.png" alt="操作截图 33" /></p><p>删除一个pod后，会自动创建出一个新的pod，以满足replicas数。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete pod nginx-57cc89bc77-2g66z</span><br><span class="line"># kubectl get pod</span><br></pre></td></tr></table></figure><p>删除deployment</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete -f d1.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-034.png" alt="操作截图 34" /></p><p>当deployment的pod负载较大，需要更多的pod来分摊负载，就需要管理员手动去调整副本数。</p><p>可以设置让k8s根据pod负载情况自动去调整deployment里pod的副本数，这可以通过水平自动更新HPA(Horizontal Pod Autoscalers)来实现,通过检查pod的cpu负载通知deployment让其自动更新pod数量。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">kubectl autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]</span><br><span class="line">--cpu-percent如果不写默认是80.</span><br></pre></td></tr></table></figure><p>查看当前是否有hpa配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get hpa</span><br></pre></td></tr></table></figure><p>创建hpa</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get deployments.apps</span><br><span class="line"># kubectl autoscale deployment nginx --min=2  --max=10</span><br></pre></td></tr></table></figure><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get horizontalpodautoscalers.autoscaling</span><br><span class="line"># kubectl scale deployment nginx --replicas=1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-035.png" alt="操作截图 35" /></p><p>删除hpa</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete horizontalpodautoscalers.autoscaling nginx</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-036.png" alt="操作截图 36" /></p><p>修改deployment的配置，设置每个容器的资源限制</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># kubectl edit deployments.apps nginx</span><br><span class="line">- image: nginx</span><br><span class="line">imagePullPolicy: IfNotPresent</span><br><span class="line">name: nginx</span><br><span class="line">resources:</span><br><span class="line">requests:</span><br><span class="line">cpu: 100m</span><br></pre></td></tr></table></figure><p>创建deployment副本数为1个</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl scale deployment nginx --replicas=1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-037.png" alt="操作截图 37" /></p><p>创建新的HPA，使得每个pod的cpu数最大使用率不超过70%</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl autoscale deployment nginx --min=1 --max=5 --cpu-percent=70</span><br></pre></td></tr></table></figure><p>为deployment创建一个服务，类型为NodePort</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort</span><br><span class="line"># kubectl get service</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-038.png" alt="操作截图 38" /></p><p>在任一客户端安装ab测试对其进行压测：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># yum install httpd-tools –y   (# apt install apache2-utils)</span><br><span class="line"># ab -t 600 -n 1000000 -c 1000 http://192.168.111.135:30267/index.html</span><br></pre></td></tr></table></figure><p>随着请求CPU负载增加pod自动扩展。</p><p>负载降低后pod数不是立刻减少，要等到一段时间，默认是5m，目的是防止pod数的抖动。</p><p>升级镜像可以通过以下三种方法：</p><h3 id="1-kubectl-edit-deploy"><a class="markdownIt-Anchor" href="#1-kubectl-edit-deploy"></a> 1、kubectl edit deploy</h3><h3 id="2-修改yaml文件然后执行kubectl-apply-f-yaml文件"><a class="markdownIt-Anchor" href="#2-修改yaml文件然后执行kubectl-apply-f-yaml文件"></a> 2、修改yaml文件，然后执行kubectl apply -f yaml文件</h3><h3 id="3-命令行方式修改-命令行方式可以记录镜像变更信息回滚方便"><a class="markdownIt-Anchor" href="#3-命令行方式修改-命令行方式可以记录镜像变更信息回滚方便"></a> 3、命令行方式修改。命令行方式可以记录镜像变更信息，回滚方便。</h3><p>命令行升级deployment里镜像的语法：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">kubectl set image deploy/名字  容器名=镜像  &lt;--record&gt;</span><br><span class="line"># kubectl set image deploy/nginx nginx=nginx:latest</span><br><span class="line"># kubectl set image deploy/nginx nginx=nginx:1.21</span><br><span class="line"># kubectl get deployments.apps -o wide</span><br><span class="line"># kubectl set image deploy/nginx nginx=nginx:1.20</span><br><span class="line"># kubectl get deployments.apps -o wide</span><br></pre></td></tr></table></figure><p>查看镜像变化过程，由于没有记录无法查看，</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl rollout history deployment nginx</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-039.png" alt="操作截图 39" /></p><p>更新镜像时增加–record选项记录更新以方便镜像回滚。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl set image deploy/nginx nginx=nginx:1.20  --record</span><br><span class="line"># kubectl set image deploy/nginx nginx=nginx:1.21  --record</span><br><span class="line"># kubectl rollout history deployment nginx</span><br><span class="line"># kubectl get deployments.apps nginx -o wide</span><br></pre></td></tr></table></figure><p>如果变更后的镜像有问题，我们可以把镜像回滚到变更之前的版本，回滚的语法是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">kubectl rollout undo deployment 名字 --to-revision=版本</span><br><span class="line"># kubectl rollout undo deployment nginx --to-revision=5</span><br><span class="line"># kubectl get deployments.apps nginx -o wide</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-040.png" alt="操作截图 40" /></p><p>对deployment可能会有多个pod，使用滚动更新不是一次性全部更新，而是先更新几个pod镜像，更新完成后再更新几个，直至将所有pod全部更新完毕。</p><p>rollingUpgrade主要涉及两个参数：</p><h3 id="1-maxsurge指定一次创建几个pod可以是百分比也可以是具体数字"><a class="markdownIt-Anchor" href="#1-maxsurge指定一次创建几个pod可以是百分比也可以是具体数字"></a> 1、maxSurge：指定一次创建几个pod，可以是百分比，也可以是具体数字</h3><h3 id="2-maxunavailable用来指定最多删除几个pod可以是百分比也可以是具体数字"><a class="markdownIt-Anchor" href="#2-maxunavailable用来指定最多删除几个pod可以是百分比也可以是具体数字"></a> 2、maxUnavailable：用来指定最多删除几个pod，可以是百分比，也可以是具体数字</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># kubectl edit deployments.apps nginx</span><br><span class="line">strategy:</span><br><span class="line">rollingUpdate:</span><br><span class="line">maxSurge: 25%</span><br><span class="line">maxUnavailable: 25%</span><br><span class="line">type: RollingUpdate</span><br></pre></td></tr></table></figure><p>默认为25%。可修改为1进行查看，即变更镜像时删除一个创建一个。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl scale deployment nginx --replicas=5</span><br><span class="line"># kubectl set image deploy/nginx nginx=nginx:1.21</span><br><span class="line"># kubectl set image deploy/nginx nginx=nginx:1.20</span><br><span class="line"># kubectl get pod</span><br></pre></td></tr></table></figure><p>因为docker现在被墙了，其他版本镜像无法拉取</p><p><img src="/images/posts/kubernetes-lab-notes/image-041.png" alt="操作截图 41" /></p><h3 id="52-job-练习-计算圆周率小数点后-2000-位"><a class="markdownIt-Anchor" href="#52-job-练习-计算圆周率小数点后-2000-位"></a> 5.2 job 练习： 计算圆周率小数点后 2000 位</h3><p>job：执行一次性任务，如数据处理分析测试运算等。</p><p>创建一个yaml文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create job job3 --image=busybox --dry-run=client -o yaml -- sh -c &quot;echo hello &amp;&amp; sleep 30&quot; &gt; job3.yaml</span><br></pre></td></tr></table></figure><p>对生成的yaml进行修改添加imagePullPolicy:IfNotPresent后创建job</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f job3.yaml</span><br></pre></td></tr></table></figure><p>当任务完成时job完成，pod状态变为Completed。</p><p>删除job</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete jobs.batch job3</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-042.png" alt="操作截图 42" /></p><p>job练习：计算圆周率小数点后2000位</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create job job2 --image=perl -- perl -Mbignum=bpi -wle &#x27;print bpi(2000)&#x27;</span><br><span class="line"># kubectl get pod</span><br><span class="line"># kubectl get jobs.batch</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-043.png" alt="操作截图 43" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-044.png" alt="操作截图 44" /></p><p>查看计算结果</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl logs job2-le3gv</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-045.png" alt="操作截图 45" /></p><h3 id="53-cronjob"><a class="markdownIt-Anchor" href="#53-cronjob"></a> 5.3 cronjob</h3><p>cronjob是周期性的、循环的。（linux上使用cron周期性执行任务）</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get cronjobs.batch</span><br></pre></td></tr></table></figure><p>创建cronjobs</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create cronjob NAME --image=image --schedule=‘cron表达式&#x27; -- [COMMAND] [args...] [flags] [options]</span><br></pre></td></tr></table></figure><p>创建一个每一分钟执行一次的cronjob</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create cronjob cjob1 --image=busybox --schedule=&quot;*/1 * * * *&quot; --dry-run=client  -o yaml -- /bin/sh -c &quot;echo hello &amp;&amp; sleep 30&quot; &gt; cjob1.yaml</span><br></pre></td></tr></table></figure><p>修改添加镜像下载策略imagePullPolicy: IfNotPresent</p><p>创建cronjob</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f cjob1.yaml</span><br><span class="line"># kubectl get cronjobs.batch</span><br><span class="line"># kubectl get pod</span><br></pre></td></tr></table></figure><p>每隔1分钟生成一个pod，该pod完成任务sleep 30后就结束。</p><p>Completed pod数由选项successfulJobsHistoryLimit决定，默认为3。</p><p>删除cronjob</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete cronjobs.batch cjob1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-046.png" alt="操作截图 46" /></p><h3 id="54-statefulsets"><a class="markdownIt-Anchor" href="#54-statefulsets"></a> 5.4 StatefulSets</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"># vi headless-nginx.yaml</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Service</span><br><span class="line">metadata:</span><br><span class="line">name: nginx</span><br><span class="line">labels:</span><br><span class="line">app: nginx</span><br><span class="line">spec:</span><br><span class="line">ports:</span><br><span class="line">- port: 80</span><br><span class="line">name: web</span><br><span class="line">clusterIP: None</span><br><span class="line">selector:</span><br><span class="line">app: nginx</span><br><span class="line"># kubectl apply -f headless-nginx.yaml</span><br><span class="line"># vi stateful-nginx.yaml</span><br><span class="line">apiVersion: apps/v1</span><br><span class="line">kind: StatefulSet</span><br><span class="line">metadata:</span><br><span class="line">name: nginx</span><br><span class="line">spec:</span><br><span class="line">selector:</span><br><span class="line">matchLabels:</span><br><span class="line">app: nginx</span><br><span class="line">serviceName: &quot;nginx&quot;</span><br><span class="line">replicas: 3</span><br><span class="line">minReadySeconds: 10</span><br><span class="line">template:</span><br><span class="line">metadata:</span><br><span class="line">labels:</span><br><span class="line">app: nginx</span><br><span class="line">spec:</span><br><span class="line">terminationGracePeriodSeconds: 10</span><br><span class="line">containers:</span><br><span class="line">- name: nginx</span><br><span class="line">image: nginx:latest</span><br><span class="line">imagePullPolicy: IfNotPresent</span><br><span class="line">ports:</span><br><span class="line">- containerPort: 80</span><br><span class="line">name: web</span><br><span class="line"># kubectl apply -f stateful-nginx.yaml</span><br></pre></td></tr></table></figure><h2 id="六-使用命令行和yaml文件创建deployment-and-job"><a class="markdownIt-Anchor" href="#六-使用命令行和yaml文件创建deployment-and-job"></a> 六、使用命令行和yaml文件创建deployment and job</h2><h3 id="61-deployment"><a class="markdownIt-Anchor" href="#61-deployment"></a> 6.1 deployment</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create deployment web1 --image=nginx --dry-run=client -o yaml &gt; web1.yaml</span><br><span class="line"># vi web1.yaml</span><br><span class="line"># kubectl apply -f web1.yaml</span><br><span class="line"># kubectl get deployments.apps -o wide</span><br><span class="line"># kubectl get pod -o wide</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-047.png" alt="操作截图 47" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl expose deployment nginx --name=web1 --port=80 --target-port=80</span><br></pre></td></tr></table></figure><p>不使用–name指定服务名，则保持和deployment名字一致。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">--port指服务端口， --target-port是后端pod运行服务的端口。</span><br></pre></td></tr></table></figure><p>查看服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get svc</span><br><span class="line"># kubectl get service -o wide</span><br></pre></td></tr></table></figure><p>查看详细信息</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl describe service web1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-048.png" alt="操作截图 48" /></p><p>使用命令生成yaml文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl expose deployment web1 --name=web1 --port=80 --target-port=80 --dry-run=client -o yaml &gt; svc1.yaml</span><br></pre></td></tr></table></figure><p>查看yaml文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># vi svc1.yaml</span><br></pre></td></tr></table></figure><p>使用yaml文件创建service</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f svc1.yaml</span><br></pre></td></tr></table></figure><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get svc</span><br></pre></td></tr></table></figure><p>删除服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete -f svc1.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-049.png" alt="操作截图 49" /></p><h3 id="62-服务发布"><a class="markdownIt-Anchor" href="#62-服务发布"></a> 6.2 服务发布</h3><p>外部主机访问集群服务主要有三种方式：</p><h3 id="1-nodeport"><a class="markdownIt-Anchor" href="#1-nodeport"></a> 1、NodePort</h3><p>将服务映射到物理机(集群中所有节点)的某个端口提供外界访问</p><h3 id="2-loadbalancer"><a class="markdownIt-Anchor" href="#2-loadbalancer"></a> 2、LoadBalancer</h3><p>使用LoadBalancer给svc分配一个地址(外界可访问地址)，svc直接对外提供服务。</p><p>需要安装部署第三方工具metallb。</p><h3 id="3-ingress"><a class="markdownIt-Anchor" href="#3-ingress"></a> 3、ingress</h3><p>使用nginx-ingress实现反向代理，通过域名对外提供服务。</p><p>需要安装部署nginx-ingress控制器。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl expose deployment web1 --name=web1 --port 80 --type=NodePort</span><br><span class="line"># kubectl get svc</span><br><span class="line"># kubectl edit svc web1</span><br></pre></td></tr></table></figure><p>Yaml文件为:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">apiVersion: v1</span><br><span class="line">kind: Service</span><br><span class="line">metadata:</span><br><span class="line">creationTimestamp: null</span><br><span class="line">labels:</span><br><span class="line">app: web1</span><br><span class="line">name: web1</span><br><span class="line">spec:</span><br><span class="line">ports:</span><br><span class="line">- port: 80</span><br><span class="line">protocol: TCP</span><br><span class="line">targetPort: 80</span><br><span class="line">selector:</span><br><span class="line">app: web1</span><br><span class="line">type: NodePort</span><br><span class="line">status:</span><br><span class="line">loadBalancer: &#123;&#125;</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-050.png" alt="操作截图 50" /></p><h2 id="七-安装loadblancer"><a class="markdownIt-Anchor" href="#七-安装loadblancer"></a> 七、安装loadblancer</h2><p>使用LoadBalancer需要安装部署第三方工具metalllb。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl edit configmap -n kube-system kube-proxy</span><br></pre></td></tr></table></figure><p>修改 strictARP: true</p><p>下载部署metallb所需yaml文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># mkdir ~/metallb</span><br><span class="line"># cd ~/metallb/</span><br><span class="line"># wget https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-051.png" alt="操作截图 51" /></p><p>部署metallb</p><p>可以修改配置文件添加imagePullPolicy: IfNotPresent然后进行部署</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f metallb-native.yaml</span><br></pre></td></tr></table></figure><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get deployments.apps -n metallb-system</span><br><span class="line"># kubectl get daemonsets.apps  -n metallb-system</span><br><span class="line"># kubectl get pod -n metallb-system</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-052.png" alt="操作截图 52" /></p><p>创建一个外部地址池</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"># vi ipaddresspool.yaml</span><br><span class="line">apiVersion: metallb.io/v1beta1</span><br><span class="line">kind: IPAddressPool</span><br><span class="line">metadata:</span><br><span class="line">namespace: metallb-system</span><br><span class="line">name: doc-l2-label</span><br><span class="line">labels:</span><br><span class="line">zone: east</span><br><span class="line">spec:</span><br><span class="line">addresses:</span><br><span class="line">- 192.168.111.135/24</span><br><span class="line"># kubectl apply -f ipaddresspool.yaml</span><br><span class="line"># kubectl get ipaddresspools.metallb.io  -n metallb-system</span><br></pre></td></tr></table></figure><p>修改地址池范围，指定可用地址</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl edit ipaddresspools.metallb.io  -n metallb-system</span><br><span class="line">spec:</span><br><span class="line">addresses:</span><br><span class="line">- 192.168.100.210 - 192.168.100.219</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-053.png" alt="操作截图 53" /></p><p>发布地址池：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># vi l2advertisement.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-054.png" alt="操作截图 54" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f l2advertisement.yaml</span><br><span class="line"># kubectl get l2advertisements.metallb.io -n metallb-system</span><br></pre></td></tr></table></figure><p>创建一个pod</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl run pod1 --image=nginx --image-pull-policy=IfNotPresent</span><br></pre></td></tr></table></figure><p>为pod1创建一个名为svc1类型为LoadBalancer的服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl expose pod pod1 --name=svc1 --port=80 --type=LoadBalancer</span><br><span class="line"># kubectl get svc</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-055.png" alt="操作截图 55" /></p><p>访问EXTERNAL-IP即可。</p><p>删除svc1和pod1</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete svc svc1</span><br><span class="line"># kubectl delete pod pod1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-056.png" alt="操作截图 56" /></p><h2 id="八-安装ingress-nginx"><a class="markdownIt-Anchor" href="#八-安装ingress-nginx"></a> 八、安装ingress-nginx</h2><p>官网：<a href="https://kubernetes.github.io/ingress-nginx/">https://kubernetes.github.io/ingress-nginx/</a></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># mkdir ~/ingress-nginx</span><br><span class="line"># cd ~/ingress-nginx/</span><br><span class="line"># wget http://bigdata.cg.lzu.edu.cn/kubernetes/ingress-nginx/deploy.yaml</span><br><span class="line"># cat  deploy.yaml | grep image</span><br></pre></td></tr></table></figure><p>所需镜像国内网络无法下载，从国外下载打包回来。并修改yaml文件中镜像名称和实际镜像名称一致。</p><p>校内镜像下载： wget <a href="http://bigdata.cg.lzu.edu.cn/kubernetes/ingress-nginx-v1.7.0.tar">http://bigdata.cg.lzu.edu.cn/kubernetes/ingress-nginx-v1.7.0.tar</a></p><p>下载镜像文件并在所有节点加载镜像。并根据实际镜像tag修改deploy.yaml文件。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f deploy.yaml</span><br><span class="line"># kubectl get ns</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-057.png" alt="操作截图 57" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get pod -n ingress-nginx</span><br><span class="line"># kubectl get svc -n ingress-nginx</span><br><span class="line"># kubectl get configmaps -n ingress-nginx</span><br></pre></td></tr></table></figure><p>修改pod副本数为worker数</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl scale deployment ingress-nginx-controller -n ingress-nginx --replicas=2</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-058.png" alt="操作截图 58" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get svc -n ingress-nginx</span><br></pre></td></tr></table></figure><p>创建一个名为pod1的pod</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl run pod1 --image=nginx --image-pull-policy=IfNotPresent</span><br></pre></td></tr></table></figure><p>为这个pod1创建名为pod1的svc</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl expose pod pod1 --name=pod1 --port 80</span><br></pre></td></tr></table></figure><p>创建ingress策略</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create ingress pod1.lzu.edu.cn --class=nginx --rule=pod1.lzu.edu.cn/*=pod1:80</span><br><span class="line"># kubectl get ingress</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-059.png" alt="操作截图 59" /></p><p>手动添加解析后访问</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"># curl http://pod1.lzu.edu.cn</span><br><span class="line"># kubectl create ingress pod1.lzu.edu.cn --class=nginx --rule=pod1.lzu.edu.cn/*=pod1:80 --dry-run=client -o yaml &gt; pod1.lzu.edu.cn.ymal</span><br><span class="line"># cat pod1.lzu.edu.cn.ymal</span><br><span class="line">apiVersion: networking.k8s.io/v1</span><br><span class="line">kind: Ingress</span><br><span class="line">metadata:</span><br><span class="line">creationTimestamp: null</span><br><span class="line">name: pod1.lzu.edu.cn</span><br><span class="line">spec:</span><br><span class="line">ingressClassName: nginx</span><br><span class="line">rules:</span><br><span class="line">- host: pod1.lzu.edu.cn</span><br><span class="line">http:</span><br><span class="line">paths:</span><br><span class="line">- backend:</span><br><span class="line">service:</span><br><span class="line">name: pod1</span><br><span class="line">port:</span><br><span class="line">number: 80</span><br><span class="line">path: /</span><br><span class="line">pathType: Prefix</span><br><span class="line">status:</span><br><span class="line">loadBalancer: &#123;&#125;</span><br></pre></td></tr></table></figure><h2 id="九-准备nfs-server并安装其他服务"><a class="markdownIt-Anchor" href="#九-准备nfs-server并安装其他服务"></a> 九、准备NFS server并安装其他服务</h2><p>nfs-server configure：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install nfs-kernel-server</span><br><span class="line"># mkdir /nfs_data</span><br><span class="line"># vi /etc/exports</span><br><span class="line">/nfs_data 192.168.111.135/24(rw,insecure,no_subtree_check,no_root_squash,crossmnt)</span><br><span class="line"># systemctl restart nfs-server</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-060.png" alt="操作截图 60" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git</span><br><span class="line"># cd ~/nfs-subdir-external-provisioner/deploy</span><br><span class="line"># vi deployment.yaml</span><br></pre></td></tr></table></figure><p>增加镜像下载策略和修改NFS_SERVER和NFS_PATH和nfs server path</p><p><img src="/images/posts/kubernetes-lab-notes/image-061.png" alt="操作截图 61" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f rbac.yaml</span><br><span class="line"># kubectl apply -f deployment.yaml</span><br><span class="line"># kubectl get deployments.apps</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-062.png" alt="操作截图 62" /></p><p>镜像需要从境外下载，下载镜像导入到各工作节点</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># wget http://bigdata.cg.lzu.edu.cn/kubernetes/nfs-subdir-provisioner-v4.0.2.tar</span><br><span class="line"># docker image load -i nfs-subdir-provisioner-v4.0.2.tar    （所有节点均需导入）</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-063.png" alt="操作截图 63" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f class.yaml</span><br><span class="line"># kubectl get sc</span><br><span class="line"># kubectl apply -f test-claim.yaml</span><br><span class="line"># kubectl get pvc</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-064.png" alt="操作截图 64" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"># cat test-claim.yaml</span><br><span class="line">kind: PersistentVolumeClaim</span><br><span class="line">apiVersion: v1</span><br><span class="line">metadata:</span><br><span class="line">name: test-claim</span><br><span class="line">spec:</span><br><span class="line">storageClassName: nfs-client</span><br><span class="line">accessModes:</span><br><span class="line">- ReadWriteMany</span><br><span class="line">resources:</span><br><span class="line">requests:</span><br><span class="line">storage: 1Mi</span><br><span class="line"># cat test-pod.yaml</span><br><span class="line">kind: Pod</span><br><span class="line">apiVersion: v1</span><br><span class="line">metadata:</span><br><span class="line">name: test-pod</span><br><span class="line">spec:</span><br><span class="line">containers:</span><br><span class="line">- name: test-pod</span><br><span class="line">image: busybox:stable</span><br><span class="line">command:</span><br><span class="line">- &quot;/bin/sh&quot;</span><br><span class="line">args:</span><br><span class="line">- &quot;-c&quot;</span><br><span class="line">- &quot;touch /mnt/SUCCESS &amp;&amp; exit 0 || exit 1&quot;</span><br><span class="line">volumeMounts:</span><br><span class="line">- name: nfs-pvc</span><br><span class="line">mountPath: &quot;/mnt&quot;</span><br><span class="line">restartPolicy: &quot;Never&quot;</span><br><span class="line">volumes:</span><br><span class="line">- name: nfs-pvc</span><br><span class="line">persistentVolumeClaim:</span><br><span class="line">claimName: test-claim</span><br></pre></td></tr></table></figure><h2 id="十-安装heml-tool-rancher-danshbord-wordpress"><a class="markdownIt-Anchor" href="#十-安装heml-tool-rancher-danshbord-wordpress"></a> 十、安装heml tool、rancher danshbord、wordpress</h2><p>下载helm：<a href="https://github.com/helm/helm/releases">https://github.com/helm/helm/releases</a></p><h3 id="1-下载-需要的版本"><a class="markdownIt-Anchor" href="#1-下载-需要的版本"></a> 1、下载 需要的版本</h3><h3 id="2-解压"><a class="markdownIt-Anchor" href="#2-解压"></a> 2、解压</h3><h3 id="3-在解压目中找到helm程序移动到需要的目录中mv-~linux-amd64helm-usrlocalbinhelm"><a class="markdownIt-Anchor" href="#3-在解压目中找到helm程序移动到需要的目录中mv-~linux-amd64helm-usrlocalbinhelm"></a> 3、在解压目中找到helm程序，移动到需要的目录中(mv ~/linux-amd64/helm /usr/local/bin/helm)</h3><p>然后就可以执行客户端程序并 添加稳定仓库: helm help.</p><p>helm仓库语法帮助</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># helm repo -h</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-065.png" alt="操作截图 65" /></p><p>添加仓库语法：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm repo add 名字  地址</span><br></pre></td></tr></table></figure><p>添加仓库</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># helm repo add github https://burdenbear.github.io/kube-charts-mirror/</span><br></pre></td></tr></table></figure><p>查看当前的仓库</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># helm repo list</span><br></pre></td></tr></table></figure><p>仓库更新</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># helm repo update</span><br></pre></td></tr></table></figure><p>删除仓库</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># helm repo remove github</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-066.png" alt="操作截图 66" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-067.png" alt="操作截图 67" /></p><p>通过helm安装cert-manager</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># helm repo add jetstack https://charts.jetstack.io</span><br><span class="line"># kubectl create namespace cert-manager</span><br><span class="line"># kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.7.1/cert-manager.crds.yaml</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-068.png" alt="操作截图 68" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># proxy_host=username:password@proxyserver:port</span><br><span class="line"># helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.7.1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-069.png" alt="操作截图 69" /></p><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl rollout status deployment -n cert-manager cert-manager</span><br><span class="line"># kubectl get deployments.apps -n cert-manager</span><br><span class="line"># kubectl get pods -n cert-manager</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-070.png" alt="操作截图 70" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># helm repo add rancher-stable https://releases.rancher.com/server-charts/stable</span><br><span class="line"># kubectl create namespace cattle-system</span><br></pre></td></tr></table></figure><p>通过文件创建secret</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># vi mypasswd</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-071.png" alt="操作截图 71" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create secret generic mysecret2 --from-file=mypasswd</span><br></pre></td></tr></table></figure><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get secrets</span><br><span class="line"># kubectl get secrets mysecret2</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-072.png" alt="操作截图 72" /></p><p>看到其类型为Opaque，即使用base64编码格式的secret</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get secrets mysecret2 -o yaml</span><br></pre></td></tr></table></figure><p>看到只有一个data，名为mypasswd，内容被base64编码过了</p><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl get secrets mysecret2 -o jsonpath=&#x27;&#123;.data.mypasswd&#125; &#x27;</span><br><span class="line"># kubectl get secrets mysecret2 -o jsonpath=&#x27;&#123;.data.mypasswd&#125;&#x27; | base64 -d</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-073.png" alt="操作截图 73" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-074.png" alt="操作截图 74" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl apply -f pod1_secret.yaml</span><br><span class="line"># kubectl get pod</span><br><span class="line"># kubectl exec pod1 -- ls /root/mypasswd</span><br><span class="line"># kubectl exec pod1 -- cat /root/mypasswd/mypasswd</span><br></pre></td></tr></table></figure><p>通过helm安装cert-manager</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># helm repo add jetstack https://charts.jetstack.io</span><br><span class="line"># kubectl create namespace cert-manager</span><br><span class="line"># kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.7.1/cert-manager.crds.yaml</span><br><span class="line"># proxy_host=username:password@proxyserver:port</span><br><span class="line"># helm upgrade --install cert-manager jetstack/cert-manager \</span><br><span class="line">--namespace cert-manager --version v1.7.1 \</span><br><span class="line">--set http_proxy=http://$&#123;proxy_host&#125; \</span><br><span class="line">--set https_proxy=http://$&#123;proxy_host&#125; \</span><br><span class="line">--set no_proxy=127.0.0.0/8\\,10.0.0.0/8\\,cattle-system.svc\\,172.16.0.0/12\\,192.168.0.0/16\\,.svc\\,.cluster.local</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-075.png" alt="操作截图 75" /></p><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># kubectl rollout status deployment -n cert-manager cert-manager</span><br><span class="line"># kubectl get deployments.apps -n cert-manager</span><br><span class="line"># kubectl get pods -n cert-manager</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-076.png" alt="操作截图 76" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># helm repo add rancher-stable https://releases.rancher.com/server-charts/stable</span><br><span class="line"># kubectl create namespace cattle-system</span><br><span class="line"># kubectl create secret tls lzu-key --cert=lzu2023.crt --key=lzu2023.key -n cattle-system</span><br><span class="line"># helm upgrade -install rancher rancher-stable/rancher \</span><br><span class="line">--namespace cattle-system \</span><br><span class="line">--set hostname=student0.lzu.edu.cn \</span><br><span class="line">--set ingress.tls.source=lzu-key  \</span><br><span class="line">--set https_proxy=http://$&#123;proxy_host&#125; \</span><br><span class="line">--set no_proxy=127.0.0.0/8\\,10.0.0.0/8\\,cattle-system.svc\\,172.16.0.0/12\\,192.168.0.0/16\\,.svc\\,.cluster.local</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-077.png" alt="操作截图 77" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># helm upgrade --install rancher rancher-stable/rancher --namespace cattle-system</span><br></pre></td></tr></table></figure><p>查看</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># kubectl rollout status deployment -n cattle-system rancher</span><br><span class="line"># kubectl get deployments.apps -n cattle-system</span><br><span class="line"># kubectl get service -n cattle-system</span><br><span class="line"># kubectl get ingress -n cattle-system</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-078.png" alt="操作截图 78" /></p><p>默认创建的ingress类型不是nginx，没有注册到ingress-nginx-controller，删除重建(rke创建的kubernetes无需此步骤)</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># kubectl delete ingress rancher -n cattle-system</span><br><span class="line"># kubectl create ingress rancher --class=nginx --rule=student0.lzu.edu.cn/*=rancher:80,tsl=lzu-key   -n cattle-system</span><br></pre></td></tr></table></figure><p><img src="/images/posts/kubernetes-lab-notes/image-079.png" alt="操作截图 79" /></p><p>rancher使用</p><p>客户端自行添加域名解析</p><p>访问<a href="https://student0.lzu.edu.cn">https://student0.lzu.edu.cn</a></p><p>修改设置管理员密码时需满足复杂度要求，默认最小12位。</p><p>自建kubernetes在rancher创建ingress默认class不是nginx，需手工创建。</p><p>(ingress-nginx-controller使用了LoadBalancer分配的外部地址。)</p><p>rke创建的kubernetes在集群前端添加负载均衡即可。负载均衡将80/443负载到kubernetes集群所有worker节点，DNS将域名解析到负载均衡对外端口。</p><p>(rancher将nginx-ingress-controller采用了daemonset部署，直接使用了worker节点80/443端口）</p><p><img src="/images/posts/kubernetes-lab-notes/image-080.png" alt="操作截图 80" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-081.png" alt="操作截图 81" /></p><p><img src="/images/posts/kubernetes-lab-notes/image-082.png" alt="操作截图 82" /></p><p>Rancher 还可以通过 RKE 方式自动搭建 Kubernetes 集群：</p><p><img src="/images/posts/kubernetes-lab-notes/image-083.png" alt="操作截图 83" /></p><p>此外，还可以在 K8S 上搭建 WordPress，流程和之前单独部署 WordPress 基本一致：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># cd /var/www/html/</span><br><span class="line"># wget https://cn.wordpress.org/latest-zh_CN.zip</span><br><span class="line"># dnf install -y unzip</span><br><span class="line"># unzip latest-zh_CN.zip</span><br></pre></td></tr></table></figure><p>修改权限</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># chown -R apache. wordpress/</span><br></pre></td></tr></table></figure><p>删除下载文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># rm latest-zh_CN.zip</span><br></pre></td></tr></table></figure><p>将wordpress文件放置到apache根目录</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># mv /var/www/html/wordpress/* /var/www/html/</span><br><span class="line"># rmdir /var/www/html/wordpress/</span><br></pre></td></tr></table></figure><p>修改wordpress配置文件，配置数据库信息</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># cp /var/www/html/wp-config-sample.php /var/www/html/wp-config.php</span><br><span class="line"># vi /var/www/html/wp-config.php</span><br></pre></td></tr></table></figure><p>/** WordPress 数据库名称 */</p><p>define( ‘DB_NAME’, ‘wordpress’ );</p><p>/** 数据库用户名 */</p><p>define( ‘DB_USER’, ‘wordpress’ );</p><p>/** 数据库密码 */</p><p>define( ‘DB_PASSWORD’, ‘Wordpress!2024’ );</p><p>/** 数据库主机 */</p><p>define( ‘DB_HOST’, ‘172.16.249.229:33061’ );</p><p>数据库信息配置正确就可以进行初始化了。</p><p>日志文件目录/var/log/httpd/</p><p>php配置文件/etc/httpd/conf.d/php.conf</p><p>php-fpm配置文件/etc/php-fpm.d/www.conf</p><p>apache配置文件/etc/httpd/conf/httpd.conf</p><p>apache的php配置文件 /etc/httpd/conf.d/php.conf</p><p>supervisor配置文件/etc/supervisord.conf</p><p>supervisor增加的启动服务配置文件/etc/supervisord.d/startup.ini</p><p>下面访问虚拟机ip地址：<a href="http://192.168.111.135">http://192.168.111.135</a></p><p><img src="/images/posts/kubernetes-lab-notes/image-084.png" alt="操作截图 84" /></p><p>至此，K8S 集群安装、基础对象操作、服务暴露、Ingress、NFS、Rancher 和 WordPress 部署都已经完成。</p><h2 id="十一-一些记录"><a class="markdownIt-Anchor" href="#十一-一些记录"></a> 十一、一些记录</h2><p>这次搭建覆盖了 Kubernetes 的几个核心操作面：</p><p>基本kubectl命令：我练习了使用基本的kubectl命令来与Kubernetes集群进行交互。</p><p>Pod操作：我学习了如何对Pod执行基本操作，包括创建、扩展和删除。</p><p>Deployment和Service创建：我使用YAML文件创建了 Deployment和Service，展示了我对Kubernetes声明性配置方法的理解。</p><p>负载均衡器和 ingress 控制器：我安装并配置了负载均衡器和ingress控制器，启用了对Kubernetes应用程序的外部访问。</p><p>NFS服务器和其他服务：我设置了NFS服务器并安装了其他服务，扩展了Kubernetes集群的功能。</p><p>工具和Dashboard：我安装并探索了工具，例如Helm、Rancher Dashboard和WordPress，扩展了我的Kubernetes技能。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这篇记录整理一次 Kubernetes 环境搭建和应用部署流程，内容覆盖 Docker、cri-dockerd、kubeadm、Calico、Metrics Server、Pod、Deployment、Service、LoadBalancer、Ingress、NFS、Hel</summary>
      
    
    
    
    <category term="Cloud Computing and Big Data" scheme="https://zhouzhq2021.github.io/categories/Cloud-Computing-and-Big-Data/"/>
    
    
    <category term="Kubernetes" scheme="https://zhouzhq2021.github.io/tags/Kubernetes/"/>
    
  </entry>
  
  <entry>
    <title>OpenStack 实践：完整环境搭建与服务验证</title>
    <link href="https://zhouzhq2021.github.io/2024/05/28/openstack-lab-notes/"/>
    <id>https://zhouzhq2021.github.io/2024/05/28/openstack-lab-notes/</id>
    <published>2024-05-27T16:00:00.000Z</published>
    <updated>2026-06-08T05:00:50.276Z</updated>
    
    <content type="html"><![CDATA[<p>OpenStack 的部署链路比较长，组件之间的依赖也多。这篇记录按实际搭建顺序展开，从虚拟机环境准备开始，依次配置基础服务、Keystone、Glance、Placement、Nova、Neutron、Dashboard、Cinder，并完成实例创建和冷迁移验证。</p><h2 id="一-环境准备"><a class="markdownIt-Anchor" href="#一-环境准备"></a> 一、环境准备</h2><p>使用 VirtualBox 创建一个 Ubuntu 22.04 虚拟机，OpenStack 的所有组件都部署在这台虚拟机中，详细规格如下：</p><p><img src="/images/posts/openstack-lab-notes/image-003.png" alt="操作截图 3" /></p><p>一个NAT网卡，两个host-only网卡，地址为：192.168.56.103以及192.168.212.4。内存分配8G,8个处理器。其他保持默认。</p><h2 id="二-基础配置"><a class="markdownIt-Anchor" href="#二-基础配置"></a> 二、基础配置</h2><p>基础配置沿用之前整理过的一套 Linux 初始化流程，这里只保留和后续 OpenStack 部署相关的关键操作。</p><h3 id="21-网络配置"><a class="markdownIt-Anchor" href="#21-网络配置"></a> 2.1 网络配置</h3><h3 id="22-主机名配置"><a class="markdownIt-Anchor" href="#22-主机名配置"></a> 2.2 主机名配置</h3><p>配置主机名方便后续的环境配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/hosts</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-004.png" alt="操作截图 4" /></p><p><img src="/images/posts/openstack-lab-notes/image-005.png" alt="操作截图 5" /></p><h3 id="23-配置时区"><a class="markdownIt-Anchor" href="#23-配置时区"></a> 2.3 配置时区</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># timedatectl set-timezone Asia/Shanghai</span><br><span class="line"># apt-get install ntp</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-006.png" alt="操作截图 6" /></p><p><img src="/images/posts/openstack-lab-notes/image-007.png" alt="操作截图 7" /></p><h3 id="24-ssh配置"><a class="markdownIt-Anchor" href="#24-ssh配置"></a> 2.4 SSH配置</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># ssh-keygen -t rsa -P &#x27;&#x27; -f ~/.ssh/id_rsa</span><br><span class="line"># cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys</span><br><span class="line"># chmod 0600 ~/.ssh/authorized_keys</span><br><span class="line"># vim /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p>配置完毕后可以正常SSH登陆虚拟机</p><p><img src="/images/posts/openstack-lab-notes/image-008.png" alt="操作截图 8" /></p><h3 id="25-常规优化"><a class="markdownIt-Anchor" href="#25-常规优化"></a> 2.5 常规优化</h3><p>更新系统</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt update &amp;&amp; apt upgrade -y</span><br></pre></td></tr></table></figure><p>优化file-max nropen和ulimit</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/security/limits.conf</span><br><span class="line"># vi /etc/sysctl.conf</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-009.png" alt="操作截图 9" /></p><h2 id="三-openstack-依赖服务配置"><a class="markdownIt-Anchor" href="#三-openstack-依赖服务配置"></a> 三、OpenStack 依赖服务配置</h2><h3 id="31-rabbimq-server"><a class="markdownIt-Anchor" href="#31-rabbimq-server"></a> 3.1 rabbimq-server</h3><p>安装</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install rabbitmq-server</span><br><span class="line"># vi /etc/rabbitmq/rabbitmq-env.conf</span><br><span class="line">NODENAME=rabbit@localhost</span><br><span class="line">NODE_IP_ADDRESS=0.0.0.0</span><br><span class="line">NODE_PORT=5672</span><br><span class="line"># systemctl restart rabbitmq-server</span><br><span class="line"># systemctl enable rabbitmq-server</span><br></pre></td></tr></table></figure><p>添加用户并配置权限</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># rabbitmqctl add_user openstack &#x27;Openstack!2024&#x27;</span><br><span class="line"># rabbitmqctl set_permissions openstack &quot;.*&quot; &quot;.*&quot; &quot;.*&quot;</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-010.png" alt="操作截图 10" /></p><h3 id="32-memcached"><a class="markdownIt-Anchor" href="#32-memcached"></a> 3.2 memcached</h3><p>安装</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install memcached python3-memcache</span><br><span class="line"># vi /etc/memcached.conf</span><br><span class="line">-l 0.0.0.0</span><br><span class="line"># systemctl restart memcached</span><br><span class="line"># systemctl enable memcached</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-011.png" alt="操作截图 11" /></p><h3 id="33-mariadb-database"><a class="markdownIt-Anchor" href="#33-mariadb-database"></a> 3.3 mariadb database</h3><p>安装</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install mariadb-server python3-pymysql</span><br><span class="line"># vi /etc/mysql/mariadb.conf.d/50-server.cnf</span><br><span class="line">[mysqld]</span><br><span class="line">bind-address            =  0.0.0.0</span><br><span class="line">default-storage-engine = innodb</span><br><span class="line">max_connections = 4096</span><br><span class="line">innodb_file_per_table = on</span><br><span class="line">collation-server = utf8_general_ci</span><br><span class="line">character-set-server = utf8</span><br><span class="line"># systemctl restart mariadb</span><br><span class="line"># systemctl enable mariadb</span><br><span class="line"># mysql_secure_installation</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-012.png" alt="操作截图 12" /></p><h2 id="四-openstack-核心组件部署"><a class="markdownIt-Anchor" href="#四-openstack-核心组件部署"></a> 四、OpenStack 核心组件部署</h2><h3 id="41-keystone"><a class="markdownIt-Anchor" href="#41-keystone"></a> 4.1 keystone</h3><p>创建keystone database</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># mysql -u root -p</span><br><span class="line">&gt; create database keystone;</span><br><span class="line">&gt; grant all privileges on keystone.* to keystone@localhost identified by &#x27;Keystone!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on keystone.* to keystone@&#x27;%&#x27; identified by &#x27;Keystone!2024&#x27;;</span><br><span class="line">&gt; flush privileges;</span><br><span class="line">&gt; exit</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-013.png" alt="操作截图 13" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install keystone python3-openstackclient apache2 libapache2-mod-wsgi-py3 python3-oauth2client</span><br><span class="line"># mv /etc/keystone/keystone.conf /etc/keystone/keystone.conf.org</span><br><span class="line"># grep -vE &quot;^$|^#&quot; /etc/keystone/keystone.conf.org</span><br><span class="line">#  vi /etc/keystone/keystone.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">log_dir = /var/log/keystone</span><br><span class="line">[cache]</span><br><span class="line">memcache_servers = 192.168.56.103:11211</span><br><span class="line">[database]</span><br><span class="line">connection = mysql+pymysql://keystone:Keystone!2024@192.168.56.103/keystone</span><br><span class="line">[token]</span><br><span class="line">provider = fernet</span><br><span class="line"># chmod 640 /etc/keystone/keystone.conf</span><br><span class="line"># chown keystone.keystone /etc/keystone/keystone.conf</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-014.png" alt="操作截图 14" /></p><p>#  su -s /bin/sh -c “keystone-manage db_sync” keystone</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"># keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone</span><br><span class="line"># keystone-manage credential_setup --keystone-user keystone --keystone-group keystone</span><br><span class="line"># controller=192.168.56.103</span><br><span class="line"># keystone-manage bootstrap --bootstrap-password &#x27;Keystone!2024&#x27;  \</span><br><span class="line">--bootstrap-admin-url http://$controller:5000/v3/ \</span><br><span class="line">--bootstrap-internal-url http://$controller:5000/v3/ \</span><br><span class="line">--bootstrap-public-url http://$controller:5000/v3/ \</span><br><span class="line">--bootstrap-region-id RegionOne</span><br><span class="line"># vi /etc/apache2/apache2.conf</span><br><span class="line">ServerName controller</span><br><span class="line"># vi /etc/apache2/conf-enabled/security.conf</span><br><span class="line">ServerTokens  Prod</span><br><span class="line"># systemctl restart apache2</span><br><span class="line"># cat /etc/apache2/sites-enabled/keystone.conf</span><br><span class="line"># netstat -an | grep 5000</span><br><span class="line"># lsof | grep 5000</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-015.png" alt="操作截图 15" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"># vi ~/keystonerc</span><br><span class="line">export OS_PROJECT_DOMAIN_NAME=default</span><br><span class="line">export OS_USER_DOMAIN_NAME=default</span><br><span class="line">export OS_PROJECT_NAME=admin</span><br><span class="line">export OS_USERNAME=admin</span><br><span class="line">export OS_PASSWORD=Keystone!2024</span><br><span class="line">export OS_AUTH_URL=http://192.168.56.103:5000/v3</span><br><span class="line">export OS_IDENTITY_API_VERSION=3</span><br><span class="line">export OS_IMAGE_API_VERSION=2</span><br><span class="line">export PS1=&#x27;\u@\h \W(keystone)\$&#x27;</span><br><span class="line"># chmod 600 ~/keystonerc</span><br><span class="line"># source ~/keystonerc</span><br><span class="line"># echo &quot;source ~/keystonerc &quot; &gt;&gt; ~/.bash_profile</span><br></pre></td></tr></table></figure><p>验证服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># openstack endpoint list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-016.png" alt="操作截图 16" /></p><p>create service project for the openstack</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># openstack project create --domain default --description &quot;Service Project&quot; service</span><br><span class="line"># openstack project list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-017.png" alt="操作截图 17" /></p><p>验证完毕。</p><h3 id="42-验证glance服务"><a class="markdownIt-Anchor" href="#42-验证glance服务"></a> 4.2 验证glance服务</h3><p>创建glance database</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># mysql -u root -p</span><br><span class="line">&gt; create database glance;</span><br><span class="line">&gt; grant all privileges on glance.* to glance@localhost identified by &#x27;Glance!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on glance.* to glance@&#x27;%&#x27; identified by &#x27;Glance!2024&#x27;;</span><br><span class="line">&gt; flush privileges;</span><br><span class="line">&gt; exit</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-018.png" alt="操作截图 18" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># openstack user create --domain default --project service --password &#x27;Glance!2024&#x27; glance</span><br><span class="line"># openstack role add --project service --user glance admin</span><br><span class="line"># openstack service create --name glance --description &quot;OpenStack Image&quot; image</span><br><span class="line"># glance=192.168.56.103</span><br><span class="line"># openstack endpoint create --region RegionOne image public http://$glance:9292</span><br><span class="line"># openstack endpoint create --region RegionOne image internal http://$glance:9292</span><br><span class="line"># openstack endpoint create --region RegionOne image admin http://$glance:9292</span><br><span class="line"># openstack endpoint list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-019.png" alt="操作截图 19" /></p><p>安装glance</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"># apt install glance -y</span><br><span class="line"># mv /etc/glance/glance-api.conf /etc/glance/glance-api.conf.org</span><br><span class="line"># vi /etc/glance/glance-api.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">bind_host = 0.0.0.0</span><br><span class="line">show_image_direct_url = True</span><br><span class="line">[database]</span><br><span class="line">connection = mysql+pymysql://glance:Glance!2024@192.168.56.103/glance</span><br><span class="line">[glance_store]</span><br><span class="line">stores = file,http</span><br><span class="line">default_store = file</span><br><span class="line">filesystem_store_datadir = /var/lib/glance/images/</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">www_authenticate_uri = http://192.168.56.103:5000</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">memcached_servers = 192.168.56.103:11211</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = glance</span><br><span class="line">password = Glance!2024</span><br><span class="line">[paste_deploy]</span><br><span class="line">flavor = keystone</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-020.png" alt="操作截图 20" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># chmod 640 /etc/glance/glance-api.conf</span><br><span class="line"># chown root:glance /etc/glance/glance-api.conf</span><br></pre></td></tr></table></figure><p>添加数据</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># su -s /bin/sh -c &quot;glance-manage db_sync&quot; glance</span><br><span class="line">Start the service</span><br><span class="line"># systemctl restart glance-api</span><br><span class="line"># systemctl enable glance-api</span><br></pre></td></tr></table></figure><p>验证glance服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># wget http://bigdata.cg.lzu.edu.cn/cirros-0.5.2-x86_64-disk.img</span><br><span class="line"># qemu-img info  cirros-0.5.2-x86_64-disk.img</span><br><span class="line"># qemu-img convert -f qcow2 -O qcow2 cirros-0.5.2-x86_64-disk.img  cirros-0.5.2-x86_64-disk.qcow2</span><br><span class="line"># qemu-img convert -f qcow2 -O raw cirros-0.5.2-x86_64-disk.img  cirros-0.5.2-x86_64-disk.raw</span><br><span class="line"># openstack image create  cirros-0.5.2  --file /root/cirros-0.5.2-x86_64-disk.qcow2  --disk-format qcow2 --container-format bare  --public</span><br><span class="line"># openstack image list</span><br></pre></td></tr></table></figure><p>#openstack image show cirros-0.5.2</p><p><img src="/images/posts/openstack-lab-notes/image-021.png" alt="操作截图 21" /></p><p>验证完毕。</p><h3 id="43-验证placement服务"><a class="markdownIt-Anchor" href="#43-验证placement服务"></a> 4.3 验证placement服务</h3><p>创建glancement database</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"># mysql -u root -p</span><br><span class="line">&gt;  create database placement;</span><br><span class="line">&gt;  grant all privileges on placement.* to placement@localhost identified by &#x27;Placement!2024&#x27;;</span><br><span class="line">&gt;  grant all privileges on placement.* to placement@&#x27;%&#x27; identified by &#x27;Placement!2024&#x27;;</span><br><span class="line">&gt;  flush privileges;</span><br><span class="line">&gt;  exit</span><br><span class="line">create placement user in service project</span><br><span class="line"># openstack user create --domain default --project service --password &#x27;Placement!2024&#x27; placement</span><br><span class="line"># openstack role add --project service --user placement admin</span><br><span class="line"># openstack service create --name placement --description &quot;Placement API&quot; placement</span><br><span class="line"># controller=192.168.56.103</span><br><span class="line"># openstack endpoint create --region RegionOne placement public http://$controller:8778</span><br><span class="line"># openstack endpoint create --region RegionOne placement internal http://$controller:8778</span><br><span class="line"># openstack endpoint create --region RegionOne placement admin http://$controller:8778</span><br><span class="line"># openstack endpoint list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-022.png" alt="操作截图 22" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"># apt  install -y placement-api</span><br><span class="line"># mv /etc/placement/placement.conf /etc/placement/placement.conf.org</span><br><span class="line"># vi /etc/placement/placement.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">debug = false</span><br><span class="line">[placement_database]</span><br><span class="line">connection = mysql+pymysql://placement:Placement!2024@192.168.56.103/placement</span><br><span class="line">[api]</span><br><span class="line">auth_strategy = keystone</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">www_authenticate_uri = http://192.168.56.103:5000</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">memcached_servers = 192.168.56.103:11211</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = placement</span><br><span class="line">password = Placement!2024</span><br><span class="line"># chmod 640 /etc/placement/placement.conf</span><br><span class="line"># chown root:placement /etc/placement/placement.conf</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-023.png" alt="操作截图 23" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># su -s /bin/sh -c &quot;placement-manage db sync&quot; placement</span><br><span class="line"># systemctl restart apache2</span><br><span class="line"># cat /etc/apache2/sites-enabled/placement-api.conf</span><br></pre></td></tr></table></figure><p>进行验证</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># placement-status upgrade check</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-024.png" alt="操作截图 24" /></p><p>验证完毕。</p><h3 id="44-验证nova服务"><a class="markdownIt-Anchor" href="#44-验证nova服务"></a> 4.4 验证nova服务</h3><p>创建nova database</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"># mysql -u root -p</span><br><span class="line">&gt; create database nova;</span><br><span class="line">&gt; create database nova_api;</span><br><span class="line">&gt; create database nova_cell0;</span><br><span class="line">&gt; grant all privileges on nova.* to nova@localhost identified by &#x27;Nova!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on nova.* to nova@&#x27;%&#x27; identified by &#x27;Nova!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on nova_api.* to nova@localhost identified by &#x27;Nova!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on nova_api.* to nova@&#x27;%&#x27; identified by &#x27;Nova!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on nova_cell0.* to nova@localhost  identified by &#x27;Nova!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on nova_cell0.* to nova@&#x27;%&#x27; identified by &#x27;Nova!2024&#x27;;</span><br><span class="line">&gt; flush privileges;</span><br><span class="line">&gt; exit</span><br><span class="line"># openstack user create --domain default --project service --password &#x27;Nova!2024&#x27; nova</span><br><span class="line"># openstack role add --project service --user nova admin</span><br><span class="line"># openstack service create --name nova --description &quot;OpenStack Compute&quot; compute</span><br><span class="line"># controller=192.168.56.103</span><br><span class="line"># openstack endpoint create --region RegionOne compute public http://$controller:8774/v2.1/%\(tenant_id\)s</span><br><span class="line"># openstack endpoint create --region RegionOne compute internal http://$controller:8774/v2.1/%\(tenant_id\)s</span><br><span class="line"># openstack endpoint create --region RegionOne compute admin http://$controller:8774/v2.1/%\(tenant_id\)s</span><br><span class="line"># openstack endpoint list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-025.png" alt="操作截图 25" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install nova-api nova-conductor nova-scheduler nova-novncproxy python3-novaclient</span><br><span class="line">create a new config file</span><br><span class="line"># mv /etc/nova/nova.conf  /etc/nova/nova.conf.org</span><br><span class="line"># vi /etc/nova/nova.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">my_ip = 192.168.56.103</span><br><span class="line">state_path = /var/lib/nova</span><br><span class="line">enabled_apis = osapi_compute,metadata</span><br><span class="line">log_dir = /var/log/nova</span><br><span class="line">transport_url = rabbit://openstack:Openstack!2024@192.168.56.103</span><br><span class="line">[api]</span><br><span class="line">auth_strategy = keystone</span><br><span class="line">[api_database]</span><br><span class="line">connection = mysql+pymysql://nova:Nova!2024@192.168.56.103/nova_api</span><br><span class="line">[database]</span><br><span class="line">connection = mysql+pymysql://nova:Nova!2024@192.168.56.103/nova</span><br><span class="line">[glance]</span><br><span class="line">api_servers = http://192.168.56.103:9292</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">www_authenticate_uri = http://192.168.56.103:5000</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">memcached_servers = 192.168.56.103:11211</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = nova</span><br><span class="line">password = Nova!2024</span><br><span class="line">[oslo_concurrency]</span><br><span class="line">lock_path = $state_path/tmp</span><br><span class="line">[placement]</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">os_region_name = RegionOne</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = placement</span><br><span class="line">password = Placement!2024</span><br><span class="line">[scheduler]</span><br><span class="line">discover_hosts_in_cells_interval = 300</span><br><span class="line">[wsgi]</span><br><span class="line">api_paste_config = /etc/nova/api-paste.ini</span><br><span class="line"># chmod 640 /etc/nova/nova.conf</span><br><span class="line"># chown root.nova /etc/nova/nova.conf</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-026.png" alt="操作截图 26" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># su -s /bin/sh -c &quot;nova-manage api_db sync&quot; nova</span><br><span class="line"># su -s /bin/sh -c &quot;nova-manage cell_v2 map_cell0&quot; nova</span><br><span class="line"># su -s /bin/sh -c &quot;nova-manage cell_v2 create_cell --name=cell1 --verbose&quot; nova</span><br><span class="line"># su -s /bin/sh -c &quot;nova-manage db sync&quot; nova</span><br><span class="line"># su -s /bin/sh -c &quot;nova-manage cell_v2 list_cells&quot; nova</span><br><span class="line"># systemctl restart nova-api nova-conductor nova-scheduler nova-novncproxy</span><br><span class="line"># systemctl enable nova-api nova-conductor nova-scheduler nova-novncproxy</span><br></pre></td></tr></table></figure><p>进行验证</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># openstack compute service list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-027.png" alt="操作截图 27" /></p><p>安装KVM HyperVisor</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install qemu-kvm libvirt-daemon-system libvirt-daemon virtinst bridge-utils libosinfo-bin libguestfs-tools virt-top</span><br><span class="line"># systemctl start libvirtd</span><br><span class="line"># systemctl enable libvirtd</span><br><span class="line"># virsh version</span><br><span class="line"># apt -y install nova-compute nova-compute-kvm</span><br></pre></td></tr></table></figure><p>配置nova.conf文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/nova/nova.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">my_ip = 192.168.56.103</span><br><span class="line">state_path = /var/lib/nova</span><br><span class="line">enabled_apis = osapi_compute,metadata</span><br><span class="line">log_dir = /var/log/nova</span><br><span class="line">transport_url = rabbit://openstack:Openstack!2024@192.168.56.103</span><br><span class="line">[api]</span><br><span class="line">auth_strategy = keystone</span><br><span class="line">[glance]</span><br><span class="line">api_servers = http://192.168.56.103:9292</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">www_authenticate_uri = http://192.168.56.103:5000</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">memcached_servers = 192.168.56.103:11211</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = nova</span><br><span class="line">password = Nova!2024</span><br><span class="line">[oslo_concurrency]</span><br><span class="line">lock_path = $state_path/tmp</span><br><span class="line">[placement]</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">os_region_name = RegionOne</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = placement</span><br><span class="line">password = Placement!2024</span><br><span class="line">[scheduler]</span><br><span class="line">discover_hosts_in_cells_interval = 300</span><br><span class="line">[wsgi]</span><br><span class="line">api_paste_config = /etc/nova/api-paste.ini</span><br><span class="line">[vnc]</span><br><span class="line">enabled = True</span><br><span class="line">server_listen = 0.0.0.0</span><br><span class="line">server_proxyclient_address = $my_ip</span><br><span class="line">novncproxy_base_url = http://192.168.56.103:6080/vnc_auto.html</span><br><span class="line"># systemctl restart nova-compute</span><br><span class="line"># systemctl enable nova-compute</span><br><span class="line"># su -s /bin/sh -c &quot;nova-manage cell_v2 discover_hosts --verbose&quot; nova</span><br><span class="line"># openstack compute service list</span><br><span class="line"># openstack hypervisor list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-028.png" alt="操作截图 28" /></p><p><img src="/images/posts/openstack-lab-notes/image-029.png" alt="操作截图 29" /></p><h3 id="45-验证neutron服务"><a class="markdownIt-Anchor" href="#45-验证neutron服务"></a> 4.5 验证neutron服务</h3><p>创建neutron database</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># mysql -u root –p</span><br><span class="line">&gt; create database neutron;</span><br><span class="line">&gt; grant all privileges on neutron.* to neutron@localhost identified by &#x27;Neutron!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on neutron.* to neutron@&#x27;%&#x27; identified by &#x27;Neutron!2024&#x27;;</span><br><span class="line">&gt; flush privileges;</span><br><span class="line">&gt; exit</span><br><span class="line"># openstack user create --domain default --project service --password &#x27;Neutron!2024&#x27; neutron</span><br><span class="line"># openstack role add --project service --user neutron admin</span><br><span class="line"># openstack service create --name neutron --description &quot;OpenStack Networking service&quot; network</span><br><span class="line"># export network=192.168.56.103</span><br><span class="line"># openstack endpoint create --region RegionOne network public http://$network:9696</span><br><span class="line"># openstack endpoint create --region RegionOne network internal http://$network:9696</span><br><span class="line"># openstack endpoint create --region RegionOne network admin http://$network:9696</span><br><span class="line"># openstack endpoint list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-030.png" alt="操作截图 30" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install neutron-server  neutron-metadata-agent neutron-plugin-ml2  neutron-dhcp-agent neutron-l3-agent  python3-neutronclient  neutron-openvswitch-agent</span><br><span class="line"># apt -y install neutron-common neutron-plugin-ml2 neutron-openvswitch-agent</span><br><span class="line"># ip addr</span><br><span class="line"># vi /etc/netplan/00-installer-config.yaml network:</span><br><span class="line">network:</span><br><span class="line">ethernets:</span><br><span class="line">enp0s3:</span><br><span class="line">dhcp4: true</span><br><span class="line">enp0s8:</span><br><span class="line">dhcp4: true</span><br><span class="line">enp0s9:</span><br><span class="line">dhcp4: false</span><br><span class="line">version: 2</span><br><span class="line"># netplan apply</span><br><span class="line"># ovs-vsctl show</span><br><span class="line"># ovs-vsctl add-br br-eth</span><br><span class="line"># ovs-vsctl add-port br-eth  enp0s9</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-031.png" alt="操作截图 31" /></p><p>Controller Node config file:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># cd /etc/neutron/</span><br><span class="line"># mv neutron.conf neutron.conf.org</span><br><span class="line"># mv metadata_agent.ini metadata_agent.ini.org</span><br><span class="line"># mv l3_agent.ini l3_agent.ini.org</span><br><span class="line"># mv dhcp_agent.ini dhcp_agent.ini.org</span><br><span class="line"># cd /etc/neutron/plugins/ml2/</span><br><span class="line"># mv ml2_conf.ini ml2_conf.ini.org</span><br><span class="line"># mv openvswitch_agent.ini openvswitch_agent.ini.org</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-032.png" alt="操作截图 32" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/neutron/neutron.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">core_plugin = ml2</span><br><span class="line">service_plugins = router</span><br><span class="line">auth_strategy = keystone</span><br><span class="line">state_path = /var/lib/neutron</span><br><span class="line">dhcp_agent_notification = True</span><br><span class="line">allow_overlapping_ips = True</span><br><span class="line">notify_nova_on_port_status_changes = True</span><br><span class="line">notify_nova_on_port_data_changes = True</span><br><span class="line">transport_url = rabbit://openstack:Openstack!2024@192.168.56.103</span><br><span class="line">[database]</span><br><span class="line">connection = mysql+pymysql://neutron:Neutron!2024@192.168.56.103/neutron</span><br><span class="line">[agent]</span><br><span class="line">root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">www_authenticate_uri = http://192.168.56.103:5000</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">memcached_servers = 192.168.56.103:11211</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = neutron</span><br><span class="line">password = Neutron!2024</span><br><span class="line">[nova]</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">region_name = RegionOne</span><br><span class="line">project_name = service</span><br><span class="line">username = nova</span><br><span class="line">password = Nova!2024</span><br><span class="line">[oslo_concurrency]</span><br><span class="line">lock_path = $state_path/tmp</span><br><span class="line"># vi /etc/neutron/l3_agent.ini</span><br><span class="line">[DEFAULT]</span><br><span class="line">interface_driver = openvswitch</span><br><span class="line"># vi /etc/neutron/dhcp_agent.ini</span><br><span class="line">[DEFAULT]</span><br><span class="line">interface_driver = openvswitch</span><br><span class="line">dhcp_driver = neutron.agent.linux.dhcp.Dnsmasq</span><br><span class="line">enable_isolated_metadata = true</span><br><span class="line"># vi /etc/neutron/metadata_agent.ini</span><br><span class="line">[DEFAULT]</span><br><span class="line">nova_metadata_host = 192.168.56.103</span><br><span class="line">metadata_proxy_shared_secret = metadata_secret</span><br><span class="line">[cache]</span><br><span class="line">memcache_servers = 192.168.56.103:11211</span><br><span class="line"># vi /etc/neutron/plugins/ml2/ml2_conf.ini</span><br><span class="line">[DEFAULT]</span><br><span class="line">[ml2]</span><br><span class="line">type_drivers = flat,vlan,gre,vxlan</span><br><span class="line">tenant_network_types =</span><br><span class="line">mechanism_drivers = openvswitch</span><br><span class="line">extension_drivers = port_security</span><br><span class="line">[ml2_type_flat]</span><br><span class="line">flat_networks = physnet1</span><br><span class="line">[ml2_type_vlan]</span><br><span class="line">network_vlan_ranges = physnet1:1:1000</span><br><span class="line"># vi  /etc/neutron/plugins/ml2/openvswitch_agent.ini</span><br><span class="line">[DEFAULT]</span><br><span class="line">[ovs]</span><br><span class="line">bridge_mappings = physnet1:br-eth</span><br><span class="line">[securitygroup]</span><br><span class="line">firewall_driver = openvswitch</span><br><span class="line">enable_security_group = true</span><br><span class="line">enable_ipset = true</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-033.png" alt="操作截图 33" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">chmod 640 /etc/neutron/dhcp_agent.ini</span><br><span class="line">chmod 640 /etc/neutron/l3_agent.ini</span><br><span class="line">chmod 640 /etc/neutron/metadata_agent.ini</span><br><span class="line">chmod 640 /etc/neutron/neutron.conf</span><br><span class="line">chmod 640 /etc/neutron/plugins/ml2/ml2_conf.ini</span><br><span class="line">chmod 640 /etc/neutron/plugins/ml2/openvswitch_agent.ini</span><br><span class="line">chgrp neutron /etc/neutron/dhcp_agent.ini</span><br><span class="line">chgrp neutron /etc/neutron/l3_agent.ini</span><br><span class="line">chgrp neutron /etc/neutron/metadata_agent.ini</span><br><span class="line">chgrp neutron /etc/neutron/neutron.conf</span><br><span class="line">chgrp neutron /etc/neutron/plugins/ml2/ml2_conf.ini</span><br><span class="line">chgrp neutron /etc/neutron/plugins/ml2/openvswitch_agent.ini</span><br><span class="line">ln -s /etc/neutron/plugins/ml2/ml2_conf.ini /etc/neutron/plugin.ini</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-034.png" alt="操作截图 34" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/nova/nova.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">use_neutron = True</span><br><span class="line">linuxnet_interface_driver = nova.network.linux_net.LinuxOVSInterfaceDriver</span><br><span class="line">firewall_driver = nova.virt.firewall.NoopFirewallDriver</span><br><span class="line">vif_plugging_is_fatal = True</span><br><span class="line">vif_plugging_timeout = 300</span><br><span class="line">[neutron]</span><br><span class="line">auth_url = http://192.168.100.100:5000</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">region_name = RegionOne</span><br><span class="line">project_name = service</span><br><span class="line">username = neutron</span><br><span class="line">password = Neutron!2024</span><br><span class="line">service_metadata_proxy = True</span><br><span class="line">metadata_proxy_shared_secret = metadata_secret</span><br><span class="line"># su -s /bin/bash neutron -c &quot;neutron-db-manage --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugin.ini upgrade head&quot;</span><br><span class="line"># systemctl restart neutron-server neutron-metadata-agent neutron-dhcp-agent neutron-openvswitch-agent</span><br><span class="line"># systemctl enable neutron-server neutron-metadata-agent neutron-dhcp-agent neutron-openvswitch-agent</span><br><span class="line"># systemctl restart nova-api</span><br><span class="line"># systemctl disable neutron-l3-agent &amp;&amp; systemctl stop neutron-l3agent</span><br><span class="line"># openstack  network agent list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-035.png" alt="操作截图 35" /></p><p>因为本实验所有内容在一台虚拟机上完成，前面配置的是控制节点的neutron服务，计算节点的neutron服务配置文件内容比控制节点的配置更简化，这里不再重新配置。因为该虚拟机既是控制节点也是计算节点，从上图中可以看到计算节点的服务也成功启动了。下面配置一下子网。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># openstack network create --project service --share --external --provider-network-type flat --provider-physical-network physnet1 network1</span><br><span class="line"># openstack network list</span><br><span class="line"># openstack network show network1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-036.png" alt="操作截图 36" /></p><p>创建子网，Provider网络由外部网络决定。创建了名为subnent1的子网，网络地址段为192.168.57.0/24。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># openstack subnet create subnet1 --network network1 --subnet-range 192.168.57.0/24 --allocation-pool start=192.168.57.2,end=192.168.57.254 --gateway 192.168.57.1 --dns-nameserver  202.201.0.133</span><br><span class="line"># openstack subnet list</span><br><span class="line"># openstack subnet show subnet1</span><br><span class="line"># openstack port list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-037.png" alt="操作截图 37" /></p><p><img src="/images/posts/openstack-lab-notes/image-038.png" alt="操作截图 38" /></p><h3 id="46-创建一个实例虚拟机"><a class="markdownIt-Anchor" href="#46-创建一个实例虚拟机"></a> 4.6 创建一个实例(虚拟机)</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># openstack image list</span><br><span class="line"># openstack flavor create --id 0 --vcpus 1 --ram 1024 --disk 1 small1</span><br><span class="line"># openstack flavor list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-039.png" alt="操作截图 39" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># openstack keypair create --public-key ~/.ssh/id_rsa.pub key1</span><br><span class="line"># openstack keypair list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-040.png" alt="操作截图 40" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#  openstack security group list</span><br><span class="line">#  openstack security group create web-default</span><br><span class="line">#  openstack security group rule create --protocol tcp --dst-port 80:80 --ingress web-default</span><br><span class="line">#  openstack security group rule create --protocol tcp --dst-port 443:443 --ingress web-default</span><br><span class="line">#  openstack security group rule create --protocol tcp --dst-port 22:22 --ingress web-default</span><br><span class="line">#  openstack security group rule create --protocol icmp --icmp-type 8 --icmp-code 0  --ingress web-default</span><br><span class="line"># openstack server create --flavor small1 --image  cirros-0.5.2 \</span><br><span class="line">--security-group web-default --nic net-id=network1 --key-name key1 cirrostest1</span><br><span class="line"># openstack server list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-041.png" alt="操作截图 41" /></p><p>创建成功。</p><h3 id="47-安装dashboard"><a class="markdownIt-Anchor" href="#47-安装dashboard"></a> 4.7 安装dashboard</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install openstack-dashboard</span><br><span class="line"># mv /etc/openstack-dashboard/local_settings.py /etc/openstack-dashboard/local_settings.py.org</span><br><span class="line"># grep -Ev &#x27;^$|#&#x27; /etc/openstack-dashboard/local_settings.py.org &gt; /etc/openstack-dashboard/local_settings.py</span><br><span class="line"># chown root:horizon /etc/openstack-dashboard/local_settings.py</span><br><span class="line"># chmod 640 /etc/openstack-dashboard/local_settings.py</span><br><span class="line"># vi /etc/openstack-dashboard/local_settings.py</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-042.png" alt="操作截图 42" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># systemctl restart apache2</span><br></pre></td></tr></table></figure><p>然后我们访问<a href="http://192.168.56.103/horizon">http://192.168.56.103/horizon</a></p><p>账户为admin，密码为Keystone!2024</p><p><img src="/images/posts/openstack-lab-notes/image-043.png" alt="操作截图 43" /></p><p><img src="/images/posts/openstack-lab-notes/image-044.png" alt="操作截图 44" /></p><p><img src="/images/posts/openstack-lab-notes/image-045.png" alt="操作截图 45" /></p><h3 id="48-实现cinder"><a class="markdownIt-Anchor" href="#48-实现cinder"></a> 4.8 实现cinder</h3><p>创建cinder database:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># mysql -u root -p</span><br><span class="line">&gt; create database cinder;</span><br><span class="line">&gt; grant all privileges on cinder.* to cinder@localhost identified by &#x27;Cinder!2024&#x27;;</span><br><span class="line">&gt; grant all privileges on cinder.* to cinder@&#x27;%&#x27; identified by &#x27;Cinder!2024&#x27;;</span><br><span class="line">&gt; flush privileges;</span><br><span class="line">&gt; exit</span><br><span class="line"># openstack user create --domain default --project service --password &#x27;Cinder!2024&#x27; cinder</span><br><span class="line"># openstack role add --project service --user cinder admin</span><br><span class="line"># openstack service create --name cinderv3 --description &quot;OpenStack Block Storage&quot; volumev3</span><br><span class="line"># export controller=192.168.100.100</span><br><span class="line"># openstack endpoint create --region RegionOne volumev3 public http://$controller:8776/v3/%\(tenant_id\)s</span><br><span class="line"># openstack endpoint create --region RegionOne volumev3 internal http://$controller:8776/v3/%\(tenant_id\)s</span><br><span class="line"># openstack endpoint create --region RegionOne volumev3 admin http://$controller:8776/v3/%\(tenant_id\)s</span><br><span class="line"># openstack endpoint list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-046.png" alt="操作截图 46" /></p><p>安装cinder-api cinder-scheduler at controller node</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install cinder-api cinder-scheduler python3-cinderclient</span><br><span class="line"># mv /etc/cinder/cinder.conf /etc/cinder/cinder.conf.org</span><br><span class="line"># vi /etc/cinder/cinder.conf</span><br><span class="line"># chmod 640 /etc/cinder/cinder.conf</span><br><span class="line"># chgrp cinder /etc/cinder/cinder.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">my_ip = 192.168.56.103</span><br><span class="line">rootwrap_config = /etc/cinder/rootwrap.conf</span><br><span class="line">api_paste_confg = /etc/cinder/api-paste.ini</span><br><span class="line">state_path = /var/lib/cinder</span><br><span class="line">auth_strategy = keystone</span><br><span class="line">transport_url = rabbit://openstack:Openstack!2024@192.168.56.103</span><br><span class="line">enable_v3_api = True</span><br><span class="line">[database]</span><br><span class="line">connection = mysql+pymysql://cinder:Cinder!2024@192.168.56.103/cinder</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">www_authenticate_uri = http://192.168.56.103:5000</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">memcached_servers = 192.168.56.103:11211</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = cinder</span><br><span class="line">password = Cinder!2024</span><br><span class="line">[oslo_concurrency]</span><br><span class="line">lock_path = $state_path/tmp</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-047.png" alt="操作截图 47" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#  su -s /bin/bash cinder -c &quot;cinder-manage db sync&quot;</span><br><span class="line"># systemctl restart cinder-scheduler</span><br><span class="line"># echo &quot;export OS_VOLUME_API_VERSION=3&quot; &gt;&gt; ~/keystonerc</span><br><span class="line"># source ~/keystonerc</span><br><span class="line"># openstack volume service list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-048.png" alt="操作截图 48" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"># apt -y install cinder-volume python3-mysqldb</span><br><span class="line"># mv /etc/cinder/cinder.conf /etc/cinder/cinder.conf.org</span><br><span class="line"># vi /etc/cinder/cinder.conf</span><br><span class="line"># chown root.cinder /etc/cinder/cinder.conf</span><br><span class="line"># chmod 640 /etc/cinder/cinder.conf</span><br><span class="line"># systemctl enable cinder-volume</span><br><span class="line">[DEFAULT]</span><br><span class="line">my_ip = 192.168.56.103</span><br><span class="line">rootwrap_config = /etc/cinder/rootwrap.conf</span><br><span class="line">api_paste_confg = /etc/cinder/api-paste.ini</span><br><span class="line">state_path = /var/lib/cinder</span><br><span class="line">auth_strategy = keystone</span><br><span class="line">transport_url = rabbit://openstack:Openstack!2024@192.168.56.103</span><br><span class="line">enable_v3_api = True</span><br><span class="line">glance_api_servers = http://192.168.56.103:9292</span><br><span class="line">enabled_backends =</span><br><span class="line">[database]</span><br><span class="line">connection = mysql+pymysql://cinder:Cinder!2024@192.168.56.103/cinder</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">www_authenticate_uri = http://192.168.56.103:5000</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">memcached_servers = 192.168.56.103:11211</span><br><span class="line">auth_type = password</span><br><span class="line">project_domain_name = default</span><br><span class="line">user_domain_name = default</span><br><span class="line">project_name = service</span><br><span class="line">username = cinder</span><br><span class="line">password = Cinder!2024</span><br><span class="line">[oslo_concurrency]</span><br><span class="line">lock_path = $state_path/tmp</span><br><span class="line">[oslo_policy]</span><br><span class="line">enforce_new_defaults = true</span><br><span class="line"># openstack role list</span><br><span class="line"># openstack role create service</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-049.png" alt="操作截图 49" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># openstack role add --user cinder --project service service</span><br><span class="line"># openstack role add --user nova --project service service</span><br></pre></td></tr></table></figure><p>#vi /etc/cinder/cinder.conf</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">[service_user]</span><br><span class="line">send_service_user_token = True</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">project_domain_name = Default</span><br><span class="line">project_name = service</span><br><span class="line">user_domain_name = Default</span><br><span class="line">auth_type = password</span><br><span class="line">username = cinder</span><br><span class="line">password = Cinder!2024</span><br><span class="line"># vi /etc/nova/nova.conf</span><br><span class="line">[keystone_authtoken]</span><br><span class="line">service_token_roles = service</span><br><span class="line">service_token_roles_required = true</span><br><span class="line">[service_user]</span><br><span class="line">send_service_user_token = True</span><br><span class="line">auth_url = http://192.168.56.103:5000</span><br><span class="line">project_domain_name = Default</span><br><span class="line">project_name = service</span><br><span class="line">user_domain_name = Default</span><br><span class="line">auth_type = password</span><br><span class="line">username = cinder</span><br><span class="line">password = Cinder!2024</span><br><span class="line">[cinder]</span><br><span class="line">region_name = RegionOne</span><br><span class="line"># systemctl restart nova-compute</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-050.png" alt="操作截图 50" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># systemctl enable cinder-volume</span><br><span class="line"># apt -y install open-iscsi</span><br><span class="line"># lsblk</span><br><span class="line"># pvcreate /dev/sdb</span><br><span class="line"># vgcreate cinder_data /dev/sdb</span><br></pre></td></tr></table></figure><p>增加volume服务相关配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/cinder/cinder.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">enabled_backends = lvm</span><br><span class="line">[lvm]</span><br><span class="line">target_helper = lioadm</span><br><span class="line">target_protocol = iscsi</span><br><span class="line"># IP address of Storage Node</span><br><span class="line">target_ip_address = 192.168.56.103</span><br><span class="line"># volume group name just created</span><br><span class="line">volume_group = cinder_data</span><br><span class="line">volume_driver = cinder.volume.drivers.lvm.LVMVolumeDriver</span><br><span class="line">volumes_dir = $state_path/volumes</span><br></pre></td></tr></table></figure><p>启动服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># systemctl restart cinder-volume.service</span><br><span class="line"># openstack volume service list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-051.png" alt="操作截图 51" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># openstack volume create --size 10 disk01</span><br><span class="line"># openstack volume list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-052.png" alt="操作截图 52" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># vgdisplay</span><br><span class="line"># lvdisplay</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-053.png" alt="操作截图 53" /></p><p>计算节点配置安装过程与控制节点基本一致，这里不再展示，因为我使用的是一台主机完成全部实验。</p><h3 id="49创建实例与冷迁移"><a class="markdownIt-Anchor" href="#49创建实例与冷迁移"></a> 4.9创建实例与冷迁移</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openstack compute service list</span><br></pre></td></tr></table></figure><p>冷迁移只需做计算节点间nova用户ssh免密认证即可，因为我们这里是伪集群，因此我们需要做一些调整</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># usermod -s /bin/bash nova</span><br><span class="line"># su - nova</span><br><span class="line">$ ssh-keygen -t rsa -P &#x27;&#x27; -f ~/.ssh/id_rsa</span><br><span class="line">$ cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys</span><br><span class="line">$ chmod 600 ~/.ssh/authorized_keys</span><br><span class="line">$ vi ~/.ssh/config</span><br><span class="line">Host *</span><br><span class="line">StrictHostKeyChecking no</span><br><span class="line">$ chmod 600 ~/.ssh/config</span><br></pre></td></tr></table></figure><p>其他计算节点从完成ssh免密认证的节点远程拷贝配置，并修改文件属主和属组</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># ssh node03</span><br><span class="line"># scp -r node02:/var/lib/nova/.ssh /var/lib/nova/</span><br><span class="line"># chown -R nova.nova /var/lib/nova/.ssh/</span><br></pre></td></tr></table></figure><p>给所有节点nova.conf配置文件[DEFAULT]下添加冷迁移配置并重启服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/nova/nova.conf</span><br><span class="line">[DEFAULT]</span><br><span class="line">resume_guests_state_on_host_boot = true</span><br><span class="line">allow_resize_to_same_host=True</span><br><span class="line">enabled_filters = AvailabilityZoneFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter,ServerGroupAffinityFilter</span><br></pre></td></tr></table></figure><p>控制节点重启服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># systemctl restart nova-api nova-conductor nova-scheduler nova-novncproxy</span><br></pre></td></tr></table></figure><p>计算节点重启服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># systemctl restart nova-compute</span><br></pre></td></tr></table></figure><p><img src="/images/posts/openstack-lab-notes/image-054.png" alt="操作截图 54" /></p><p>到这里，实例创建、Cinder 卷服务和冷迁移流程都已经跑通。由于当前环境是单机伪集群，没有继续展开热迁移。</p><h2 id="五-一些记录"><a class="markdownIt-Anchor" href="#五-一些记录"></a> 五、一些记录</h2><p>这次 OpenStack 搭建主要有三点收获：</p><p>（1）环境准备和基础配置很关键，包括网络、主机名、时区、SSH 和系统参数优化。OpenStack 组件很多，前期基础环境不稳定，后面排错成本会很高。</p><p>（2）Keystone、Glance、Placement、Nova、Neutron、Cinder 之间的依赖关系需要逐层验证。每完成一个服务，都要通过 endpoint、service list 或资源创建命令确认状态。</p><p>（3）OpenStack 的复杂度不在单个命令，而在配置一致性。数据库连接、RabbitMQ 地址、Keystone 认证、服务用户、endpoint 和 systemd 服务状态需要全部对齐。</p><p>后续如果继续扩展，可以把控制节点和计算节点拆开，再补充多节点调度、热迁移和更完整的存储后端。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;OpenStack 的部署链路比较长，组件之间的依赖也多。这篇记录按实际搭建顺序展开，从虚拟机环境准备开始，依次配置基础服务、Keystone、Glance、Placement、Nova、Neutron、Dashboard、Cinder，并完成实例创建和冷迁移验证。&lt;/p&gt;</summary>
      
    
    
    
    <category term="Cloud Computing and Big Data" scheme="https://zhouzhq2021.github.io/categories/Cloud-Computing-and-Big-Data/"/>
    
    
    <category term="OpenStack" scheme="https://zhouzhq2021.github.io/tags/OpenStack/"/>
    
  </entry>
  
  <entry>
    <title>Hadoop 与 Spark 单节点大数据环境搭建</title>
    <link href="https://zhouzhq2021.github.io/2024/05/21/hadoop-spark-environment-lab-notes/"/>
    <id>https://zhouzhq2021.github.io/2024/05/21/hadoop-spark-environment-lab-notes/</id>
    <published>2024-05-20T16:00:00.000Z</published>
    <updated>2026-06-08T05:00:50.459Z</updated>
    
    <content type="html"><![CDATA[<p>这篇记录整理单节点 Hadoop 与 Spark 环境的搭建过程。整体目标是在一台云服务器上完成 JDK、Hadoop、HDFS、YARN、MapReduce、Spark、spark-submit 和 PySpark/Jupyter 的基本验证。</p><h2 id="一-前期准备"><a class="markdownIt-Anchor" href="#一-前期准备"></a> 一、前期准备</h2><p>这次使用华为云服务器完成搭建，配置为 8 核 16G 内存。整个过程中只有一个节点 <code>node01</code>，因此以单节点伪分布式方式运行 Hadoop 和 Spark，服务器的详细配置如下：</p><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-003.png" alt="操作截图 3" /></p><h3 id="1-网络配置"><a class="markdownIt-Anchor" href="#1-网络配置"></a> 1. 网络配置</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"># ls /etc/netplan/</span><br><span class="line"># vi /etc/netplan/01-netcfg.yaml</span><br><span class="line">network:</span><br><span class="line">version: 2</span><br><span class="line">ethernets:</span><br><span class="line">ens3:</span><br><span class="line">dhcp4: false</span><br><span class="line">match:</span><br><span class="line">macaddress: fa:16:3e:b1:41:1c</span><br><span class="line">set-name: ens3</span><br><span class="line">addresses: [192.168.1.101/24]</span><br><span class="line">routes:</span><br><span class="line">- to: default</span><br><span class="line">via: 192.168.1.1</span><br><span class="line">metric: 100</span><br><span class="line">nameservers:</span><br><span class="line">addresses: [202.201.0.131,202.201.0.132]</span><br><span class="line"># netplan apply</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-004.png" alt="操作截图 4" /></p><h3 id="2-设置ssh免密认证"><a class="markdownIt-Anchor" href="#2-设置ssh免密认证"></a> 2. 设置ssh免密认证</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># ssh-keygen -t rsa -P &#x27;&#x27; -f ~/.ssh/id_rsa</span><br><span class="line"># cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys</span><br><span class="line"># chmod 0600 ~/.ssh/authorized_keys</span><br><span class="line"># vi ~/.ssh/config</span><br><span class="line">Host *</span><br><span class="line">StrictHostKeyChecking no</span><br><span class="line"># chmod 600 ~/.ssh/config</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-005.png" alt="操作截图 5" /></p><h3 id="3-主机名设置和解析"><a class="markdownIt-Anchor" href="#3-主机名设置和解析"></a> 3. 主机名设置和解析</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ip addr</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-006.png" alt="操作截图 6" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/hosts</span><br><span class="line">127.0.1.1       localhost</span><br><span class="line">127.0.1.1       hadoop  hadoop</span><br><span class="line">127.0.1.1       node01</span><br><span class="line">192.168.1.242   node01</span><br><span class="line"># hostnamectl set-hostname node01</span><br></pre></td></tr></table></figure><h3 id="4-设置时区和时钟同步"><a class="markdownIt-Anchor" href="#4-设置时区和时钟同步"></a> 4. 设置时区和时钟同步</h3><p>华为云服务器本身设置了时区和时钟同步，不需要额外设置</p><p>详细设置可以参考之前的实验</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># timedatectl set-timezone Asia/Shanghai</span><br></pre></td></tr></table></figure><h3 id="5-优化系统关闭不需要的服务"><a class="markdownIt-Anchor" href="#5-优化系统关闭不需要的服务"></a> 5. 优化系统关闭不需要的服务</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/sysctl.conf</span><br><span class="line">fs.file-max = 20480000</span><br><span class="line">fs.nr_open= 10240000</span><br><span class="line"># sysctl -p</span><br><span class="line"># vi /etc/security/limits.conf</span><br><span class="line">root soft nofile 102400</span><br><span class="line">root hard nofile 104800</span><br><span class="line">* soft nofile 102400</span><br><span class="line">* hard nofile 104800</span><br><span class="line"># reboot</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-007.png" alt="操作截图 7" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># systemctl set-default multi-user.target</span><br><span class="line"># systemctl disable cloud-config.service cloud-final.service cloud-init-local.service cloud-init.service cloud-init-hotplugd.socket cloud-config.target cloud-init.target  multipathd.service multipathd.socket iscsid.socket  apparmor.service systemd-resolved.service ufw.service fwupd.service  graphical.target cron</span><br><span class="line"># rm /etc/resolv.conf</span><br><span class="line"># vi /etc/resolv.conf</span><br><span class="line">nameserver 202.201.0.132</span><br></pre></td></tr></table></figure><p>最后升级一下系统</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># apt update &amp;&amp; apt upgrade -y</span><br></pre></td></tr></table></figure><h2 id="二-环境搭建过程"><a class="markdownIt-Anchor" href="#二-环境搭建过程"></a> 二、环境搭建过程</h2><h3 id="1-在虚拟机上创建新用户"><a class="markdownIt-Anchor" href="#1-在虚拟机上创建新用户"></a> 1. 在虚拟机上创建新用户</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># adduser hadoop</span><br><span class="line"># sudo su - hadoop</span><br><span class="line">hadoop@node01:~$</span><br><span class="line">$ ssh-keygen -t rsa -P &#x27;&#x27; -f ~/.ssh/id_rsa</span><br><span class="line">$ cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys</span><br><span class="line">$ chmod 0600 ~/.ssh/authorized_keys</span><br><span class="line">$ vi ~/.ssh/config</span><br><span class="line">Host *</span><br><span class="line">StrictHostKeyChecking no</span><br><span class="line">$ chmod 600 ~/.ssh/config</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-008.png" alt="操作截图 8" /></p><h3 id="2-安装jdk8并配置java环境"><a class="markdownIt-Anchor" href="#2-安装jdk8并配置java环境"></a> 2. 安装JDK8并配置java环境</h3><p>在hadoop用户下完成jdk8的安装配置：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ wget http://bigdata.cg.lzu.edu.cn/bigdata_software/jdk-8u401-linux-x64.tar.gz</span><br><span class="line">$ tar -zxvf jdk-8u401-linux-x64.tar.gz</span><br><span class="line">$ ./jdk1.8.0_401/bin/java -version</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-009.png" alt="操作截图 9" /></p><p>在.bashrc文件中添加jdk8环境变量</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ vi .bashrc</span><br><span class="line">export JAVA_HOME=/home/hadoop/jdk1.8.0_401</span><br><span class="line">export PATH=$JAVA_HOME/bin:$PATH</span><br><span class="line">export CLASSPATH=.:$JAVA_HOME/jre/lib:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar</span><br><span class="line">$ source .bashrc</span><br><span class="line">$ java -version</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-010.png" alt="操作截图 10" /></p><p>因为我们只有一个节点node01，所以到这里java环境就配置完毕了，不需要将jdk8包和修改后的环境变量scp到其他节点。</p><h3 id="3-安装apache-hadoop-336并验证hdfs-yarn-mapreduce服务"><a class="markdownIt-Anchor" href="#3-安装apache-hadoop-336并验证hdfs-yarn-mapreduce服务"></a> 3. 安装Apache Hadoop 3.3.6并验证HDFS、YARN、MapReduce服务</h3><p>下载hadoop-3.3.6二进制包，解压</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ wget http://bigdata.cg.lzu.edu.cn/bigdata_software/hadoop-3.3.6.tar.gz</span><br><span class="line">$ tar -zxvf hadoop-3.3.6.tar.gz</span><br></pre></td></tr></table></figure><p>设置haodoop环境变量</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ vi ~/.bashrc</span><br><span class="line">export HADOOP_HOME=/home/hadoop/hadoop-3.3.6</span><br><span class="line">export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin</span><br><span class="line">export HADOOP_MAPRED_HOME=$HADOOP_HOME</span><br><span class="line">export HDFS_NAMENODE_USER=hadoop</span><br><span class="line">export HDFS_DATANODE_USER=hadoop</span><br><span class="line">export HDFS_SECONDARYNAMENODE_USER=hadoop</span><br><span class="line">export YARN_RESOURCEMANAGER_USER=hadoop</span><br><span class="line">export YARN_NODEMANAGER_USER=hadoop</span><br><span class="line">$ source ~/.bashrc</span><br><span class="line">$ hadoop version</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-011.png" alt="操作截图 11" /></p><p>修改hadoop配置文件：</p><h3 id="1-etchadoophadoop-envsh"><a class="markdownIt-Anchor" href="#1-etchadoophadoop-envsh"></a> 1. etc/hadoop/hadoop-env.sh</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ cd $HADOOP_HOME</span><br><span class="line">$ vi etc/hadoop/hadoop-env.sh</span><br><span class="line">export JAVA_HOME=/home/hadoop/jdk1.8.0_401</span><br><span class="line">export HADOOP_HOME=/home/hadoop/hadoop-3.3.6</span><br><span class="line">export HADOOP_CONF_DIR=$&#123;HADOOP_HOME&#125;/etc/hadoop</span><br></pre></td></tr></table></figure><h3 id="2-etchadoopcore-sitexml"><a class="markdownIt-Anchor" href="#2-etchadoopcore-sitexml"></a> 2. etc/hadoop/core-site.xml:</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ vi etc/hadoop/core-site.xml</span><br><span class="line">&lt;configuration&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;fs.defaultFS&lt;/name&gt;</span><br><span class="line">&lt;value&gt;hdfs://node01:9000&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;hadoop.tmp.dir&lt;/name&gt;</span><br><span class="line">&lt;value&gt;/home/hadoop/hadoop-3.3.6/tmp&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;/configuration&gt;</span><br></pre></td></tr></table></figure><p>创建tmp目录</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ mkdir /home/hadoop/hadoop-3.3.6/tmp</span><br></pre></td></tr></table></figure><h3 id="3-etchadoophdfs-sitexml"><a class="markdownIt-Anchor" href="#3-etchadoophdfs-sitexml"></a> 3. etc/hadoop/hdfs-site.xml</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">$ vi etc/hadoop/hdfs-site.xml</span><br><span class="line">&lt;configuration&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;dfs.namenode.http-address&lt;/name&gt;</span><br><span class="line">&lt;value&gt;node01:9870&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;dfs.namenode.name.dir&lt;/name&gt;</span><br><span class="line">&lt;value&gt;/home/hadoop/hadoop-3.3.6/dfs/name&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;dfs.datanode.data.dir&lt;/name&gt;</span><br><span class="line">&lt;value&gt;/home/hadoop/hadoop-3.3.6/dfs/data&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;dfs.replication&lt;/name&gt;</span><br><span class="line">&lt;value&gt;1&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;dfs.permissions.enabled&lt;/name&gt;</span><br><span class="line">&lt;value&gt;false&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;/configuration&gt;</span><br></pre></td></tr></table></figure><h3 id="4-etchadoopmapred-sitexml"><a class="markdownIt-Anchor" href="#4-etchadoopmapred-sitexml"></a> 4. etc/hadoop/mapred-site.xml</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">$ vi etc/hadoop/mapred-site.xml</span><br><span class="line">&lt;configuration&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;mapreduce.framework.name&lt;/name&gt;</span><br><span class="line">&lt;value&gt;yarn&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;mapreduce.application.classpath&lt;/name&gt;</span><br><span class="line">&lt;value&gt;$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;mapreduce.admin.user.env&lt;/name&gt;</span><br><span class="line">&lt;value&gt;HADOOP_MAPRED_HOME=/home/hadoop/hadoop-3.3.6&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;yarn.app.mapreduce.am.env&lt;/name&gt;</span><br><span class="line">&lt;value&gt;HADOOP_MAPRED_HOME=/home/hadoop/hadoop-3.3.6&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;/configuration&gt;</span><br></pre></td></tr></table></figure><h3 id="5-etchadoopyarn-sitexml"><a class="markdownIt-Anchor" href="#5-etchadoopyarn-sitexml"></a> 5. etc/hadoop/yarn-site.xml</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">$ vi etc/hadoop/yarn-site.xml</span><br><span class="line">&lt;configuration&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;yarn.nodemanager.aux-services&lt;/name&gt;</span><br><span class="line">&lt;value&gt;mapreduce_shuffle&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;yarn.nodemanager.aux-services.mapreduce_shuffle.class&lt;/name&gt;</span><br><span class="line">&lt;value&gt;org.apache.hadoop.mapred.ShuffleHandler&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;yarn.resourcemanager.hostname&lt;/name&gt;</span><br><span class="line">&lt;value&gt;node01&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;yarn.nodemanager.env-whitelist&lt;/name&gt;</span><br><span class="line">&lt;value&gt;JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;property&gt;</span><br><span class="line">&lt;name&gt;yarn.nodemanager.vmem-check-enabled&lt;/name&gt;</span><br><span class="line">&lt;value&gt;false&lt;/value&gt;</span><br><span class="line">&lt;/property&gt;</span><br><span class="line">&lt;/configuration&gt;</span><br></pre></td></tr></table></figure><h3 id="6-etchadoopworkers"><a class="markdownIt-Anchor" href="#6-etchadoopworkers"></a> 6. etc/hadoop/workers</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ vi etc/hadoop/workers</span><br><span class="line">node01</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-012.png" alt="操作截图 12" /></p><p>初始文件系统</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$  hdfs namenode -format</span><br></pre></td></tr></table></figure><p>启动分布式文件系统hdfs服务：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$  start-dfs.sh</span><br></pre></td></tr></table></figure><p>第一次启动会创建数据目录和日志目录</p><p>查看进程：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ jps</span><br></pre></td></tr></table></figure><p>启动资源管理服务yarn：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">start-yarn.sh</span><br><span class="line">$ jps</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-013.png" alt="操作截图 13" /></p><p>上述资源启动均成功</p><p>资源管理默认web页面为<a href="http://node01:8088/">http://node01:8088/</a></p><p>因为这里使用的是云服务，因此这里地址为<a href="http://121.36.29.177:8088/">http://121.36.29.177:8088/</a></p><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-014.png" alt="操作截图 14" /></p><p>node01交互界面： <a href="http://121.36.29.177:9870/">http://121.36.29.177:9870/</a></p><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-015.png" alt="操作截图 15" /></p><h3 id="4-学习使用hdfs"><a class="markdownIt-Anchor" href="#4-学习使用hdfs"></a> 4. 学习使用HDFS</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ hdfs dfs -mkdir /user</span><br><span class="line">$ hdfs dfs -mkdir /user/hadoop</span><br></pre></td></tr></table></figure><p>创建input测试目录，上传测试文件：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ hdfs dfs -mkdir input</span><br><span class="line">$ cd $HADOOP_HOME</span><br><span class="line">hadoop@node01:~/hadoop-3.3.6$ hdfs dfs -put etc/hadoop/*.xml input</span><br><span class="line">hadoop@node01:~/hadoop-3.3.6$ hdfs dfs -ls</span><br><span class="line">hadoop@node01:~/hadoop-3.3.6$ hdfs dfs -ls input</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-016.png" alt="操作截图 16" /></p><p>关于hdfs dfs命令可以参考帮助</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hdfs dfs -h</span><br></pre></td></tr></table></figure><p>下面给出hdfs的基本命令以及解释</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">hdfs dfs -ls：列出 HDFS 中的文件和目录。</span><br><span class="line">hdfs dfs -put &lt;local_path&gt; &lt;hdfs_path&gt;：将本地文件上传到 HDFS 中。</span><br><span class="line">hdfs dfs -get &lt;hdfs_path&gt; &lt;local_path&gt;：从 HDFS 中下载文件到本地文件系统。</span><br><span class="line">hdfs dfs -mkdir &lt;hdfs_path&gt;：在 HDFS 中创建一个新目录。</span><br><span class="line">hdfs dfs -copyFromLocal &lt;local_path&gt; &lt;hdfs_path&gt;：从本地文件系统复制文件到 HDFS 中。</span><br><span class="line">hdfs dfs -copyToLocal &lt;hdfs_path&gt; &lt;local_path&gt;：从 HDFS 复制文件到本地文件系统。</span><br><span class="line">hdfs dfs -mv &lt;source_path&gt; &lt;destination_path&gt;：移动文件或目录到新的位置。</span><br><span class="line">hdfs dfs -chown &lt;owner&gt; &lt;hdfs_path&gt;：更改文件或目录的所有者。</span><br><span class="line">hdfs dfs -chgrp &lt;group&gt; &lt;hdfs_path&gt;：更改文件或目录的所属组。</span><br><span class="line">hdfs dfs -chmod &lt;permissions&gt; &lt;hdfs_path&gt;：更改文件或目录的权限。</span><br><span class="line">hdfs dfs -appendToFile &lt;local_path&gt; &lt;hdfs_path&gt;：将本地文件内容追加到 HDFS 文件中。</span><br><span class="line">hdfs dfs -rm &lt;hdfs_path&gt;：删除 HDFS 中的文件。</span><br><span class="line">hdfs dfs -rmdir &lt;hdfs_path&gt;：删除 HDFS 中的空目录。</span><br><span class="line">hdfs dfs -du &lt;hdfs_path&gt;：显示指定目录的大小。</span><br><span class="line">hdfs dfs -expunge：清空 HDFS 中的垃圾文件。</span><br></pre></td></tr></table></figure><p>运行例子</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cd $HADOOP_HOME</span><br><span class="line">hadoop@node01:~/hadoop-3.3.6$ bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar grep input output &#x27;dfs[a-z.]+&#x27;</span><br></pre></td></tr></table></figure><p>查看输出：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ bin/hdfs dfs -cat output/*</span><br><span class="line">$ hdfs dfs -cat output/*</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-017.png" alt="操作截图 17" /></p><p>运行pi例子：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cd $HADOOP_HOME/share/hadoop/mapreduce</span><br><span class="line">$ hadoop jar hadoop-mapreduce-examples-3.3.6.jar pi 10 20</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-018.png" alt="操作截图 18" /></p><p>最后，可以运行以下命令停止服务：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ stop-yarn.sh</span><br><span class="line">$ stop-dfs.sh</span><br></pre></td></tr></table></figure><h3 id="5-完成wordcount-examples"><a class="markdownIt-Anchor" href="#5-完成wordcount-examples"></a> 5. 完成WordCount examples</h3><h3 id="1-create-java-sourcecode-file"><a class="markdownIt-Anchor" href="#1-create-java-sourcecode-file"></a> 1. Create java sourcecode file</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">$ vi WordCount.java</span><br><span class="line">import java.io.IOException;</span><br><span class="line">import java.util.StringTokenizer;</span><br><span class="line">import org.apache.hadoop.conf.Configuration;</span><br><span class="line">import org.apache.hadoop.fs.Path;</span><br><span class="line">import org.apache.hadoop.io.IntWritable;</span><br><span class="line">import org.apache.hadoop.io.Text;</span><br><span class="line">import org.apache.hadoop.mapreduce.Job;</span><br><span class="line">import org.apache.hadoop.mapreduce.Mapper;</span><br><span class="line">import org.apache.hadoop.mapreduce.Reducer;</span><br><span class="line">import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class="line">import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class="line">public class WordCount &#123;</span><br><span class="line">public static class TokenizerMapper</span><br><span class="line">extends Mapper&lt;Object, Text, Text, IntWritable&gt;&#123;</span><br><span class="line">private final static IntWritable one = new IntWritable(1);</span><br><span class="line">private Text word = new Text();</span><br><span class="line">public void map(Object key, Text value, Context context</span><br><span class="line">) throws IOException, InterruptedException &#123;</span><br></pre></td></tr></table></figure><p>StringTokenizer itr = new StringTokenizer(value.toString());</p><p>while (itr.hasMoreTokens()) {</p><p>word.set(itr.nextToken());</p><p>context.write(word, one);</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">public static class IntSumReducer</span><br><span class="line">extends Reducer&lt;Text,IntWritable,Text,IntWritable&gt; &#123;</span><br><span class="line">private IntWritable result = new IntWritable();</span><br><span class="line">public void reduce(Text key, Iterable&lt;IntWritable&gt; values,</span><br><span class="line">Context context</span><br><span class="line">) throws IOException, InterruptedException &#123;</span><br></pre></td></tr></table></figure><p>int sum = 0;</p><p>for (IntWritable val : values) {</p><p>sum += val.get();</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>result.set(sum);</p><p>context.write(key, result);</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">public static void main(String[] args) throws Exception &#123;</span><br><span class="line">Configuration conf = new Configuration();</span><br><span class="line">Job job = Job.getInstance(conf, &quot;word count&quot;);</span><br></pre></td></tr></table></figure><p>job.setJarByClass(WordCount.class);</p><p>job.setMapperClass(TokenizerMapper.class);</p><p>job.setCombinerClass(IntSumReducer.class);</p><p>job.setReducerClass(IntSumReducer.class);</p><p>job.setOutputKeyClass(Text.class);</p><p>job.setOutputValueClass(IntWritable.class);</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">FileInputFormat.addInputPath(job, new Path(args[0]));</span><br><span class="line">FileOutputFormat.setOutputPath(job, new Path(args[1]));</span><br><span class="line">System.exit(job.waitForCompletion(true) ? 0 : 1);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-compile-wordcountjava-and-create-a-jar"><a class="markdownIt-Anchor" href="#2-compile-wordcountjava-and-create-a-jar"></a> 2. Compile WordCount.java and create a jar:</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ export HADOOP_CLASSPATH=$JAVA_HOME/lib/tools.jar</span><br><span class="line">$ hadoop com.sun.tools.javac.Main WordCount.java</span><br><span class="line">$ jar cf wc.jar WordCount*.class</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-019.png" alt="操作截图 19" /></p><h3 id="3-create-text-file-and-upload-it-to-hdfs"><a class="markdownIt-Anchor" href="#3-create-text-file-and-upload-it-to-hdfs"></a> 3. Create text file and upload it to hdfs</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ vi file1</span><br><span class="line">Hello World  Bye World</span><br><span class="line">Hello Hadoop Bye Hadoop</span><br><span class="line">Bye Hadoop Hello Hadoop</span><br><span class="line">$ hdfs dfs -rm -r input</span><br><span class="line">$ hdfs dfs -rm -r output</span><br><span class="line">$ hdfs dfs -mkdir input</span><br><span class="line">$ hdfs dfs -put file1 input</span><br><span class="line">$ hdfs dfs -ls input</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-020.png" alt="操作截图 20" /></p><h3 id="4-run-the-application"><a class="markdownIt-Anchor" href="#4-run-the-application"></a> 4. Run the application:</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ hadoop jar wc.jar WordCount /user/hadoop/input/file1 /user/hadoop/output/</span><br><span class="line">$ hdfs dfs -ls output</span><br><span class="line">$ hdfs dfs -cat output/part-r-00000</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-021.png" alt="操作截图 21" /></p><h3 id="6-启动spark并且验证spark交互界面"><a class="markdownIt-Anchor" href="#6-启动spark并且验证spark交互界面"></a> 6. 启动spark并且验证spark交互界面</h3><p>安装scala语言支持</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt update &amp;&amp; sudo apt install scala -y</span><br></pre></td></tr></table></figure><p>下载解压spark-3.4.2</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ wget http://bigdata.cg.lzu.edu.cn/bigdata_software/spark-3.4.2-bin-hadoop3.tgz</span><br><span class="line">$ tar -zxvf spark-3.4.2-bin-hadoop3.tgz</span><br></pre></td></tr></table></figure><p>添加环境变量</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vi .bashrc</span><br></pre></td></tr></table></figure><p>#spark env config</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">export SPARK_HOME=~/spark-3.4.2-bin-hadoop3</span><br><span class="line">export PATH=$PATH:$SPARK_HOME/bin</span><br><span class="line">export LD_LIBRARY_PATH=$HADOOP_HOME/lib/native/:$LD_LIBRARY_PATH</span><br><span class="line">$ source .bashrc</span><br></pre></td></tr></table></figure><p>配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ cd $SPARK_HOME/conf</span><br><span class="line">$ cp workers.template workers</span><br></pre></td></tr></table></figure><p>添加工作节点（单节点只添加本机）</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">$ vi workers</span><br><span class="line">node01</span><br><span class="line">$ cp spark-env.sh.template spark-env.sh</span><br><span class="line">$ vi spark-env.sh</span><br><span class="line">export JAVA_HOME=/home/hadoop/jdk1.8.0_401</span><br><span class="line">export HADOOP_HOME=/home/hadoop/hadoop-3.3.6</span><br><span class="line">export HADOOP_CONF_DIR=/home/hadoop/hadoop-3.3.6/etc/hadoop/</span><br><span class="line">export SPARK_MASTER_HOST=node01</span><br><span class="line">export SPARK_PID_DIR=/home/hadoop/spark-3.4.2-bin-hadoop3/data</span><br><span class="line">export SPARK_LOCAL_DIR=/home/hadoop/spark-3.4.2-bin-hadoop3</span><br><span class="line">export SPARK_EXECUTOR_MEMORY=512M</span><br><span class="line">export SPARK_WORKER_MEMORY=2G</span><br><span class="line">export SCALA_HOME=/usr/share/scala</span><br><span class="line">$ cp spark-defaults.conf.template spark-defaults.conf</span><br><span class="line">$ vi spark-defaults.conf</span><br><span class="line">spark.master                     spark://node01:7077</span><br></pre></td></tr></table></figure><p>这里不需要拷贝到其他节点，因为我使用的是单机伪集群。</p><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-022.png" alt="操作截图 22" /></p><p>启动服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ $SPARK_HOME/sbin/start-all.sh</span><br><span class="line">$ jps</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-023.png" alt="操作截图 23" /></p><p>访问Web查看</p><p><a href="http://node01:8080/">http://node01:8080/</a></p><p>这里对应为：<a href="http://121.36.29.177:8080/">http://121.36.29.177:8080/</a></p><p>可以看到服务均成功启动。</p><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-024.png" alt="操作截图 24" /></p><p>运行一个例子</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ $SPARK_HOME/bin/run-example SparkPi 10</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-025.png" alt="操作截图 25" /></p><p>测试spark-shell</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ spark-shell</span><br><span class="line">scala&gt; val textFile=sc.textFile(&quot;file:///home/hadoop/spark-3.4.2-bin-hadoop3/README.md&quot;)</span><br><span class="line">scala&gt; textFile.count()</span><br><span class="line">scala&gt; :quit</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-026.png" alt="操作截图 26" /></p><p>测试pyspark</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ pyspark</span><br><span class="line">&gt;&gt;&gt; lines=sc.textFile(&quot;file:///home/hadoop/spark-3.4.2-bin-hadoop3/README.md&quot;)</span><br><span class="line">&gt;&gt;&gt; lines.count()</span><br><span class="line">125</span><br><span class="line">&gt;&gt;&gt; exit()</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-027.png" alt="操作截图 27" /></p><h3 id="7-完成spark-submit-exercise"><a class="markdownIt-Anchor" href="#7-完成spark-submit-exercise"></a> 7. 完成spark-submit exercise</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ wget http://bigdata.cg.lzu.edu.cn/bigdata_software/spark_examples/try1.txt</span><br><span class="line">$ vi try1.py</span><br><span class="line">from pyspark import SparkConf,SparkContext</span><br><span class="line">conf = SparkConf().setMaster(&quot;spark://node01:7077&quot;).setAppName(&quot;My try1&quot;)</span><br><span class="line">sc = SparkContext(conf=conf)</span><br><span class="line">sc.setLogLevel(&#x27;WARN&#x27;)</span><br><span class="line">txt = sc.textFile(&quot;try1.txt&quot;)</span><br><span class="line">print(txt.count())</span><br><span class="line">as_lines = txt.filter(lambda line: &#x27;as&#x27; in line.lower())</span><br><span class="line">print(as_lines.count())</span><br></pre></td></tr></table></figure><p>将文件上传到hdfs</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hdfs dfs -put try1.txt</span><br></pre></td></tr></table></figure><p>提交任务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ spark-submit  try1.py</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-028.png" alt="操作截图 28" /></p><p>使用jupyter notebook</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ sudo apt update &amp;&amp; sudo apt install pip</span><br><span class="line">$ sudo pip3 install jupyter</span><br><span class="line">$ sudo pip3 install pyspark</span><br><span class="line">$ vi ~/.bashrc</span><br><span class="line">export  PYTHONPATH=$PATH:$SPARK_HOME/python</span><br><span class="line">export PYSPARK_PYTHON=python3</span><br><span class="line">$ source ~/.bashrc</span><br><span class="line">$ jupyter notebook --ip=node01</span><br><span class="line">$ $SPARK_HOME/sbin/stop-all.sh</span><br></pre></td></tr></table></figure><p><img src="/images/posts/hadoop-spark-environment-lab-notes/image-029.png" alt="操作截图 29" /></p><p>至此，Hadoop、Spark、spark-submit、PySpark 和 Jupyter Notebook 的基本测试都已经跑通。</p><h2 id="三-一些记录"><a class="markdownIt-Anchor" href="#三-一些记录"></a> 三、一些记录</h2><p>这次搭建主要覆盖了以下几个方面：</p><p>（1）Linux 用户和运行环境管理：创建独立的 <code>hadoop</code> 用户，配置 SSH 免密、JDK 和环境变量，保证 Hadoop/Spark 不依赖 root 用户运行。</p><p>（2）Hadoop 基础组件：安装并配置 HDFS、YARN 和 MapReduce，理解 NameNode、DataNode、ResourceManager、NodeManager 等组件的基本关系。</p><p>（3）HDFS 与 MapReduce：通过 HDFS 命令完成文件上传、下载、浏览和删除，并用 WordCount 理解 MapReduce 作业的提交和输出流程。</p><p>（4）Spark 生态：启动 Spark Standalone，使用 <code>spark-shell</code>、<code>pyspark</code> 和 <code>spark-submit</code> 验证交互式计算与作业提交。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这篇记录整理单节点 Hadoop 与 Spark 环境的搭建过程。整体目标是在一台云服务器上完成 JDK、Hadoop、HDFS、YARN、MapReduce、Spark、spark-submit 和 PySpark/Jupyter 的基本验证。&lt;/p&gt;
&lt;h2 id=&quot;一</summary>
      
    
    
    
    <category term="Cloud Computing and Big Data" scheme="https://zhouzhq2021.github.io/categories/Cloud-Computing-and-Big-Data/"/>
    
    
    <category term="Hadoop" scheme="https://zhouzhq2021.github.io/tags/Hadoop/"/>
    
  </entry>
  
  <entry>
    <title>Ceph Cluster Installation：分布式存储集群搭建与使用</title>
    <link href="https://zhouzhq2021.github.io/2024/04/13/ceph-cluster-lab-notes/"/>
    <id>https://zhouzhq2021.github.io/2024/04/13/ceph-cluster-lab-notes/</id>
    <published>2024-04-12T16:00:00.000Z</published>
    <updated>2026-06-08T05:00:50.369Z</updated>
    
    <content type="html"><![CDATA[<p>Ceph 可以同时提供块存储、文件存储和对象存储，适合作为云平台和大数据平台的底层存储。这篇记录按实际操作顺序整理，从三节点集群初始化开始，逐步完成 Dashboard、MDS、RGW、RBD、iSCSI、CephFS 和 S3 访问验证。</p><h2 id="一-目标与环境准备"><a class="markdownIt-Anchor" href="#一-目标与环境准备"></a> 一、目标与环境准备</h2><p>这次主要围绕 Ceph 存储的安装、配置和使用展开：</p><p>• 安装 ceph</p><p>• 配置 Mon，OSD，MGR，Dashboard，MDS 和 radosgw</p><p>• 测试块存储</p><p>• 测试文件存储</p><p>• 测试对象存储</p><p>• 测试 iscsi multipath 特性</p><p>环境方面，在华为云上准备 3 台配置相同的弹性云服务器。每台服务器配置两块硬盘、两张网卡，并分配弹性公网 IP。</p><p>规格：通用计算型 | 8vCPUs | 16GiB | sn3.2xlarge.2</p><p>镜像：Ubuntu 22.04 server 64bit | 公共镜像</p><p>各服务器网段：node01 | 192.168.0.10/24 | 192.168.1.10/24</p><p>node02 | 192.168.0.11/24 | 192.168.1.11/24</p><p>node03 | 192.168.0.12/24 | 192.168.1.12/24</p><p>VPC拓扑图展示如下:</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-003.png" alt="操作截图 3" /></p><p>后续还额外准备了 Windows 主机和 Ubuntu 主机，用来访问图形化界面，并作为客户端测试 Ceph 的块存储、文件存储和对象存储。</p><h2 id="二-基础环境配置"><a class="markdownIt-Anchor" href="#二-基础环境配置"></a> 二、基础环境配置</h2><h3 id="21-ssh配置"><a class="markdownIt-Anchor" href="#21-ssh配置"></a> 2.1 ssh配置</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># ssh-keygen -t rsa -P &#x27;&#x27; -f ~/.ssh/id_rsa</span><br><span class="line"># cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys</span><br><span class="line"># chmod 0600 ~/.ssh/authorized_keys</span><br><span class="line"># vi ~/.ssh/config</span><br><span class="line">Host *</span><br><span class="line">StrictHostKeyChecking no</span><br><span class="line"># chmod 600 ~/.ssh/config</span><br><span class="line"># scp -r ~/.ssh/   ceph-02:/root/</span><br><span class="line"># scp -r ~/.ssh/   ceph-03:/root/</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-004.png" alt="操作截图 4" /></p><h3 id="22-主机名解析配置"><a class="markdownIt-Anchor" href="#22-主机名解析配置"></a> 2.2 主机名解析配置</h3><p>将主机名解析文件配置为图片中的内容。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/hosts</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-005.png" alt="操作截图 5" /></p><h3 id="23-系统优化"><a class="markdownIt-Anchor" href="#23-系统优化"></a> 2.3 系统优化</h3><p>对服务器做一些常规优化，使用到前面实验的内容，这里做一些总结。</p><p>(1)关闭不必要的服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># systemctl stop cloud-config.service cloud-final.service  cloud-init-local.service cloud-init.service cloud-config.target cloud-init.target</span><br></pre></td></tr></table></figure><p>(2)更新系统</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># apt update &amp;&amp; apt upgrade -y</span><br></pre></td></tr></table></figure><p>(3)优化file-max nropen和ulimit</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># vi /etc/security/limits.conf</span><br><span class="line"># vi /etc/sysctl.conf</span><br><span class="line"># ulimit -Hn</span><br><span class="line"># ulimit -Sn</span><br></pre></td></tr></table></figure><p>(4)重启</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># reboot</span><br></pre></td></tr></table></figure><h3 id="24-安装ceph包"><a class="markdownIt-Anchor" href="#24-安装ceph包"></a> 2.4 安装Ceph包</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># apt update &amp;&amp; apt install ceph -y</span><br><span class="line"># ceph -v</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-006.png" alt="操作截图 6" /></p><h2 id="三-ceph集群原理"><a class="markdownIt-Anchor" href="#三-ceph集群原理"></a> 三、Ceph集群原理</h2><p>Ceph是一个统一的分布式存储系统，设计初衷是提供较好的性能、可靠性和可扩展性。一个 Ceph 存储集群至少需要一个 Ceph Monitor（监视器）、Ceph Manager（管理） 和 Ceph OSD（对象存储守护进程）。下面介绍一下这几个部分帮助完成下面的实验：</p><p>（1）Monitors</p><p>Ceph Monitor (ceph-mon) 维护集群状态的映射，包括监视器映射、管理器映射、OSD 映射、MDS 映射和 CRUSH 映射。这些映射是Ceph守护进程相互协调所需的关键集群状态。监视器还负责管理守护进程和客户端之间的身份验证。</p><p>（2）Managers</p><p>Ceph Manage守护进程 ( ceph-mgr) 负责跟踪运行时指标和Ceph集群的当前状态，包括存储利用率、当前性能指标和系统负载。</p><p>（3）Ceph OSD</p><p>一个Ceph OSD（ceph-osd）存储数据、处理数据复制、恢复、重新平衡，并通过检查其他 Ceph OSD 守护进程的心跳来向 Ceph 监视器和管理器提供一些监视信息。</p><p>（4）MDS</p><p>Ceph 元数据服务器(MDS ceph-mds) 代表Ceph 文件系统存储元数据（即 Ceph 块设备和 Ceph 对象存储不使用 MDS）。</p><p>下面是ceph的架构图：</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-007.png" alt="操作截图 7" /></p><p>后续会用到块存储、文件存储和对象存储三类能力，先简单梳理它们在 Ceph 中对应的组件：</p><p>（1）RADOSGW（对象网关）</p><p>Ceph对象存储使用Ceph对象网关守护进程(radosgw)，它是用于与Ceph存储集群交互的HTTP服务器。由于它提供了与OpenStack Swift和Amazon S3兼容的接口，因此Ceph对象网关有自己的用户管理。</p><p>（2）BRD（块存储）</p><p>块是字节序列（通常为512）。Ceph块设备是精简配置的、可调整大小的，并将数据分条存储在多个OSD上。Ceph块设备利用RADOS功能，包括快照、复制和强一致性。</p><p>（3）CEPHFS（文件存储）</p><p>Ceph文件系统或CephFS是一个符合 POSIX 的文件系统，构建在Ceph的分布式对象存储RADOS之上。文件元数据与文件数据存储在单独的 RADOS 池中，并通过可调整大小的元数据服务器集群或MDS提供服务，该集群可以扩展以支持更高吞吐量的元数据工作负载。文件系统的客户端可以直接访问 RADOS 以读取和写入文件数据块。</p><h2 id="四-集群搭建与存储功能验证"><a class="markdownIt-Anchor" href="#四-集群搭建与存储功能验证"></a> 四、集群搭建与存储功能验证</h2><h3 id="41-配置-ceph-集群"><a class="markdownIt-Anchor" href="#41-配置-ceph-集群"></a> 4.1 配置 ceph 集群</h3><p>(1)生成一个 uuid 作为集群的唯一标识号 fsid:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@ceph-01:~# uuidgen</span><br><span class="line">7361c797-d1c2-4dc7-bbdb-f637c5ee0fab</span><br></pre></td></tr></table></figure><p>(2)在集群每个节点都安装好ceph并配置好：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">root@ceph-01:~# vi /etc/ceph/ceph.conf</span><br><span class="line">[global]</span><br><span class="line">fsid = 7361c797-d1c2-4dc7-bbdb-f637c5ee0fab</span><br><span class="line">mon initial members = node01</span><br><span class="line">mon host = 192.168.1.10</span><br><span class="line">public network = 192.168.1.0/24</span><br><span class="line">cluster network = 192.168.0.0/24</span><br><span class="line">auth cluster required = cephx</span><br><span class="line">auth service required = cephx</span><br><span class="line">auth client required = cephx</span><br><span class="line">osd journal size = 1024</span><br><span class="line">osd pool default size = 3</span><br><span class="line">osd pool default min size = 2</span><br><span class="line">osd pool default pg num = 16</span><br><span class="line">osd pool default pgp num = 16</span><br><span class="line">osd crush chooseleaf type = 1</span><br><span class="line">osd_mkfs_type = xfs</span><br><span class="line">max mds = 5</span><br><span class="line">mds max file size = 100000000000000</span><br><span class="line">mds cache size = 1000000</span><br><span class="line">mon osd down out interval = 900</span><br><span class="line">[mon]</span><br><span class="line">mon clock drift allowed = .50</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-008.png" alt="操作截图 8" /></p><p>（3）对令牌环进行配置</p><p>创建令牌</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon &#x27;allow *&#x27;</span><br></pre></td></tr></table></figure><p>创建管理令牌</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon &#x27;allow *&#x27; --cap osd &#x27;allow *&#x27; --cap mds &#x27;allow *&#x27; --cap mgr &#x27;allow *&#x27;</span><br><span class="line"># ceph-authtool --create-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring --gen-key -n client.bootstrap-osd --cap mon &#x27;profile bootstrap-osd&#x27;</span><br><span class="line"># ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring</span><br><span class="line"># ceph-authtool /tmp/ceph.mon.keyring --import-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring</span><br></pre></td></tr></table></figure><p>创建monmaptool</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># monmaptool --create --add node01 192.168.1.10 --fsid 7361c797-d1c2-4dc7-bbdb-f637c5ee0fab  /tmp/monmap</span><br><span class="line"># sudo -u ceph mkdir /var/lib/ceph/mon/ceph-node01</span><br><span class="line"># chown ceph.ceph /tmp/ceph.mon.keyring</span><br><span class="line"># sudo -u ceph ceph-mon --mkfs -i node01 --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring</span><br></pre></td></tr></table></figure><p>创建文件夹防止重新安装</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># sudo touch /var/lib/ceph/mon/ceph-node01/done</span><br></pre></td></tr></table></figure><p>启动并查看node01状态</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># systemctl start ceph-mon@node01</span><br><span class="line"># systemctl status ceph-mon@node01</span><br><span class="line"># systemctl enable ceph-mon@node01</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-009.png" alt="操作截图 9" /></p><p>出现两条警告，输入下面的命令进行处理，警告消失。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># ceph config set mon mon_warn_on_insecure_global_id_reclaim_allowed false</span><br><span class="line"># ceph mon enable -msgr2</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-010.png" alt="操作截图 10" /></p><p>最后将node01的配置传给node02和node03：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># scp /etc/ceph/* node02:/etc/ceph/</span><br><span class="line"># scp /etc/ceph/* node03:/etc/ceph/</span><br><span class="line"># scp /var/lib/ceph/bootstrap-osd/ceph.keyring  node02:/var/lib/ceph/bootstrap-osd/</span><br><span class="line"># scp /var/lib/ceph/bootstrap-osd/ceph.keyring  node03:/var/lib/ceph/bootstrap-osd/</span><br></pre></td></tr></table></figure><p>（4）为集群添加OSD</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># sudo ceph-volume lvm create --data /dev/vdb</span><br><span class="line"># systemctl start ceph-osd@0</span><br><span class="line"># systemctl status ceph-osd@0</span><br><span class="line"># systemctl enable ceph-osd@0</span><br><span class="line"># ceph -s</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-011.png" alt="操作截图 11" /></p><p>Node02和node03同理，这里不在详细写明，直接附上运行结果：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ceph -s</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-012.png" alt="操作截图 12" /></p><p>（5）发现集群健康情况中提示没有 mgr 节点，我们选择在 mon 节点上安装 mgr 节点</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># ssh node01</span><br><span class="line"># ceph auth get-or-create mgr.node01 mon &#x27;allow profile mgr&#x27; osd &#x27;allow *&#x27; mds &#x27;allow *&#x27;</span><br><span class="line"># sudo -u ceph mkdir /var/lib/ceph/mgr/ceph-node01/</span><br><span class="line"># ceph auth get mgr.node01 -o /var/lib/ceph/mgr/ceph-node01/keyring</span><br><span class="line"># systemctl start ceph-mgr@node01</span><br><span class="line"># systemctl status ceph-mgr@node01</span><br><span class="line"># systemctl enable ceph-mgr@node01</span><br><span class="line"># ceph -s</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-013.png" alt="操作截图 13" /></p><p>（6）为 ceph 集群安装 mgr-dashboard 软件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># apt install ceph-mgr-dashboard -y</span><br><span class="line"># ceph mgr module enable dashboard</span><br><span class="line"># ceph dashboard create-self-signed-cert</span><br><span class="line"># openssl req -new -nodes -x509   -subj &quot;/O=IT/CN=ceph-mgr-dashboard&quot; -days 3650   -keyout dashboard.key -out dashboard.crt -extensions v3_ca</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-014.png" alt="操作截图 14" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># ceph config-key set mgr/dashboard/crt -i dashboard.crt</span><br><span class="line"># ceph config-key set mgr/dashboard/key -i dashboard.key</span><br><span class="line"># ceph config set mgr mgr/dashboard/server_addr  192.168.1.10</span><br><span class="line"># ceph config set mgr mgr/dashboard/server_port  8080</span><br><span class="line"># ceph config set mgr mgr/dashboard/ssl_server_port 8443</span><br><span class="line"># ceph config dump</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-015.png" alt="操作截图 15" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># systemctl daemon-reload</span><br><span class="line"># systemctl restart ceph-mgr@node01</span><br><span class="line"># netstat -an | grep 8443</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-016.png" alt="操作截图 16" /></p><p>然后配置账户和密码：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># ceph dashboard ac-role-show</span><br><span class="line"># vi adminpassword</span><br><span class="line"># ceph dashboard ac-user-create admin  -i adminpassword administrator</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-017.png" alt="操作截图 17" /></p><p>配置完成之后，访问https://192.168.1.10:8443查看 dashboard，如图所示，说明配置成功。</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-018.png" alt="操作截图 18" /></p><p><img src="/images/posts/ceph-cluster-lab-notes/image-019.png" alt="操作截图 19" /></p><p>至此，集群的初始配置完成。</p><h3 id="42-添加mds和radosgw服务"><a class="markdownIt-Anchor" href="#42-添加mds和radosgw服务"></a> 4.2 添加MDS和radosgw服务</h3><p>创建rbd池然后初始化</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># ceph osd lspools</span><br><span class="line"># ceph osd pool create rbd 16 16</span><br><span class="line"># rbd pool init rbd</span><br></pre></td></tr></table></figure><p>添加MDS服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># ssh node01</span><br><span class="line"># sudo -u ceph mkdir -p /var/lib/ceph/mds/ceph-node01</span><br><span class="line"># ceph auth get-or-create mds.node01 osd &quot;allow rwx&quot; mds &quot;allow&quot; mon &quot;allow profile mds&quot;</span><br><span class="line"># ceph auth get mds.node01 -o /var/lib/ceph/mds/ceph-node01/keyring</span><br><span class="line"># vi /etc/ceph/ceph.conf</span><br><span class="line">[mds.node01]</span><br><span class="line">host = node01</span><br><span class="line"># systemctl start ceph-mds@node01</span><br><span class="line"># systemctl enable ceph-mds@node01</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-020.png" alt="操作截图 20" /></p><p>安装radosgw并初始化</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># apt install radosgw -y</span><br><span class="line"># vi /etc/ceph/ceph.conf</span><br><span class="line">[client.rgw.node01]</span><br><span class="line">host = node01</span><br><span class="line">rgw frontends = &quot;beast port=7480&quot;</span><br><span class="line">rgw dns name = node01</span><br></pre></td></tr></table></figure><p>创建目录</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># sudo -u ceph mkdir -p /var/lib/ceph/radosgw/ceph-rgw.node01</span><br></pre></td></tr></table></figure><p>配置令牌</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ceph auth get-or-create client.rgw.node01 osd &#x27;allow rwx&#x27; mon &#x27;allow rw&#x27; -o /var/lib/ceph/radosgw/ceph-rgw.node01/keyring</span><br></pre></td></tr></table></figure><p>创建池</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># ceph osd pool create .rgw.root   16 16</span><br><span class="line"># ceph osd pool create default.rgw.control   16 16</span><br><span class="line"># ceph osd pool create default.rgw.meta  16 16</span><br><span class="line"># ceph osd pool create default.rgw.log  16 16</span><br><span class="line"># ceph osd pool create default.rgw.buckets.index  16 16</span><br><span class="line"># ceph osd pool create default.rgw.buckets.data  16 16</span><br><span class="line"># ceph osd pool create default.rgw.buckets.non-ec   16 16</span><br></pre></td></tr></table></figure><p>初始化池</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># ceph osd pool application enable .rgw.root rgw</span><br><span class="line"># ceph osd pool application enable default.rgw.control rgw</span><br><span class="line"># ceph osd pool application enable default.rgw.meta  rgw</span><br><span class="line"># ceph osd pool application enable default.rgw.log rgw</span><br><span class="line"># ceph osd pool application enable default.rgw.buckets.index rgw</span><br><span class="line"># ceph osd pool application enable default.rgw.buckets.data  rgw</span><br><span class="line"># ceph osd pool application enable default.rgw.buckets.non-ec  rgw</span><br></pre></td></tr></table></figure><p>启动服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># systemctl start  ceph-radosgw@rgw.node01</span><br><span class="line"># systemctl enable  ceph-radosgw@rgw.node01</span><br><span class="line"># ceph -s</span><br></pre></td></tr></table></figure><p>创建管理用户</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># radosgw-admin user create --uid=&quot;admin&quot; --display-name=&quot;admin user&quot; --system</span><br><span class="line"># radosgw-admin user list</span><br></pre></td></tr></table></figure><p>添加RGW到dashboard</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># vi rgw_access_key</span><br><span class="line"># vi rgw_secret_key</span><br><span class="line"># ceph dashboard set-rgw-api-ssl-verify false</span><br><span class="line"># ceph dashboard set-rgw-api-access-key -i rgw_access_key</span><br><span class="line"># ceph dashboard set-rgw-api-secret-key -i rgw_secret_key</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-021.png" alt="操作截图 21" /></p><p>安装 ceph-iscsi并配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># apt install ceph-iscsi</span><br><span class="line"># vi /etc/ceph/iscsi-gateway.cfg</span><br><span class="line">[config]</span><br><span class="line">cluster_name = ceph</span><br><span class="line">gateway_keyring = ceph.client.admin.keyring</span><br><span class="line">api_secure = false</span><br><span class="line">api_user = admin</span><br><span class="line">api_password = admin</span><br><span class="line">api_port = 5001</span><br><span class="line">trusted_ip_list = 192.168.1.10</span><br></pre></td></tr></table></figure><p>启动服务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># systemctl enable  tcmu-runner</span><br><span class="line"># systemctl enable  rbd-target-api</span><br><span class="line"># systemctl enable  rbd-target-gw</span><br><span class="line"># systemctl daemon-reload</span><br><span class="line"># systemctl restart tcmu-runner</span><br><span class="line"># systemctl restart rbd-target-api</span><br><span class="line"># systemctl restart rbd-target-gw</span><br><span class="line"># vi iscsigw-node01</span><br><span class="line">http://admin:admin@192.168.1.10:5001</span><br><span class="line"># ceph dashboard set-iscsi-api-ssl-verification false</span><br><span class="line"># ceph dashboard iscsi-gateway-add -i iscsigw-node01</span><br><span class="line"># ceph dashboard iscsi-gateway-list</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-022.png" alt="操作截图 22" /></p><h3 id="43-块存储的使用包括选做部分"><a class="markdownIt-Anchor" href="#43-块存储的使用包括选做部分"></a> 4.3 块存储的使用（包括选做部分）</h3><p>前面我们已经成功安装并配置了ceph-iscsi，下面我们先看一下ceph状态以及存储池情况：</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-023.png" alt="操作截图 23" /></p><p><img src="/images/posts/ceph-cluster-lab-notes/image-024.png" alt="操作截图 24" /></p><h4 id="431-创建镜像"><a class="markdownIt-Anchor" href="#431-创建镜像"></a> 4.3.1 创建镜像</h4><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">rbd 存储池并不能直接用于块设备，而是需要事先在其中按需创建映像（image），并把映像文件作为块设备使用。</span><br><span class="line"># ceph osd pool create images 16 16</span><br><span class="line"># rbd pool init images</span><br><span class="line"># rbd create --size 10G images/image1</span><br><span class="line"># rbd ls images</span><br><span class="line"># rbd info images/image1</span><br><span class="line"># rbd map images/image1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-025.png" alt="操作截图 25" /></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># rbd showmapped</span><br><span class="line"># fdisk /dev/rbd0</span><br><span class="line"># fdisk -l /dev/rbd0</span><br><span class="line"># mkfs.xfs /dev/rbd0p1</span><br><span class="line"># mount /dev/rbd0p1 /mnt</span><br><span class="line"># df -kh</span><br></pre></td></tr></table></figure><p>此时对块设备/dev/rbd0就可以进行分区格式化文件系统挂载使用了。</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-026.png" alt="操作截图 26" /></p><p>断开映射</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># rbd device unmap /dev/rbd0</span><br></pre></td></tr></table></figure><p>删除镜像</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># rbd remove rbd/image1</span><br></pre></td></tr></table></figure><p>这种方法删除镜像后，镜像将不可恢复。推荐使用trash命令，这个命令删除是将镜像移动一回收站，用法为：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rbd trash move &lt;pool name&gt;/&lt;image name&gt;</span><br><span class="line">rbd trash list --pool &lt;pool name&gt;</span><br><span class="line">rbd trash restore &lt;pool name&gt;/&lt;id&gt;</span><br></pre></td></tr></table></figure><p>此外我们还可以调整镜像大小：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rbd resize &lt;pool name&gt;/&lt;image name&gt; --size &lt;size number&gt;</span><br></pre></td></tr></table></figure><h4 id="432-镜像快照"><a class="markdownIt-Anchor" href="#432-镜像快照"></a> 4.3.2 镜像快照</h4><p>Ceph全面支持快照，快照是在某个时间点上生成的只读RBD镜像副本。可以通过创建和恢复快照来保持镜像的状态以及从快照恢复原始数据。</p><p>创建一个镜像快照</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># rbd snap create images/image1@20240412</span><br><span class="line"># rbd snap ls images/image1</span><br></pre></td></tr></table></figure><p>保护和克隆快照</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># rbd snap protect images/image1@20240412</span><br><span class="line"># rbd clone images/image1@20240412 images/image2</span><br><span class="line"># rbd info images/image2</span><br></pre></td></tr></table></figure><p>查看快照的子快照</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># rbd children images/image1</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-027.png" alt="操作截图 27" /></p><p>扁平化克隆，然后删除它</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># rbd flatten images/image2</span><br><span class="line"># rbd snap unprotect images/image1@20240412</span><br><span class="line"># rbd snap purge images/image1</span><br><span class="line"># rbd  rm images/image1</span><br><span class="line"># rbd  rm images/image2</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-028.png" alt="操作截图 28" /></p><p><img src="/images/posts/ceph-cluster-lab-notes/image-029.png" alt="操作截图 29" /></p><p>以上就是镜像与快照的实际操作与应用。</p><h4 id="433-在客户端配置ceph"><a class="markdownIt-Anchor" href="#433-在客户端配置ceph"></a> 4.3.3 在客户端配置ceph</h4><p>客户端使用ceph块存储需要在集群创建client账户并授权。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ceph auth get-or-create client.rbd mon &#x27;allow r&#x27; osd &#x27;allow rwx pool=rbd&#x27;</span><br></pre></td></tr></table></figure><p>从集群导出为令牌环文件，将该文发送给客户端，客户端（这里新创建一个test客户端，内网ip为192.168.1.158）也安装好ceph。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># ceph auth get client.rbd -o ceph.client.rbd.keyring</span><br><span class="line"># scp ./ceph.client.rbd.keyring root@192.168.1.158:/etc/ceph/ceph.client.rbd.keyring</span><br></pre></td></tr></table></figure><p>切换到test_node</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># sudo apt install ceph-common -y</span><br><span class="line"># apt install ceph-iscsi</span><br><span class="line"># vim /etc/ceph/ceph.conf</span><br><span class="line">[global]</span><br><span class="line">fsid = 7361c797-d1c2-4dc7-bbdb-f637c5ee0fab</span><br><span class="line">mon initial members = node01,node02,node03</span><br><span class="line">mon host = 192.168.1.10, 192.168.1.11, 192.168.1.12</span><br><span class="line">auth cluster required = cephx</span><br><span class="line">auth service required = cephx</span><br><span class="line">auth client required = cephx</span><br><span class="line">[client.rbd]</span><br><span class="line">keyring = /etc/ceph/ceph.client.rbd.keyring</span><br></pre></td></tr></table></figure><h4 id="434-配置-iscsi-target"><a class="markdownIt-Anchor" href="#434-配置-iscsi-target"></a> 4.3.4 配置 iSCSI Target</h4><p>使用 root 用户运行gwcli开启一个命令行窗口：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"># 创 建 一 个iqn</span><br><span class="line">/&gt; cd /iscsi -targets/iscsi -targets &gt; create iqn.2024-04.com.zzq:test1</span><br><span class="line"># 创 建iSCSI网 关</span><br><span class="line">/iscsi -targets &gt; cd iqn.2024-04.com.zzq:test1/gateways</span><br><span class="line">/iscsi -target...est1/gateways &gt; create cephnode1 192.168.1.10</span><br><span class="line">/iscsi -target...est1/gateways &gt; create cephnode2 192.168.1.11</span><br><span class="line"># 添 加 镜 像</span><br><span class="line">/iscsi -target...est1/gateways &gt; cd /disks</span><br><span class="line">/disks &gt; create pool=rbd image=disk_1 size=10G</span><br><span class="line"># 为 客 户 端 创 建initiator</span><br><span class="line">/disks &gt; cd /iscsi -targets/iqn.2024-04.com.zzq:test1/hosts/</span><br><span class="line">iscsi -target...t:test1/hosts &gt; create iqn.2024-04.com.zzq:client</span><br><span class="line"># 为 客 户 端 创 建 硬 盘</span><br><span class="line">/iscsi -target...om.zzq:client &gt; disk add rbd/disk_1</span><br><span class="line"># 设 置 密 码</span><br><span class="line">/iscsi -target...om.zzq:client &gt; auth username=username@zzq password=password@zzq</span><br></pre></td></tr></table></figure><p>使用 apt 安装 open-iscsi 和 multipath-tools</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># apt install open-iscsi multipath-tools</span><br><span class="line"># vi /etc/iscsi/initiatorname.iscsi</span><br><span class="line">InitiatorName=iqn.2024-04.com.zzq:client</span><br><span class="line"># vi /etc/iscsi/iscsid.conf</span><br><span class="line">node.session.auth.authmethod = CHAP</span><br><span class="line">node.session.auth.username = username@zzq</span><br><span class="line">node.session.auth.password = password@zzq</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-030.png" alt="操作截图 30" /></p><p>重启服务加载新的配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart iscsid open-iscsi</span><br></pre></td></tr></table></figure><p>配置 MultiPath：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/multipath.conf</span><br><span class="line">defaults &#123;</span><br><span class="line">user_friendly_names yes</span><br><span class="line">find_multipaths yes</span><br><span class="line">&#125;</span><br><span class="line">devices &#123;</span><br><span class="line">device &#123;</span><br><span class="line">vendor                 &quot;LIO-ORG&quot;</span><br><span class="line">product                &quot;TCMU device&quot;</span><br><span class="line">hardware_handler       &quot;1 alua&quot;</span><br><span class="line">path_grouping_policy   &quot;failover&quot;</span><br><span class="line">path_selector          &quot;queue -length 0&quot;</span><br><span class="line">failback                60</span><br><span class="line">path_checker            tur</span><br><span class="line">prio                    alua</span><br><span class="line">prio_args               exclusive_pref_bit</span><br><span class="line">8fast_io_fail_tmo       25</span><br><span class="line">no_path_retry           queue</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"># 修改完成后重启服务加载新的配置文件</span><br><span class="line">systemctl restart multipathd</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-031.png" alt="操作截图 31" /></p><h4 id="435-测试块存储"><a class="markdownIt-Anchor" href="#435-测试块存储"></a> 4.3.5 测试块存储</h4><p>配置完成之后，可以直接使用客户端连接 ceph 提供的块存储服务。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># iscsiadm -m discovery -t st -p 192.168.1.10</span><br></pre></td></tr></table></figure><p>登 录</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># iscsiadm -m node --login</span><br><span class="line"># multipath -ll</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-032.png" alt="操作截图 32" /></p><p>挂载情况和multipath 情况正常，块存储测试成功。</p><h3 id="44-文件存储的使用"><a class="markdownIt-Anchor" href="#44-文件存储的使用"></a> 4.4 文件存储的使用</h3><p>前面我们已经在服务器端成功添加了MDS服务（3.2节中），下面为文件存储创建其所需的存储池。一个存储data数据，一个存储meta数据。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># ceph osd pool create cephfs_data  16  16</span><br><span class="line"># ceph osd pool create cephfs_metadata  16  16</span><br><span class="line"># ceph osd pool application enable cephfs_data  cephfs</span><br><span class="line"># ceph osd pool application enable cephfs_metadata  cephfs</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-033.png" alt="操作截图 33" /></p><p>创建文件系统</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># ceph fs new cephfs cephfs_metadata cephfs_data</span><br><span class="line"># ceph osd pool set cephfs_data bulk true</span><br><span class="line"># ceph fs ls</span><br><span class="line"># ceph -s</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-034.png" alt="操作截图 34" /></p><p>挂载cephfs</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># mkdir /mnt/cephfs</span><br><span class="line"># mount -t ceph 192.168.1.158:6789:/ /mnt/cephfs -o name=admin,fs=cephfs</span><br></pre></td></tr></table></figure><p>查看情况</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ceph fs ls</span><br><span class="line">ceph fs status cephfs</span><br><span class="line">ceph mds stat</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-035.png" alt="操作截图 35" /></p><p>创建cephfs用户，并允许这个用户访问cephfs池：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ceph fs authorize cephfs client.cephfs / rw</span><br></pre></td></tr></table></figure><p>导出client.cephfs令牌环，客户端使用该凭证访问cephfs文件共享系统</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ceph auth get-or-create client.cephfs -o /etc/ceph/client.cephfs.keyring</span><br></pre></td></tr></table></figure><p>文件系统测试完成。</p><h3 id="45-对象存储"><a class="markdownIt-Anchor" href="#45-对象存储"></a> 4.5 对象存储</h3><h4 id="451-gui图形工具的使用"><a class="markdownIt-Anchor" href="#451-gui图形工具的使用"></a> 4.5.1 GUI图形工具的使用</h4><p>前面我们已经在ceph中创建了管理用户并且把RGW添加到dashboard中（3.2节），下面我们在ceph的GUI界面中创建另一个用户zzqtest，后期会使用“CloudBerry Explorer for Amazon S3”软件访问该用户。</p><p>首先登陆ceph管理页面，在users中创建一个zzqtest用户：</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-036.png" alt="操作截图 36" /></p><p>创建成功：</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-037.png" alt="操作截图 37" /></p><p>使用“CloudBerry Explorer for Amazon S3”软件访问该用户，这台主机与云服务器主机在同一子网，因此输入https://192.168.1.10:7480即可访问，两个秘钥是之前创建用户时给出的秘钥，这里直接复制粘贴过来即可。成功连接：</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-038.png" alt="操作截图 38" /></p><p>然后我在主机内新建了一个txt文件并在zzqtest下创建一个桶zzqbucket，用来测试对象存储，下面我将该txt文件传至ceph中，操作成功：</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-039.png" alt="操作截图 39" /></p><p><img src="/images/posts/ceph-cluster-lab-notes/image-040.png" alt="操作截图 40" /></p><h4 id="452-s3cmd命令行操作"><a class="markdownIt-Anchor" href="#452-s3cmd命令行操作"></a> 4.5.2 s3cmd命令行操作</h4><p>首先我们先安装s3cmd，然后进行配置：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># apt update &amp;&amp; apt install s3cmd</span><br><span class="line"># vi ~/.s3cfg</span><br><span class="line">[default]</span><br><span class="line">access_key = MJ7U5U1WVJHNOG1AAY8K</span><br><span class="line">secret_key = 0gAjF6s8RFnr1plYEkf0lJC2TBvDuRbsECfHMmL4</span><br><span class="line">host_base = 192.168.1.10:7480</span><br><span class="line">host_bucket = 192.168.1.10:7480/%(bucket)</span><br><span class="line">cloudfront_host = 192.168.1.10:7480</span><br><span class="line">use_https = False</span><br></pre></td></tr></table></figure><p>下面我在云服务器中进行s3cmd命令练习，以下是操作内容：</p><p>（1）列举所有buckets：s3cmd ls</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-041.png" alt="操作截图 41" /></p><p>（2）查看某个bucket中的内容：s3cmd ls s3://bucketname</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-042.png" alt="操作截图 42" /></p><p>（3）查看bucket包括内容: s3cmd la</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-043.png" alt="操作截图 43" /></p><p>（4）创建一个新的bucket：s3cmd mb s3://newbucket</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-044.png" alt="操作截图 44" /></p><p>（5）删除空bucket：s3cmd rb s3://newbucket</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-045.png" alt="操作截图 45" /></p><p>（6）上传一个txt文件到bucket:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s3cmd put zzqnew.txt s3://zzqbucket</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-046.png" alt="操作截图 46" /></p><p>（7）从bucket下载文件：s3cmd get s3://zzqbucket/对象存储测试.txt</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-047.png" alt="操作截图 47" /></p><p>（8）删除bucket中的文件：s3cmd rm s3://zzqbucket/对象存储测试.txt</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-048.png" alt="操作截图 48" /></p><p>（9）查看bucket已使用空间：s3cmd du -H s3://zzqbucket</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-049.png" alt="操作截图 49" /></p><p>（10）获取bucket的信息：s3cmd info s3://zzqbucket</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-050.png" alt="操作截图 50" /></p><p>（11）移动bucket中文件：s3cmd mv s3://zzqbucket/zzqnew.txt s3://zzqbucket2/zzqnew2.txt</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-051.png" alt="操作截图 51" /></p><p>（12）本地目录下文件同步bucket：s3cmd sync s3://zzqbucket</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-052.png" alt="操作截图 52" /></p><p>（13）对比指定目录下文件和bucket中文件并删除桶中差异文件（因为前面刚进行了同步，此时没有差异文件）：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s3cmd sync --delete-removed ./ s3://zzqbucket</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-053.png" alt="操作截图 53" /></p><p>（14）帮助命令：s3cmd --help</p><p><img src="/images/posts/ceph-cluster-lab-notes/image-054.png" alt="操作截图 54" /></p><h4 id="453-python编程"><a class="markdownIt-Anchor" href="#453-python编程"></a> 4.5.3 Python编程</h4><p>在使用Python访问对象存储时，我使用了前面使用的Window客户端，这台主机同样和集群处于一个内网，因此可以直接通过内网访问，并且在编程时更加方便，下面我编写了一个test.py文件，在vscode中进行运行。</p><p>我已经安装了Python3.12以及boto3，经过测试我发现有些语法需要按照boto3官方文档中的要求进行修改，这里附上文档链接：<a href="https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html%E3%80%82">https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html。</a></p><p>程序内容和运行结果如下：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">import boto3</span><br><span class="line">import boto3.session</span><br><span class="line">import botocore.config</span><br><span class="line">s3 = boto3.resource(&#x27;s3&#x27;)</span><br><span class="line">s3_client = boto3.session.Session(</span><br><span class="line">aws_access_key_id = &#x27;MJ7U5U1WVJHNOG1AAY8K&#x27;,</span><br><span class="line">aws_secret_access_key = &#x27;0gAjF6s8RFnr1plYEkf0lJC2TBvDuRbsECfHMmL4&#x27;)</span><br><span class="line">s3client = s3_client.client(service_name=&quot;s3&quot;,endpoint_url=&quot;http://192.168.1.10:7480&quot;)</span><br><span class="line">print(&quot;列出所有的桶：&quot;)</span><br><span class="line">response = s3client.list_buckets()</span><br><span class="line">buckets = [bucket[&#x27;Name&#x27;] for bucket in response[&#x27;Buckets&#x27;]]</span><br><span class="line">print(buckets)</span><br><span class="line">print(&quot;创建一个桶&quot;)</span><br><span class="line">m = s3client.create_bucket(Bucket=&#x27;my-new-bucket&#x27;)</span><br><span class="line">print(&quot;列出所有的桶：&quot;)</span><br><span class="line">response = s3client.list_buckets()</span><br><span class="line">buckets = [bucket[&#x27;Name&#x27;] for bucket in response[&#x27;Buckets&#x27;]]</span><br><span class="line">print(buckets)</span><br></pre></td></tr></table></figure><p><img src="/images/posts/ceph-cluster-lab-notes/image-055.png" alt="操作截图 55" /></p><p>到这里，Ceph 对象存储的图形界面访问、s3cmd 命令行操作和 Python 编程访问都已经完成。</p><h2 id="五-一些记录"><a class="markdownIt-Anchor" href="#五-一些记录"></a> 五、一些记录</h2><p>这次 Ceph 搭建覆盖了块存储、对象存储和文件存储三类接口。RBD 更适合云主机卷这类块设备场景，RGW 提供兼容 S3 的对象接口，CephFS 则提供共享文件系统能力。</p><p>从部署过程看，Ceph 的关键是网络、keyring、pool、daemon 名称和应用类型保持一致。Dashboard 对观察集群状态很有帮助，尤其是在排查 OSD、MDS、RGW 和 iSCSI 相关问题时。</p><p>这份记录可以作为后续重新搭建 Ceph 集群时的操作手册。</p><h2 id="附录"><a class="markdownIt-Anchor" href="#附录"></a> 附录</h2><p>搭建过程中参考过的资料：</p><p>[1] <a href="https://www.server-world.info/en/note?os=CentOS_Stream_9&amp;p=ceph18&amp;f=1">https://www.server-world.info/en/note?os=CentOS_Stream_9&amp;p=ceph18&amp;f=1</a></p><p>[2] <a href="https://zhuanlan.zhihu.com/p/671096663">https://zhuanlan.zhihu.com/p/671096663</a></p><p>[3] <a href="https://docs.ceph.com/en/reef/">https://docs.ceph.com/en/reef/</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Ceph 可以同时提供块存储、文件存储和对象存储，适合作为云平台和大数据平台的底层存储。这篇记录按实际操作顺序整理，从三节点集群初始化开始，逐步完成 Dashboard、MDS、RGW、RBD、iSCSI、CephFS 和 S3 访问验证。&lt;/p&gt;
&lt;h2 id=&quot;一-目标</summary>
      
    
    
    
    <category term="Cloud Computing and Big Data" scheme="https://zhouzhq2021.github.io/categories/Cloud-Computing-and-Big-Data/"/>
    
    
    <category term="Ceph" scheme="https://zhouzhq2021.github.io/tags/Ceph/"/>
    
  </entry>
  
</feed>
