<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Inference on Wilson Wu</title><link>https://wilsonwu.me/zh/tags/inference/</link><description>Recent content in Inference on Wilson Wu</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Mon, 17 Aug 2026 00:00:00 -0400</lastBuildDate><atom:link href="https://wilsonwu.me/zh/tags/inference/index.xml" rel="self" type="application/rss+xml"/><item><title>使用 Envoy Ratelimit 构建支持 RPM/TPM 的分布式 AI 限流服务器</title><link>https://wilsonwu.me/zh/blog/2026/envoy-ai-ratelimit/</link><pubDate>Mon, 17 Aug 2026 00:00:00 -0400</pubDate><guid>https://wilsonwu.me/zh/blog/2026/envoy-ai-ratelimit/</guid><description>&lt;p&gt;&lt;img alt="概览" loading="lazy" src="https://wilsonwu.me/zh/blog/2026/envoy-ai-ratelimit/1-overview.png"&gt;&lt;/p&gt;
&lt;p&gt;在普通 HTTP API 中，我们通常只需要限制“每分钟请求数”。但对于大模型接口，仅限制请求数量远远不够：一次请求可能只消耗几十个 Token，也可能消耗数万个 Token。&lt;/p&gt;</description><content:encoded><![CDATA[<p><img alt="概览" loading="lazy" src="/zh/blog/2026/envoy-ai-ratelimit/1-overview.png"></p>
<p>在普通 HTTP API 中，我们通常只需要限制“每分钟请求数”。但对于大模型接口，仅限制请求数量远远不够：一次请求可能只消耗几十个 Token，也可能消耗数万个 Token。</p>
<p>因此，一个实用的 AI 网关通常需要同时支持：</p>
<ul>
<li><strong>RPM（Requests Per Minute）</strong>：每分钟请求数。</li>
<li><strong>TPM（Tokens Per Minute）</strong>：每分钟 Token 数。</li>
<li>按租户、API Key、模型或套餐分别计费。</li>
<li>多个 Envoy 实例共享同一份额度。</li>
<li>在高并发条件下避免各实例独立计数造成额度放大。</li>
<li>返回统一的 <code>429 Too Many Requests</code> 和剩余额度信息。</li>
</ul>
<p>本文介绍如何使用 <a href="https://github.com/envoyproxy/ratelimit">envoyproxy/ratelimit</a>、Redis 和 Envoy 构建一套分布式 AI 限流系统。</p>
<h2 id="为什么不能只使用-envoy-local-rate-limit">为什么不能只使用 Envoy Local Rate Limit</h2>
<p>Envoy 提供了 Local Rate Limit Filter，但它的计数器默认位于当前 Envoy 进程中。</p>
<p>假设我们有 4 个 Envoy 实例，并为某个租户配置：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>RPM = 100
</span></span></code></pre></div><p>如果每个 Envoy 都独立维护 100 RPM，那么整个集群实际上可能放行：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>4 × 100 = 400 RPM
</span></span></code></pre></div><p>这显然不是我们想要的结果。</p>
<p>Envoy 的 Global Rate Limit Filter 可以把限流请求发送给外部 Rate Limit Service。<code>envoyproxy/ratelimit</code> 正是 Envoy 官方生态中常用的全局限流服务实现，它将计数器存放在 Redis 中。</p>
<p>整体架构如下：</p>
<p><img alt="架构" loading="lazy" src="/zh/blog/2026/envoy-ai-ratelimit/2-architect.png"></p>
<p>这里增加了一个很薄的 <strong>AI Quota Adapter</strong>，主要用于解决两个问题：</p>
<ol>
<li>从请求体中解析模型、租户和 Token 数量。</li>
<li>分别使用不同的 <code>hits_addend</code> 检查 RPM 和 TPM。</li>
</ol>
<h2 id="为什么-ai-tpm-需要额外的适配层">为什么 AI TPM 需要额外的适配层</h2>
<p>Envoy 的普通限流规则通常把一次 HTTP 请求视为一次命中：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>hits_addend = 1
</span></span></code></pre></div><p>这适合 RPM，但不适合 TPM。</p>
<p>例如下面两个请求：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>请求 A：消耗 200 Token
</span></span><span style="display:flex;"><span>请求 B：消耗 20,000 Token
</span></span></code></pre></div><p>如果两者都只增加一次计数，TPM 就没有意义。TPM 必须让一次请求按照 Token 数增加计数器：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>hits_addend = token_cost
</span></span></code></pre></div><p>例如：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>RPM 请求：hits_addend = 1
</span></span><span style="display:flex;"><span>TPM 请求：hits_addend = 3200
</span></span></code></pre></div><p>Rate Limit Service 的 <code>hits_addend</code> 通常作用于整个 <code>RateLimitRequest</code>，因此 RPM 和 TPM 需要使用两次限流调用，或者由自定义适配服务组合处理。</p>
<p>推荐的处理顺序是：</p>
<ol>
<li>检查并增加 RPM，<code>hits_addend=1</code>。</li>
<li>RPM 允许后，再检查并增加 TPM，<code>hits_addend=token_cost</code>。</li>
<li>任意一个返回 <code>OVER_LIMIT</code>，适配层向 Envoy 返回 429。</li>
</ol>
<p>这种模式下，每个维度都由 Redis 原子计数，多个 Envoy 实例不会各自放大额度。</p>
<p>需要注意：两次调用并不是跨计数器事务。RPM 已增加后，TPM 可能拒绝请求。因此要提前明确计数语义：</p>
<ul>
<li>RPM 统计所有进入限流流程的请求尝试；或者</li>
<li>自行扩展服务，使用一段 Redis Lua 脚本同时检查 RPM 和 TPM。</li>
</ul>
<p>多数系统接受第一种语义，因为它更简单，也能避免恶意用户通过大量超大请求免费消耗网关资源。</p>
<h2 id="设计限流-descriptor">设计限流 Descriptor</h2>
<p>限流维度通常包括：</p>
<ul>
<li><code>tenant</code>：租户、组织或 API Key。</li>
<li><code>model</code>：模型名称。</li>
<li><code>metric</code>：<code>rpm</code> 或 <code>tpm</code>。</li>
</ul>
<p>最终生成的 Descriptor 可以表示为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>tenant = tenant-a
</span></span><span style="display:flex;"><span>model  = gpt-4.1
</span></span><span style="display:flex;"><span>metric = rpm
</span></span></code></pre></div><p>以及：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>tenant = tenant-a
</span></span><span style="display:flex;"><span>model  = gpt-4.1
</span></span><span style="display:flex;"><span>metric = tpm
</span></span></code></pre></div><p>这样可以实现：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>tenant-a + gpt-4.1 -&gt; 60 RPM
</span></span><span style="display:flex;"><span>tenant-a + gpt-4.1 -&gt; 100,000 TPM
</span></span></code></pre></div><p>如果不希望按模型拆分额度，也可以只保留：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>tenant + metric
</span></span></code></pre></div><p>例如所有模型共享同一份租户额度。</p>
<h2 id="配置-envoyproxyratelimit">配置 envoyproxy/ratelimit</h2>
<p>创建 <code>config/config.yaml</code>：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">domain</span>: <span style="color:#ae81ff">ai-gateway</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">tenant</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">model</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">metric</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">value</span>: <span style="color:#ae81ff">rpm</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">rate_limit</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">minute</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">requests_per_unit</span>: <span style="color:#ae81ff">60</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">metric</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">value</span>: <span style="color:#ae81ff">tpm</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">rate_limit</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">minute</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">requests_per_unit</span>: <span style="color:#ae81ff">100000</span>
</span></span></code></pre></div><p>这里 <code>tenant</code> 和 <code>model</code> 没有指定固定值，可用于根据请求中的 Descriptor 值进行分桶。实际部署前，应使用所选 ratelimit 版本提供的配置校验工具检查配置。</p>
<p>适配层发送 RPM Descriptor：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  {<span style="color:#f92672">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;tenant&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;tenant-a&#34;</span>},
</span></span><span style="display:flex;"><span>  {<span style="color:#f92672">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;model&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;gpt-4.1&#34;</span>},
</span></span><span style="display:flex;"><span>  {<span style="color:#f92672">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;metric&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;rpm&#34;</span>}
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>发送 TPM Descriptor：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  {<span style="color:#f92672">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;tenant&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;tenant-a&#34;</span>},
</span></span><span style="display:flex;"><span>  {<span style="color:#f92672">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;model&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;gpt-4.1&#34;</span>},
</span></span><span style="display:flex;"><span>  {<span style="color:#f92672">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;metric&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;tpm&#34;</span>}
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>Descriptor 的顺序必须与配置中的嵌套路径一致。</p>
<h3 id="不同套餐额度">不同套餐额度</h3>
<p>如果不同租户拥有不同额度，可以增加套餐维度：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>tenant -&gt; plan -&gt; model -&gt; metric
</span></span></code></pre></div><p>例如：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">domain</span>: <span style="color:#ae81ff">ai-gateway</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">tenant</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">plan</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">value</span>: <span style="color:#ae81ff">standard</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">model</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">metric</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">value</span>: <span style="color:#ae81ff">rpm</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">rate_limit</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">minute</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">requests_per_unit</span>: <span style="color:#ae81ff">60</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">metric</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">value</span>: <span style="color:#ae81ff">tpm</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">rate_limit</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">minute</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">requests_per_unit</span>: <span style="color:#ae81ff">100000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">plan</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">value</span>: <span style="color:#ae81ff">enterprise</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">model</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">descriptors</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">metric</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">value</span>: <span style="color:#ae81ff">rpm</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">rate_limit</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">minute</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">requests_per_unit</span>: <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>              - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">metric</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">value</span>: <span style="color:#ae81ff">tpm</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">rate_limit</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">minute</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">requests_per_unit</span>: <span style="color:#ae81ff">2000000</span>
</span></span></code></pre></div><p>请求中的 <code>tenant</code> 用于隔离计数器，<code>plan</code> 用于选择额度。</p>
<p>不要只使用 <code>plan</code> 作为计数维度，否则所有 Standard 用户可能共享同一个计数器。</p>
<h2 id="五使用-docker-compose-部署">五、使用 Docker Compose 部署</h2>
<p>下面是一个简化示例：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">redis</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">image</span>: <span style="color:#ae81ff">redis:7-alpine</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">command</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">redis-server</span>
</span></span><span style="display:flex;"><span>      - --<span style="color:#ae81ff">appendonly</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;yes&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">redis-data:/data</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;6379:6379&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ratelimit</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">image</span>: <span style="color:#ae81ff">envoyproxy/ratelimit:&lt;固定版本号&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">command</span>: <span style="color:#ae81ff">/bin/ratelimit</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">environment</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">LOG_LEVEL</span>: <span style="color:#ae81ff">info</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">REDIS_SOCKET_TYPE</span>: <span style="color:#ae81ff">tcp</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">REDIS_URL</span>: <span style="color:#ae81ff">redis:6379</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">RUNTIME_ROOT</span>: <span style="color:#ae81ff">/data</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">RUNTIME_SUBDIRECTORY</span>: <span style="color:#ae81ff">ratelimit</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">RUNTIME_WATCH_ROOT</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">USE_STATSD</span>: <span style="color:#e6db74">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">./config:/data/ratelimit</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;8081:8081&#34;</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;6070:6070&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">depends_on</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">redis</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">redis-data</span>:
</span></span></code></pre></div><p>生产环境不要直接使用 <code>master</code> 或浮动标签，应固定到经过验证的 release 版本。</p>
<p>不同版本的镜像端口和环境变量可能存在差异，因此部署时应以对应版本仓库中的示例配置为准。</p>
<p>启动：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose up -d
</span></span></code></pre></div><p>查看日志：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose logs -f ratelimit
</span></span></code></pre></div><p>生产环境可以运行多个 ratelimit 副本：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>ratelimit-1 ─┐
</span></span><span style="display:flex;"><span>ratelimit-2 ─┼── Redis
</span></span><span style="display:flex;"><span>ratelimit-3 ─┘
</span></span></code></pre></div><p>ratelimit 服务本身可以保持无状态，只要所有副本：</p>
<ul>
<li>使用相同的配置。</li>
<li>指向同一个 Redis 逻辑集群。</li>
<li>使用一致的 Redis Key 前缀或命名空间。</li>
</ul>
<h2 id="ai-quota-adapter-的核心逻辑">AI Quota Adapter 的核心逻辑</h2>
<p>适配层可以实现为 Envoy <code>ext_authz</code> 服务。它接收 Envoy 转发过来的请求信息，完成身份识别、Token 计算和额度检查。</p>
<p>下面是简化后的 Go 伪代码：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">CheckAIQuota</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tenant</span> <span style="color:#66d9ef">string</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span> <span style="color:#66d9ef">string</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tokenCost</span> <span style="color:#66d9ef">uint32</span>,
</span></span><span style="display:flex;"><span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 第一次调用：RPM</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">rpmResp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">rateLimitClient</span>.<span style="color:#a6e22e">ShouldRateLimit</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ctx</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitRequest</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Domain</span>: <span style="color:#e6db74">&#34;ai-gateway&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Descriptors</span>: []<span style="color:#f92672">*</span><span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitDescriptor</span>{
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">Entries</span>: []<span style="color:#f92672">*</span><span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitDescriptor_Entry</span>{
</span></span><span style="display:flex;"><span>                        {<span style="color:#a6e22e">Key</span>: <span style="color:#e6db74">&#34;tenant&#34;</span>, <span style="color:#a6e22e">Value</span>: <span style="color:#a6e22e">tenant</span>},
</span></span><span style="display:flex;"><span>                        {<span style="color:#a6e22e">Key</span>: <span style="color:#e6db74">&#34;model&#34;</span>, <span style="color:#a6e22e">Value</span>: <span style="color:#a6e22e">model</span>},
</span></span><span style="display:flex;"><span>                        {<span style="color:#a6e22e">Key</span>: <span style="color:#e6db74">&#34;metric&#34;</span>, <span style="color:#a6e22e">Value</span>: <span style="color:#e6db74">&#34;rpm&#34;</span>},
</span></span><span style="display:flex;"><span>                    },
</span></span><span style="display:flex;"><span>                },
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">HitsAddend</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">rpmResp</span>.<span style="color:#a6e22e">OverallCode</span> <span style="color:#f92672">==</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitResponse_OVER_LIMIT</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ErrRPMExceeded</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 第二次调用：TPM</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tpmResp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">rateLimitClient</span>.<span style="color:#a6e22e">ShouldRateLimit</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ctx</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitRequest</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Domain</span>: <span style="color:#e6db74">&#34;ai-gateway&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Descriptors</span>: []<span style="color:#f92672">*</span><span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitDescriptor</span>{
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">Entries</span>: []<span style="color:#f92672">*</span><span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitDescriptor_Entry</span>{
</span></span><span style="display:flex;"><span>                        {<span style="color:#a6e22e">Key</span>: <span style="color:#e6db74">&#34;tenant&#34;</span>, <span style="color:#a6e22e">Value</span>: <span style="color:#a6e22e">tenant</span>},
</span></span><span style="display:flex;"><span>                        {<span style="color:#a6e22e">Key</span>: <span style="color:#e6db74">&#34;model&#34;</span>, <span style="color:#a6e22e">Value</span>: <span style="color:#a6e22e">model</span>},
</span></span><span style="display:flex;"><span>                        {<span style="color:#a6e22e">Key</span>: <span style="color:#e6db74">&#34;metric&#34;</span>, <span style="color:#a6e22e">Value</span>: <span style="color:#e6db74">&#34;tpm&#34;</span>},
</span></span><span style="display:flex;"><span>                    },
</span></span><span style="display:flex;"><span>                },
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">HitsAddend</span>: <span style="color:#a6e22e">tokenCost</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">tpmResp</span>.<span style="color:#a6e22e">OverallCode</span> <span style="color:#f92672">==</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ratelimitv3</span>.<span style="color:#a6e22e">RateLimitResponse_OVER_LIMIT</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ErrTPMExceeded</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>实际代码中的 protobuf 包名和字段类型应以项目锁定的 Envoy API 版本为准。</p>
<p>适配层的完整处理流程如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>1. 从 Authorization/API Key 中识别 tenant
</span></span><span style="display:flex;"><span>2. 从 JSON 请求体中读取 model
</span></span><span style="display:flex;"><span>3. 使用模型对应的 tokenizer 计算 input_tokens
</span></span><span style="display:flex;"><span>4. 根据策略计算 token_cost
</span></span><span style="display:flex;"><span>5. 调用 RLS 检查 RPM
</span></span><span style="display:flex;"><span>6. 调用 RLS 检查 TPM
</span></span><span style="display:flex;"><span>7. 都允许：返回 OK
</span></span><span style="display:flex;"><span>8. 任意超限：返回 429
</span></span></code></pre></div><h2 id="在-envoy-中接入适配层">在 Envoy 中接入适配层</h2>
<p>如果适配层实现 Envoy 的 <code>ext_authz</code> gRPC 协议，可以在 Envoy 中配置：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">http_filters</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">envoy.filters.http.ext_authz</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">typed_config</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;@type&#34;: </span><span style="color:#ae81ff">type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">transport_api_version</span>: <span style="color:#ae81ff">V3</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">grpc_service</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">envoy_grpc</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">cluster_name</span>: <span style="color:#ae81ff">ai_quota_adapter</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">timeout</span>: <span style="color:#ae81ff">0.</span><span style="color:#ae81ff">5s</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">with_request_body</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">max_request_bytes</span>: <span style="color:#ae81ff">1048576</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">allow_partial_message</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">pack_as_bytes</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">failure_mode_allow</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">envoy.filters.http.router</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">typed_config</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;@type&#34;: </span><span style="color:#ae81ff">type.googleapis.com/envoy.extensions.filters.http.router.v3.Router</span>
</span></span></code></pre></div><p>对应的 Cluster：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">clusters</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ai_quota_adapter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">STRICT_DNS</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">connect_timeout</span>: <span style="color:#ae81ff">0.</span><span style="color:#ae81ff">25s</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">http2_protocol_options</span>: {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">load_assignment</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">cluster_name</span>: <span style="color:#ae81ff">ai_quota_adapter</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">endpoints</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">lb_endpoints</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">endpoint</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">address</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">socket_address</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">address</span>: <span style="color:#ae81ff">ai-quota-adapter</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">port_value</span>: <span style="color:#ae81ff">9000</span>
</span></span></code></pre></div><p>因为 Token 计算需要读取 JSON 请求体，所以需要配置 <code>with_request_body</code>。</p>
<p>注意限制最大请求体，避免攻击者提交超大 Prompt 导致 Envoy 或适配层内存压力。</p>
<h2 id="tpm-应该如何计算">TPM 应该如何计算</h2>
<p>RPM 的定义比较简单，一次请求增加 1。</p>
<p>TPM 更复杂，因为请求发出时通常只知道输入 Token，尚不知道模型最终会产生多少输出 Token。</p>
<h3 id="方案一只计算输入-token">方案一：只计算输入 Token</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>token_cost = input_tokens
</span></span></code></pre></div><p>优点：</p>
<ul>
<li>请求开始前就能精确计算。</li>
<li>实现最简单。</li>
<li>非流式和流式接口都适用。</li>
</ul>
<p>缺点：</p>
<ul>
<li>无法限制输出 Token。</li>
<li>用户可能通过很短的 Prompt 请求大量输出。</li>
</ul>
<p>适合将 TPM 定义为“输入 Token 配额”的系统。</p>
<h3 id="方案二预留最大输出-token">方案二：预留最大输出 Token</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>token_cost = input_tokens + requested_max_output_tokens
</span></span></code></pre></div><p>例如：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>input_tokens = 1200
</span></span><span style="display:flex;"><span>max_tokens   = 4096
</span></span><span style="display:flex;"><span>token_cost   = 5296
</span></span></code></pre></div><p>优点：</p>
<ul>
<li>在调用模型前就能做严格准入控制。</li>
<li>不会因为输出 Token 未知而突破额度。</li>
<li>适合流式响应。</li>
</ul>
<p>缺点：</p>
<ul>
<li>实际只输出 300 Token 时仍预留了 4096 Token。</li>
<li>如果不实现退款，额度利用率会偏低。</li>
</ul>
<p>这是最容易实现“严格不超额”的方案。</p>
<p>网关必须对 <code>max_tokens</code> 设置服务端上限，不能完全信任客户端：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>effective_max_tokens = min(client_max_tokens, server_model_limit)
</span></span></code></pre></div><p>否则客户端可以提交异常大的 <code>max_tokens</code>，让自己或整个租户迅速耗尽配额。</p>
<h3 id="方案三先预留再按实际用量结算">方案三：先预留，再按实际用量结算</h3>
<p>完整思路是：</p>
<ol>
<li>请求开始时预留：</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>input_tokens + max_output_tokens
</span></span></code></pre></div><ol start="2">
<li>响应结束后得到实际输出 Token。</li>
<li>退还未使用部分：</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>refund = max_output_tokens - actual_output_tokens
</span></span></code></pre></div><p>但 <code>envoyproxy/ratelimit</code> 的标准限流调用主要面向正向增加计数，并不天然提供完整的“预留—提交—退款”事务模型。</p>
<p>如果业务必须支持精确退款，通常需要：</p>
<ul>
<li>在适配层实现额外的 Redis Lua 脚本。</li>
<li>使用独立的预留记录和过期时间。</li>
<li>请求结束后进行 commit/refund。</li>
<li>使用请求 ID 保证幂等。</li>
</ul>
<p>此时 <code>envoyproxy/ratelimit</code> 仍可负责 RPM 或基础 TPM，但结算逻辑已经属于定制配额服务。</p>
<h3 id="方案四流式增量扣费">方案四：流式增量扣费</h3>
<p>对于流式输出，可以：</p>
<ol>
<li>请求开始时扣除输入 Token。</li>
<li>每生成一批输出 Token，再增加一次 TPM 计数。</li>
<li>一旦超过额度，终止上游流。</li>
</ol>
<p>例如每 32 或 128 个 Token 扣一次。</p>
<p>这种方式需要能观察响应流，因此仅靠请求阶段的 <code>ext_authz</code> 不够，需要：</p>
<ul>
<li>Envoy <code>ext_proc</code>；</li>
<li>Wasm Filter；</li>
<li>自定义 HTTP Filter；</li>
<li>或由 AI Gateway 自身代理并读取流式响应。</li>
</ul>
<p>增量批次越小，控制越精确，但 gRPC 和 Redis 调用次数也越多。</p>
<h2 id="多-envoy-实例如何保证共享额度">多 Envoy 实例如何保证共享额度</h2>
<p>假设一个租户拥有：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>RPM = 100
</span></span><span style="display:flex;"><span>TPM = 200,000
</span></span></code></pre></div><p>请求可能随机落到不同 Envoy：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Request 1 -&gt; Envoy A
</span></span><span style="display:flex;"><span>Request 2 -&gt; Envoy B
</span></span><span style="display:flex;"><span>Request 3 -&gt; Envoy C
</span></span></code></pre></div><p>但所有限流请求最终都会访问同一套 Redis 计数器：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>ai-gateway / tenant-a / gpt-4.1 / rpm
</span></span><span style="display:flex;"><span>ai-gateway / tenant-a / gpt-4.1 / tpm
</span></span></code></pre></div><p>因此额度是整个集群共享的，而不是每个 Envoy 各有一份。</p>
<p>可以横向扩展的部分包括：</p>
<ul>
<li>Envoy。</li>
<li>AI Quota Adapter。</li>
<li>envoyproxy/ratelimit。</li>
</ul>
<p>不能随意拆分的是同一个配额 Key 的计数状态。如果使用多个完全独立的 Redis，则必须确保相同租户、模型和指标始终路由到同一个分片，否则额度又会被放大。</p>
<h2 id="返回标准限流响应">返回标准限流响应</h2>
<p>发生超限时，适配层应向 Envoy 返回 429：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">429</span> <span style="color:#a6e22e">Too Many Requests</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json</span>
</span></span><span style="display:flex;"><span>Retry-After<span style="color:#f92672">:</span> <span style="color:#ae81ff">12</span>
</span></span></code></pre></div><p>响应体可以采用 OpenAI 风格：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;error&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;message&#34;</span>: <span style="color:#e6db74">&#34;Rate limit exceeded for tenant tenant-a&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;rate_limit_error&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;code&#34;</span>: <span style="color:#e6db74">&#34;tpm_limit_exceeded&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>建议分别暴露 RPM 和 TPM 信息：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-RateLimit-Limit-Requests: 60
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-RateLimit-Remaining-Requests: 12
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-RateLimit-Reset-Requests: 18
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-RateLimit-Limit-Tokens: 100000
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-RateLimit-Remaining-Tokens: 24580
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-RateLimit-Reset-Tokens: 18
</span></span></span></code></pre></div><p>为了避免客户端形成同步重试风暴，SDK 应使用带随机抖动的指数退避：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>sleep = random(0, min(cap, base × 2^retry_count))
</span></span></code></pre></div><h2 id="精准限流需要明确的边界">“精准限流”需要明确的边界</h2>
<p>使用 Redis 共享计数器可以解决多实例额度放大的问题，但“精准”仍然需要明确具体含义。</p>
<h3 id="1-它通常是固定时间窗">1. 它通常是固定时间窗</h3>
<p>配置：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">unit</span>: <span style="color:#ae81ff">minute</span>
</span></span></code></pre></div><p>通常表示分钟级固定时间窗，不等同于严格的滑动窗口。</p>
<p>用户可能在一个窗口结束前发送 100 次请求，并在新窗口开始后立刻再发送 100 次。从任意连续 60 秒观察，流量可能接近配置值的两倍。</p>
<p>因此，该方案能够保证：</p>

<blockquote>
  <p>所有 Envoy 实例共享同一个固定时间窗计数器。</p>

</blockquote>
<p>但它不自动等于：</p>

<blockquote>
  <p>任意连续 60 秒都绝不超过限制。</p>

</blockquote>
<p>如果业务要求严格滑动窗口，需要使用 Redis Sorted Set、滑动窗口计数器或专门的配额算法。</p>
<h3 id="2-输出-token-在请求开始时未知">2. 输出 Token 在请求开始时未知</h3>
<p>如果 TPM 包含输出 Token，则无法在生成之前知道最终消耗量。</p>
<p>所谓严格控制通常意味着：</p>
<ul>
<li>使用 <code>max_tokens</code> 预留；或者</li>
<li>在流式生成过程中增量扣费。</li>
</ul>
<p>单纯在响应结束后上报实际 Token，只能用于计费和统计，不能阻止当前请求突破 TPM。</p>
<h3 id="3-rpm-和-tpm-不是一个原子事务">3. RPM 和 TPM 不是一个原子事务</h3>
<p>标准 RLS 请求中的 <code>hits_addend</code> 是统一的，而 RPM 和 TPM 需要不同增量，因此通常需要两次调用。</p>
<p>如果必须保证“RPM 和 TPM 同时检查、同时增加、任意失败都不修改”，需要定制服务并使用单个 Redis Lua 脚本完成事务。</p>
<h2 id="生产环境中的关键问题">生产环境中的关键问题</h2>
<h3 id="1-请求重试和重复扣费">1. 请求重试和重复扣费</h3>
<p>Rate Limit Service 的计数调用通常不是业务级幂等操作。</p>
<p>如果适配层因为超时重试同一个请求：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>第一次调用已经写入 Redis，但响应丢失
</span></span><span style="display:flex;"><span>第二次调用再次增加计数
</span></span></code></pre></div><p>就会产生重复扣费。</p>
<p>建议：</p>
<ul>
<li>限流调用不要盲目自动重试。</li>
<li>使用 Envoy 的 <code>x-request-id</code> 或业务请求 ID。</li>
<li>对严格计费场景，在适配层中实现请求幂等记录。</li>
<li>幂等记录过期时间至少覆盖限流窗口和最大请求时长。</li>
</ul>
<h3 id="2-故障时-fail-open-还是-fail-closed">2. 故障时 Fail Open 还是 Fail Closed</h3>
<p>当 Redis 或 ratelimit 服务不可用时，可以选择：</p>
<p><strong>Fail Open：</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>限流系统不可用时继续放行
</span></span></code></pre></div><p>优点是可用性高，缺点是可能导致成本失控。</p>
<p><strong>Fail Closed：</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>限流系统不可用时拒绝请求
</span></span></code></pre></div><p>优点是保护成本，缺点是限流组件故障会影响业务。</p>
<p>AI 推理通常成本较高，面向外部用户的付费 API 更适合 Fail Closed。内部低风险环境可以考虑短时间 Fail Open，并配合熔断和告警。</p>
<h3 id="3-tokenizer-必须与模型匹配">3. Tokenizer 必须与模型匹配</h3>
<p>不同模型的 Tokenizer 可能不同。不能使用简单的字符数除以固定比例来实现严格 TPM。</p>
<p>应该维护：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>model -&gt; tokenizer
</span></span></code></pre></div><p>例如：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>gpt-family       -&gt; cl100k/o200k 类 tokenizer
</span></span><span style="display:flex;"><span>llama-family     -&gt; 对应 SentencePiece/BPE tokenizer
</span></span><span style="display:flex;"><span>自定义模型        -&gt; 模型实际 tokenizer.json
</span></span></code></pre></div><p>如果 tokenizer 不可用，可以做保守估算，但必须预留误差：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>estimated_tokens = ceil(raw_estimate × safety_factor)
</span></span></code></pre></div><h3 id="4-规范化模型名称">4. 规范化模型名称</h3>
<p>客户端可能使用：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>gpt-4.1
</span></span><span style="display:flex;"><span>gpt-4.1-2025-04-14
</span></span><span style="display:flex;"><span>my-gpt-alias
</span></span></code></pre></div><p>限流前应将它们映射到统一的配额名称：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>my-gpt-alias -&gt; gpt-4.1
</span></span></code></pre></div><p>否则用户可能通过切换别名绕过同一模型的额度。</p>
<h3 id="5-不要信任客户端提供的-token-数">5. 不要信任客户端提供的 Token 数</h3>
<p>不能让客户端直接提交：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-Token-Cost: 1
</span></span></span></code></pre></div><p>然后按该值扣费。</p>
<p>Token 数必须由可信组件计算，或者由上游模型返回后进行校验。客户端声明只能作为参考，不能作为计费依据。</p>
<h3 id="6-配置发布必须一致">6. 配置发布必须一致</h3>
<p>如果多个 ratelimit 副本加载了不同配置，可能出现：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>副本 A：RPM 100
</span></span><span style="display:flex;"><span>副本 B：RPM 200
</span></span></code></pre></div><p>即使 Redis 相同，结果也会变得不可预测。</p>
<p>生产环境应：</p>
<ul>
<li>使用固定版本配置。</li>
<li>进行启动前校验。</li>
<li>滚动发布时保持向后兼容。</li>
<li>为配置添加版本号和监控指标。</li>
</ul>
<h2 id="监控指标">监控指标</h2>
<p>至少应监控：</p>
<h3 id="envoy">Envoy</h3>
<ul>
<li>ext_authz 调用次数。</li>
<li>ext_authz 超时和失败次数。</li>
<li>HTTP 429 数量。</li>
<li>上游请求数和并发数。</li>
<li>请求体缓冲失败次数。</li>
</ul>
<h3 id="ai-quota-adapter">AI Quota Adapter</h3>
<ul>
<li>RPM 允许和拒绝次数。</li>
<li>TPM 允许和拒绝次数。</li>
<li>Token 计算耗时。</li>
<li>RLS gRPC 延迟。</li>
<li>Tokenizer 加载失败次数。</li>
<li>按模型和租户聚合的 Token 消耗。</li>
<li>重复请求或幂等命中次数。</li>
</ul>
<h3 id="ratelimit">Ratelimit</h3>
<ul>
<li><code>OK</code>、<code>OVER_LIMIT</code>、错误数量。</li>
<li>Redis 请求延迟。</li>
<li>Redis 连接池使用率。</li>
<li>配置加载状态。</li>
<li>各限流规则命中次数。</li>
</ul>
<h3 id="redis">Redis</h3>
<ul>
<li>CPU 和内存。</li>
<li>命令延迟。</li>
<li>连接数。</li>
<li>Key 数量。</li>
<li>Key 过期速率。</li>
<li>主从复制延迟。</li>
<li>Failover 次数。</li>
</ul>
<p>高基数租户 ID 不适合直接作为 Prometheus Label。否则租户数量较多时会产生严重的指标基数问题。按租户的详细用量更适合写入日志、ClickHouse 或专门的计量系统。</p>
<h2 id="推荐的最终实现">推荐的最终实现</h2>
<p>对于大多数 AI API 网关，可以采用下面的组合：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Envoy
</span></span><span style="display:flex;"><span>  └── ext_authz
</span></span><span style="display:flex;"><span>        └── AI Quota Adapter
</span></span><span style="display:flex;"><span>              ├── 解析租户和模型
</span></span><span style="display:flex;"><span>              ├── 使用可信 tokenizer 计算 input_tokens
</span></span><span style="display:flex;"><span>              ├── 规范化并限制 max_tokens
</span></span><span style="display:flex;"><span>              ├── RPM：RLS hits_addend = 1
</span></span><span style="display:flex;"><span>              └── TPM：RLS hits_addend =
</span></span><span style="display:flex;"><span>                       input_tokens + max_output_tokens
</span></span><span style="display:flex;"><span>                        └── envoyproxy/ratelimit
</span></span><span style="display:flex;"><span>                              └── Redis
</span></span></code></pre></div><p>这一方案具备以下特征：</p>
<ul>
<li>多 Envoy 实例共享统一额度。</li>
<li>RPM 和 TPM 分别计数。</li>
<li>支持按租户、模型、套餐隔离。</li>
<li>在请求进入模型服务之前完成准入控制。</li>
<li>通过预留最大输出 Token，保证不会因为流式输出突破 TPM。</li>
<li>Envoy、适配层和 ratelimit 服务都可以横向扩展。</li>
</ul>
<p>如果还需要按照实际输出 Token 精确结算，则在此基础上增加：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>预留记录 + 响应用量采集 + commit/refund + 请求幂等
</span></span></code></pre></div><p>此时系统已经从“限流服务”演进为一套完整的“分布式 AI 配额与计量服务”。</p>
<h2 id="总结">总结</h2>
<p><code>envoyproxy/ratelimit</code> 非常适合解决多 Envoy 实例之间的全局共享限流问题。它通过 Redis 集中维护计数器，避免每个网关实例独立计算额度。</p>
<p>对于 AI 场景，需要在标准 HTTP 限流之上增加 Token 感知能力：</p>
<ul>
<li>RPM 使用 <code>hits_addend=1</code>。</li>
<li>TPM 使用 <code>hits_addend=token_cost</code>。</li>
<li>使用租户、模型和指标构建 Descriptor。</li>
<li>请求阶段通过输入 Token或最大输出 Token预留完成准入。</li>
<li>多 Envoy、多个适配层和多个 RLS 副本共享同一个 Redis 计数空间。</li>
</ul>
<p>需要特别注意的是，分布式共享计数、固定时间窗、RPM/TPM 组合事务以及实际输出 Token 结算是四个不同问题。理解这些边界，才能构建一套既能保护模型成本、又能在高并发环境中稳定运行的 AI 限流系统。</p>
]]></content:encoded></item><item><title>用 llm-d-inference-sim 模拟大模型推理：无 GPU 环境下的最佳验证工具</title><link>https://wilsonwu.me/zh/blog/2026/llm-inference-simulation/</link><pubDate>Wed, 08 Jul 2026 00:00:00 -0400</pubDate><guid>https://wilsonwu.me/zh/blog/2026/llm-inference-simulation/</guid><description>&lt;p&gt;&lt;img alt="LLM 推理模拟" loading="lazy" src="https://wilsonwu.me/zh/blog/2026/llm-inference-simulation/1-llm-inference-simulation.png"&gt;&lt;/p&gt;
&lt;p&gt;在大模型应用快速落地的过程中，&lt;strong&gt;推理服务的工程化能力&lt;/strong&gt;往往比模型本身更复杂：模型调度、路由策略、限流、灰度发布、网关治理等问题，都需要真实的推理接口进行验证。然而，大模型推理依赖 GPU 和高成本算力资源，使得开发和测试门槛居高不下。&lt;/p&gt;</description><content:encoded><![CDATA[<p><img alt="LLM 推理模拟" loading="lazy" src="/zh/blog/2026/llm-inference-simulation/1-llm-inference-simulation.png"></p>
<p>在大模型应用快速落地的过程中，<strong>推理服务的工程化能力</strong>往往比模型本身更复杂：模型调度、路由策略、限流、灰度发布、网关治理等问题，都需要真实的推理接口进行验证。然而，大模型推理依赖 GPU 和高成本算力资源，使得开发和测试门槛居高不下。</p>
<p>如果你希望在<strong>没有 GPU 或资源受限的环境中验证推理架构设计</strong>，那么这个工具值得关注：</p>
<p><a href="https://github.com/llm-d/llm-d-inference-sim?utm_source=chatgpt.com">llm-d-inference-sim GitHub 项目</a></p>
<p>本文将系统介绍这个工具的能力、设计思路，以及它在实际工程中的应用价值。</p>
<h2 id="工具定位为推理工程而生的模拟器">工具定位：为推理工程而生的“模拟器”</h2>
<p><strong>llm-d-inference-sim</strong> 是一个专门用于模拟大模型推理服务的工具，其核心能力包括：</p>
<ul>
<li>提供与真实 LLM 推理服务一致的接口（如 OpenAI-style API）</li>
<li>接收推理请求（prompt / messages）</li>
<li>按配置生成“模拟响应”</li>
<li>支持多实例、多模型模拟部署</li>
</ul>
<p>简单来说，它并不真正执行模型推理，而是：</p>

<blockquote>
  <p><strong>模拟“推理服务行为”，而非“推理计算本身”</strong></p>

</blockquote>
<h2 id="为什么需要推理模拟">为什么需要推理模拟？</h2>
<p>在真实项目中，我们经常遇到以下问题：</p>
<h3 id="1-推理资源昂贵">1. 推理资源昂贵</h3>
<ul>
<li>GPU 成本高（A100 / H100）</li>
<li>推理服务部署复杂（vLLM / TensorRT-LLM / SGLang）</li>
<li>测试环境难以复刻生产规模</li>
</ul>
<h3 id="2-工程验证依赖真实服务">2. 工程验证依赖真实服务</h3>
<p>很多关键能力必须依赖推理接口验证，例如：</p>
<ul>
<li>API 网关路由策略（Envoy / Istio）</li>
<li>多模型调度与 fallback 机制</li>
<li>embedding + intent routing</li>
<li>限流 / 熔断 / 超时控制</li>
<li>灰度发布与 A/B testing</li>
</ul>
<p>但这些能力<strong>并不依赖模型“正确性”</strong>，只依赖：</p>

<blockquote>
  <p>“是否有一个行为类似 LLM 的服务存在”</p>

</blockquote>
<h2 id="llm-d-inference-sim-的核心价值">llm-d-inference-sim 的核心价值</h2>
<h3 id="1-零-gpu-依赖">1. 零 GPU 依赖</h3>
<p>开发者可以在以下环境直接使用：</p>
<ul>
<li>本地 MacBook</li>
<li>CI/CD 环境</li>
<li>Kubernetes 测试集群</li>
<li>云主机（无 GPU）</li>
</ul>
<p>无需部署任何真实模型。</p>
<h3 id="2-高度可控的响应模拟">2. 高度可控的响应模拟</h3>
<p>可以模拟：</p>
<ul>
<li>固定响应（用于稳定测试）</li>
<li>延迟（模拟推理耗时）</li>
<li>Token 生成节奏（Streaming）</li>
<li>错误返回（测试容错）</li>
</ul>
<p>这使它非常适合：</p>

<blockquote>
  <p><strong>压测、容错测试、链路验证</strong></p>

</blockquote>
<h3 id="3-支持多模型实例">3. 支持多模型实例</h3>
<p>你可以快速部署多个“模拟模型”：</p>
<ul>
<li>gpt-4 模拟服务</li>
<li>embedding 模型模拟</li>
<li>不同延迟 / 不同性能 profile</li>
</ul>
<p>用于验证：</p>
<ul>
<li>多模型路由策略</li>
<li>智能网关（AI Gateway）</li>
<li>fallback 逻辑</li>
</ul>
<h3 id="4-与真实-api-兼容">4. 与真实 API 兼容</h3>
<p>接口风格通常兼容：</p>
<ul>
<li>OpenAI API（/v1/chat/completions）</li>
<li>streaming 输出</li>
</ul>
<p>这意味着：</p>

<blockquote>
  <p><strong>可以无缝替换真实模型服务</strong></p>

</blockquote>
<h2 id="典型使用场景">典型使用场景</h2>
<h3 id="场景-1ai-网关--服务网格验证">场景 1：AI 网关 / 服务网格验证</h3>
<p>例如使用 Envoy / Istio 构建 AI Gateway：</p>
<ul>
<li>基于路径或 header 路由不同模型</li>
<li>embedding-based routing</li>
<li>Canary 发布</li>
</ul>
<p>问题：没有真实模型怎么办？</p>
<p>使用 llm-d-inference-sim 模拟多个模型后端，即可验证完整链路。</p>
<h3 id="场景-2多模型调度系统开发">场景 2：多模型调度系统开发</h3>
<p>在做类似以下系统时：</p>
<ul>
<li>vLLM-aware scheduler</li>
<li>KV Cache aware routing</li>
<li>模型负载均衡</li>
</ul>
<p>你需要：</p>
<ul>
<li>多个模型 endpoint</li>
<li>不同响应延迟</li>
<li>可控行为</li>
</ul>
<p>模拟器可以快速构造这些环境。</p>
<h3 id="场景-3cicd-自动化测试">场景 3：CI/CD 自动化测试</h3>
<p>真实模型服务难以接入 CI：</p>
<ul>
<li>成本高</li>
<li>不稳定</li>
<li>启动慢</li>
</ul>
<p>使用模拟器：</p>
<ul>
<li>启动快速</li>
<li>行为可预测</li>
<li>支持 mock response</li>
</ul>
<p>非常适合集成测试（integration test）</p>
<h3 id="场景-4产品演示--demo-环境">场景 4：产品演示 / Demo 环境</h3>
<p>在售前或内部 Demo 中：</p>
<ul>
<li>不需要真实模型</li>
<li>只需要“看起来能跑”</li>
</ul>
<p>使用模拟器可以：</p>
<ul>
<li>快速部署多个模型服务</li>
<li>构造稳定演示环境</li>
<li>避免 GPU 成本</li>
</ul>
<h2 id="工作原理简析">工作原理简析</h2>
<p>llm-d-inference-sim 的设计可以理解为：</p>
<h3 id="请求流程">请求流程</h3>
<ol>
<li>接收 HTTP 请求（如 <code>/v1/chat/completions</code>）</li>
<li>解析输入（prompt/messages）</li>
<li>根据配置规则生成响应</li>
<li>返回 JSON 或 streaming 输出</li>
</ol>
<h3 id="模拟能力通常包括">模拟能力通常包括</h3>
<ul>
<li>
<p><strong>Response 模板</strong></p>
<ul>
<li>固定文本</li>
<li>参数化生成</li>
</ul>
</li>
<li>
<p><strong>Latency 注入</strong></p>
<ul>
<li>模拟推理耗时（如 200ms / 2s）</li>
</ul>
</li>
<li>
<p><strong>Streaming</strong></p>
<ul>
<li>分块返回 token</li>
<li>模拟真实 LLM 输出节奏</li>
</ul>
</li>
<li>
<p><strong>错误注入</strong></p>
<ul>
<li>500 / timeout</li>
<li>测试重试机制</li>
</ul>
</li>
</ul>
<h2 id="与真实推理服务的关系">与真实推理服务的关系</h2>
<table>
	<thead>
			<tr>
					<th>维度</th>
					<th>模拟器</th>
					<th>真实模型（vLLM 等）</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>GPU</td>
					<td>❌ 不需要</td>
					<td>✅ 必需</td>
			</tr>
			<tr>
					<td>成本</td>
					<td>极低</td>
					<td>高</td>
			</tr>
			<tr>
					<td>响应真实性</td>
					<td>❌ 无语义能力</td>
					<td>✅</td>
			</tr>
			<tr>
					<td>接口兼容性</td>
					<td>✅</td>
					<td>✅</td>
			</tr>
			<tr>
					<td>工程验证能力</td>
					<td>✅</td>
					<td>✅</td>
			</tr>
	</tbody>
</table>

<blockquote>
  <p><strong>模拟器 ≠ 替代模型，而是补齐工程验证环节</strong></p>

</blockquote>
<h2 id="最佳实践建议">最佳实践建议</h2>
<h3 id="1-与网关结合使用">1. 与网关结合使用</h3>
<p>推荐组合：</p>
<ul>
<li>Envoy / Istio</li>
<li>AI Gateway</li>
<li>llm-d-inference-sim</li>
</ul>
<p>构建完整推理流量链路</p>
<h3 id="2-构造多模型拓扑">2. 构造多模型拓扑</h3>
<p>例如：</p>
<ul>
<li>model-a（低延迟）</li>
<li>model-b（高质量，高延迟）</li>
<li>model-fallback</li>
</ul>
<p>验证智能路由策略</p>
<h3 id="3-注入异常场景">3. 注入异常场景</h3>
<p>主动模拟：</p>
<ul>
<li>timeout</li>
<li>高延迟</li>
<li>错误返回</li>
</ul>
<p>提升系统鲁棒性</p>
<h3 id="4-用于开发早期阶段">4. 用于开发早期阶段</h3>
<p>在以下阶段优先使用模拟器：</p>
<ul>
<li>架构设计</li>
<li>API 设计</li>
<li>路由策略验证</li>
</ul>
<p>等到后期再接入真实模型。</p>
<h2 id="总结">总结</h2>
<p><strong>llm-d-inference-sim</strong> 解决的是一个被低估但关键的问题：</p>

<blockquote>
  <p>在没有模型的情况下，如何推进大模型工程化？</p>

</blockquote>
<p>它的核心价值不在于“模拟模型”，而在于：</p>
<ul>
<li>降低开发门槛</li>
<li>提升验证效率</li>
<li>解耦算力与工程</li>
</ul>
<p>对于正在构建 AI 基础设施（AI Gateway / 推理平台 / 多模型调度系统）的团队来说，这是一个非常实用的工具。</p>
]]></content:encoded></item><item><title>大语言模型推理并行策略全景解析：TP / DP / PP / EP 原理与 vLLM 实战性能验证</title><link>https://wilsonwu.me/zh/blog/2025/llm-inference-parallelism-in-vllm/</link><pubDate>Wed, 24 Dec 2025 00:00:00 -0500</pubDate><guid>https://wilsonwu.me/zh/blog/2025/llm-inference-parallelism-in-vllm/</guid><description>&lt;p&gt;当大语言模型的参数规模跨过几十亿门槛之后，一个事实变得不可回避：&lt;strong&gt;单卡时代已经结束&lt;/strong&gt;。无论是训练还是推理，模型本身的参数规模、KV Cache 的增长速度，以及真实业务中并发请求带来的显存与算力压力，都决定了大模型必须运行在多 GPU、甚至多节点的环境中。&lt;/p&gt;</description><content:encoded><![CDATA[<p>当大语言模型的参数规模跨过几十亿门槛之后，一个事实变得不可回避：<strong>单卡时代已经结束</strong>。无论是训练还是推理，模型本身的参数规模、KV Cache 的增长速度，以及真实业务中并发请求带来的显存与算力压力，都决定了大模型必须运行在多 GPU、甚至多节点的环境中。</p>
<p><img alt="分布式推理趋势" loading="lazy" src="/zh/blog/2025/llm-inference-parallelism-in-vllm/1-overview.png"></p>
<p>然而，<strong>用多张卡跑模型</strong>并不是一个简单的资源堆叠问题。在实践中，工程团队很快会发现：<strong>不同的拆分方式，带来的性能表现差异可能是数量级的</strong>。有的配置 TTFT 极低，却在高并发下迅速失速；有的方案吞吐惊人，却难以满足在线服务对延迟的要求；还有一些组合在理论上看似合理，却在真实硬件拓扑下被通信成本完全吞噬。</p>
<p>在训练与推理阶段，业界逐步沉淀出四类核心并行策略：</p>
<ul>
<li><strong>TP（Tensor Parallelism，张量并行）</strong></li>
<li><strong>DP（Data Parallelism，数据并行）</strong></li>
<li><strong>PP（Pipeline Parallelism，流水线并行）</strong></li>
<li><strong>EP（Expert Parallelism，专家并行，主要用于 MoE）</strong></li>
</ul>
<p><img alt="alt text" loading="lazy" src="/zh/blog/2025/llm-inference-parallelism-in-vllm/2-parallelism.png"></p>
<h2 id="大语言模型四种核心并行策略基础">大语言模型四种核心并行策略基础</h2>
<h3 id="tensor-parallelismtp算子级拆分">Tensor Parallelism（TP）：算子级拆分</h3>
<p>核心思想：将单层内部的矩阵计算（如 Linear / Attention）沿维度切分到多张 GPU 上并行计算。</p>
<p>典型拆分方式：</p>
<ul>
<li>Linear 层按 <code>hidden_size</code> 或 <code>num_heads</code> 切分</li>
<li>Attention 的 Q / K / V 或 FFN 的中间层拆分</li>
</ul>
<p><img alt="alt text" loading="lazy" src="/zh/blog/2025/llm-inference-parallelism-in-vllm/3-tp.png"></p>
<p>优点：</p>
<ul>
<li>单层显存占用显著下降</li>
<li>单 token 延迟较低（适合推理）</li>
</ul>
<p>代价：</p>
<ul>
<li>每一层都需要 <strong>All-Reduce / All-Gather</strong></li>
<li>通信频繁，对 NVLink / IB 依赖高</li>
</ul>
<p>适用场景：</p>
<ul>
<li>单模型过大但层数不多</li>
<li>对 <strong>TTFT（Time To First Token）</strong>敏感的在线推理</li>
</ul>
<h3 id="data-parallelismdp请求级复制">Data Parallelism（DP）：请求级复制</h3>
<p>核心思想：模型完整复制到多张 GPU，每张 GPU 处理不同请求或 batch。</p>
<p><img alt="alt text" loading="lazy" src="/zh/blog/2025/llm-inference-parallelism-in-vllm/4-dp.png"></p>
<p>优点：</p>
<ul>
<li>实现简单</li>
<li>无模型内部通信</li>
<li>吞吐线性扩展</li>
</ul>
<p>代价：</p>
<ul>
<li>显存需求最高（每卡一整模型）</li>
<li>单请求延迟不变</li>
</ul>
<p>适用场景：</p>
<ul>
<li>模型能放进单卡</li>
<li>离线推理 / 高并发吞吐</li>
</ul>
<h3 id="pipeline-parallelismpp层级切分">Pipeline Parallelism（PP）：层级切分</h3>
<p>核心思想：将模型按层切分到多张 GPU，请求在 GPU 间<strong>流水线式</strong>流动。</p>
<p><img alt="alt text" loading="lazy" src="/zh/blog/2025/llm-inference-parallelism-in-vllm/5-pp.png"></p>
<p>优点：</p>
<ul>
<li>几乎无层内通信</li>
<li>显存效率高（比 DP 好）</li>
</ul>
<p>代价：</p>
<ul>
<li>pipeline bubble（气泡）带来空转</li>
<li>小 batch 下延迟显著升高</li>
</ul>
<p>适用场景：</p>
<ul>
<li>超深模型（如 70B+）</li>
<li>长序列推理或训练</li>
</ul>
<h3 id="expert-parallelismepmoe-专属并行">Expert Parallelism（EP）：MoE 专属并行</h3>
<p>核心思想：在 MoE（Mixture of Experts）模型中，不同 Expert 分布在不同 GPU 上，仅被激活的 Expert 参与计算。</p>
<p><img alt="alt text" loading="lazy" src="/zh/blog/2025/llm-inference-parallelism-in-vllm/6-ep.png"></p>
<p>优点：</p>
<ul>
<li>参数规模大但计算稀疏</li>
<li>性能/参数比极高</li>
</ul>
<p>代价：</p>
<ul>
<li>Token 路由与负载不均</li>
<li>通信模式复杂</li>
</ul>
<p>适用场景：</p>
<ul>
<li>MoE 模型（如 Mixtral、DeepSeek-MoE）</li>
<li>高参数规模、可接受一定调度复杂度</li>
</ul>
<h2 id="并行策略并非互斥而是可组合的">并行策略并非互斥，而是可组合的</h2>
<p>在真实系统中，并行策略通常<strong>组合使用</strong>：</p>
<table>
	<thead>
			<tr>
					<th>组合方式</th>
					<th>典型用途</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>TP × DP</td>
					<td>推理最常见组合</td>
			</tr>
			<tr>
					<td>TP × PP</td>
					<td>超大模型训练 / 推理</td>
			</tr>
			<tr>
					<td>TP × EP</td>
					<td>MoE 推理</td>
			</tr>
			<tr>
					<td>DP × EP</td>
					<td>高吞吐 MoE 服务</td>
			</tr>
			<tr>
					<td>TP × PP × DP</td>
					<td>大规模分布式集群</td>
			</tr>
	</tbody>
</table>
<p>问题不在于<strong>能不能用</strong>，而在于：<strong>不同组合，在真实推理负载下，性能表现差异极大。</strong></p>
<p>这正是 vLLM 非常适合做验证的原因。</p>
<h2 id="vllm-中的并行能力概览">vLLM 中的并行能力概览</h2>
<p>vLLM 是一个面向<strong>高性能 LLM 推理</strong>的引擎，其核心优势包括：</p>
<ul>
<li>高效 KV Cache 管理（PagedAttention）</li>
<li>原生支持 <strong>TP / PP</strong></li>
<li>对 DP 友好（多实例、多副本）</li>
<li>MoE 模型中支持 EP（依赖模型实现）</li>
</ul>
<p>在 vLLM 中，并行配置主要通过<strong>启动参数 + 资源拓扑</strong>控制。</p>
<h2 id="基于-vllm-的并行策略配置示例">基于 vLLM 的并行策略配置示例</h2>
<h3 id="tensor-parallelismtp">Tensor Parallelism（TP）</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>vllm serve meta-llama/Llama-2-70b-hf <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --tensor-parallel-size <span style="color:#ae81ff">4</span>
</span></span></code></pre></div><p>含义：</p>
<ul>
<li>模型每一层被拆分到 4 张 GPU</li>
<li>适合单请求低延迟验证</li>
</ul>
<h3 id="pipeline-parallelismpp">Pipeline Parallelism（PP）</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>vllm serve meta-llama/Llama-2-70b-hf <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --pipeline-parallel-size <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --tensor-parallel-size <span style="color:#ae81ff">2</span>
</span></span></code></pre></div><p>含义：</p>
<ul>
<li>2-stage pipeline</li>
<li>每 stage 内 2-way TP</li>
<li>总 GPU 数 = 4</li>
</ul>
<h3 id="data-parallelismdp">Data Parallelism（DP）</h3>
<p>vLLM 本身不显式声明 DP，而是通过<strong>多实例部署</strong>实现：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># GPU 0-3</span>
</span></span><span style="display:flex;"><span>CUDA_VISIBLE_DEVICES<span style="color:#f92672">=</span>0,1,2,3 vllm serve ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># GPU 4-7</span>
</span></span><span style="display:flex;"><span>CUDA_VISIBLE_DEVICES<span style="color:#f92672">=</span>4,5,6,7 vllm serve ...
</span></span></code></pre></div><p>再通过上层负载均衡（如 Kubernetes / Gateway）实现 DP。</p>
<h3 id="expert-parallelismep">Expert Parallelism（EP）</h3>
<p>对于 MoE 模型（如 Mixtral）：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>vllm serve mistralai/Mixtral-8x7B-Instruct <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --tensor-parallel-size <span style="color:#ae81ff">4</span>
</span></span></code></pre></div><p>vLLM 会自动将 Expert 分布到 TP 组内 GPU（模型内部实现 EP）。</p>
<h2 id="如何在-vllm-中验证并行组合的真实性能">如何在 vLLM 中“验证”并行组合的真实性能</h2>
<h3 id="核心评测指标">核心评测指标</h3>
<p>建议至少关注以下指标：</p>
<table>
	<thead>
			<tr>
					<th>指标</th>
					<th>含义</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>TTFT</td>
					<td>首 token 延迟</td>
			</tr>
			<tr>
					<td>Tokens/s</td>
					<td>生成吞吐</td>
			</tr>
			<tr>
					<td>GPU 利用率</td>
					<td>并行效率</td>
			</tr>
			<tr>
					<td>KV Cache 命中率</td>
					<td>长上下文效率</td>
			</tr>
			<tr>
					<td>通信开销</td>
					<td>TP / EP 成本</td>
			</tr>
	</tbody>
</table>
<h3 id="推荐对比实验矩阵">推荐对比实验矩阵</h3>
<p>以 8 GPU 为例：</p>
<table>
	<thead>
			<tr>
					<th>方案</th>
					<th>TP</th>
					<th>PP</th>
					<th>DP</th>
					<th>适用目标</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>A</td>
					<td>8</td>
					<td>1</td>
					<td>1</td>
					<td>最低延迟</td>
			</tr>
			<tr>
					<td>B</td>
					<td>4</td>
					<td>2</td>
					<td>1</td>
					<td>显存均衡</td>
			</tr>
			<tr>
					<td>C</td>
					<td>2</td>
					<td>2</td>
					<td>2</td>
					<td>吞吐优先</td>
			</tr>
			<tr>
					<td>D</td>
					<td>1</td>
					<td>1</td>
					<td>8</td>
					<td>最大并发</td>
			</tr>
	</tbody>
</table>
<p>通过固定输入长度与请求并发，对比上述方案即可得到清晰结论。</p>
<h3 id="实际经验结论常见规律">实际经验结论（常见规律）</h3>
<ul>
<li><strong>TP 越大，TTFT 越低，但通信越重</strong></li>
<li><strong>PP 提升显存利用率，但对小 batch 极不友好</strong></li>
<li><strong>DP 是吞吐的唯一线性放大手段</strong></li>
<li><strong>MoE 的 EP 在低并发下可能反而变慢</strong></li>
</ul>
<h2 id="总结">总结</h2>
<p>并行策略没有<strong>最优解</strong>，只有<strong>最适解</strong>，TP / DP / PP / EP 并不是竞争关系，而是：</p>

<blockquote>
  <p><strong>在模型规模、请求模式、硬件拓扑三者约束下的工程权衡。</strong></p>

</blockquote>
<p>vLLM 提供了一个极其适合做<strong>并行策略验证与对比实验</strong>的平台。真正有价值的工作不是“支持了什么并行”，而是：</p>
<ul>
<li>在你的模型</li>
<li>在你的硬件</li>
<li>在你的真实请求负载下</li>
</ul>
<p><strong>验证哪种组合，才是最优解。</strong></p>
]]></content:encoded></item><item><title>AI 推理的最佳选择 - vLLM</title><link>https://wilsonwu.me/zh/blog/2025/the-best-ai-inference-vllm/</link><pubDate>Wed, 15 Oct 2025 00:00:00 -0400</pubDate><guid>https://wilsonwu.me/zh/blog/2025/the-best-ai-inference-vllm/</guid><description>随着各组织从大语言模型（LLM）的试验阶段迈向生产部署，选择哪种推理平台就成了一项关键的业务决策。这个选择不仅影响性能，也影响灵活性、成本优化，以及应对快速变化业务需求的能力。</description><content:encoded><![CDATA[<p>原文链接：<a href="https://medium.com/open-5g-hypercore/episode-xxx-the-best-choice-for-ai-inference-vllm-286b2af2df71">The Best Choice for AI Inference -&gt; vLLM</a></p>

<blockquote>
  <p>注意：本译文已获得的原文作者翻译授权，为避免广告嫌疑，本文中移除了特定产品和品牌的商业宣传内容，仅保留技术特性及企业级产品功能描述，相关内容请以原文为准。</p>

</blockquote>
<p><img alt="AI 推理的最佳选择 - vLLM" loading="lazy" src="/zh/blog/2025/the-best-ai-inference-vllm/1-vllm.png"></p>
<p>随着各组织从大语言模型（LLM）的试验阶段迈向生产部署，选择哪种推理平台就成了一项关键的业务决策。这个选择不仅影响性能，也影响灵活性、成本优化，以及应对快速变化业务需求的能力。</p>
<p>对于技术人员和方案架构师在评估 LLM 推理平台时，应该重点考虑以下三大因素：</p>
<ol>
<li><strong>架构灵活性</strong>：能否在不同硬件加速器和混合云环境间部署，而不会被某一家厂商锁定。</li>
<li><strong>运行可扩展性</strong>：支持从单 GPU 部署扩展到分布式多节点的高级部署模式。</li>
<li><strong>生态开放性</strong>：对最广泛的模型与内核支持，以及能与各种企业软件生态系统整合。</li>
</ol>
<p>vLLM 在开源基础、先进内存管理能力，以及即将推出的分布式部署蓝图方面，独特地满足这些需求。与专有或硬件专用方案不同，这套组合提供了在成本、性能和运营需求上随时优化调整的自由。</p>
<p>本文将深入分析为何 vLLM 在其技术架构与能力上（尤其是其 KV-Cache 管理、并行策略，以及未来的 llm-d 分布式能力）提供了最可持续的生产级 LLM 部署路径。</p>
<hr>
<h2 id="开源优势">开源优势</h2>
<h3 id="社区驱动的大规模创新">社区驱动的大规模创新</h3>
<p>LLM 推理的发展，根本上受到开源创新的推动。过去一年半以来 <a href="https://blog.vllm.ai/2025/01/27/v1-alpha-release.html">vLLM V1: A Major Upgrade to vLLM’s Core Architecture | vLLM 博客（英文版）</a>，vLLM 在支持多样模型、功能和硬件后端方面取得显著成绩，从伯克利大学的研究项目成长为开源 AI 生态中的事实标准之一 <a href="https://blog.vllm.ai/2025/01/10/vllm-2024-wrapped-2025-vision.html">vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）</a>。</p>
<p><img alt="vLLM 社区发展" loading="lazy" src="/zh/blog/2025/the-best-ai-inference-vllm/2-vllm-growth.png"></p>

<blockquote>
  <p><a href="https://inferenceops.substack.com/p/state-of-the-model-serving-communities-269">vLLM 社区发展参考链接</a></p>

</blockquote>
<p>vLLM 现在隶属于 PyTorch 基金会托管项目（<a href="https://github.com/vllm-project/vllm">GitHub — vllm-project/vllm: A high-throughput and memory-efficient inference and serving engine for LLMs</a>），这保证了其长远的可持续性和治理机制。</p>
<h3 id="企业支持与开源结合">企业支持与开源结合</h3>
<p>许多企业及产品及解决方案对 vLLM 的态度类似于其对 Linux/OpenStack/Kubernetes 的策略：在社区驱动创新基础上，加入企业级支持、安全性和运维工具。这样模型即可部署在本地、公有云或混合云中，避免绑定。</p>
<p>这种方式带来的好处包括：</p>
<ul>
<li><strong>无厂商绑定</strong>：用户可自由选择部署环境</li>
<li><strong>透明开发</strong>：安全漏洞与缺陷公开追踪与快速修复</li>
<li><strong>社区贡献共享</strong>：一个组织开发的功能惠及整个生态</li>
<li><strong>可定制性</strong>：源代码开放，可针对特定需求修改</li>
</ul>
<p>此外，vLLM 在以下方面具备战略优势：</p>
<ul>
<li><strong>硬件无关性</strong>：不局限于 NVIDIA，支持 AMD、Intel、TPU 等多种平台（<a href="https://github.com/vllm-project/vllm">GitHub — vllm-project/vllm: A high-throughput and memory-efficient inference and serving engine for LLMs</a>）</li>
<li><strong>快速功能迭代</strong>：vLLM v1 对其核心模块（调度器、KV 缓存管理、worker、采样器、API 服务等）进行了全面重构（<a href="https://blog.vllm.ai/2025/01/27/v1-alpha-release.html">vLLM V1: A Major Upgrade to vLLM’s Core Architecture | vLLM 博客（英文版）</a>）</li>
<li><strong>生态整合</strong>：原生兼容 Hugging Face、OpenAI API、Kubernetes 生态</li>
<li><strong>成本优化</strong>：可以为不同工作负载选择最具性价比的硬件</li>
</ul>
<hr>
<h2 id="架构灵活性与并行化策略">架构灵活性与并行化策略</h2>
<h3 id="理解并行化understanding-parallelization">理解并行化（Understanding Parallelization）</h3>
<p>大型语言模型带来了特殊的扩展挑战。例如：一个拥有 70B 参数 的模型，仅其权重（使用 FP16 精度）就需约 140GB 内存，这远远超过单一卡片的容量。基于 vLLM 及相关产品和解决方案通过四种互补的并行化策略来应对这些挑战，每一种策略都解决不同类型的扩展问题。</p>
<h3 id="数据并行data-parallelism-dp跨请求扩展">数据并行（Data Parallelism, DP）：跨请求扩展</h3>
<p>数据并行是最简单的一种扩展模式：在多个服务器上运行完整模型的多个副本，每个副本处理不同的请求批次。在这种模式下：</p>
<ul>
<li><strong>维护完整模型</strong>：每个加速器／服务器保存整个模型的权重。</li>
<li><strong>分配请求</strong>：请求由负载均衡器分发给不同的副本。</li>
<li><strong>支持线性扩展</strong>：增加服务器数目可以线性提升吞吐量。</li>
<li><strong>简化部署</strong>：部署相对简单，不需要对模型进行切分（sharding）。</li>
</ul>
<pre tabindex="0"><code class="language-code" data-lang="code">          [负载均衡器]
               |
    ------------------------
    |          |           |
[Server1]  [Server2]  [Server3]
[Model A]  [Model A]  [Model A]
    |          |           |
[Batch 1]  [Batch 2]  [Batch 3]

关键：每台服务器都有完整的模型副本并行处理不同的请求
</code></pre><p>此模式与基于 KServe 的 Replica-Sets 用于模型副本服务很好的契合，能自动根据请求负载进行扩展，同时保有模型服务的简单性。</p>
<h3 id="流水线并行pipeline-parallelism-pp按层分配">流水线并行（Pipeline Parallelism, PP）：按层分配</h3>
<p>流水线并行将模型按照层（layers）分拆，不同的加速器处理不同的网络层：</p>
<ul>
<li><strong>顺序处理</strong>：请求在 GPU 间依次通过模型层进行处理。</li>
<li><strong>内存均衡</strong>：内存需求在多个设备间较为均匀分布。</li>
<li><strong>灵活部署</strong>：部署灵活，可以跨多个节点，不一定依赖特别高速的互联。</li>
<li><strong>微批处理</strong>：通过微批处理（micro-batching）来保持 GPU 利用率。</li>
</ul>
<pre tabindex="0"><code class="language-code" data-lang="code">Request → [GPU1:NN-Layers 1-8] → [GPU2:NN-Layers 9-16] → [GPU3:NN-Layers 17-24] → Response
             ↓                     ↓                      ↓
          [Memory]              [Memory]               [Memory]
          
时间线：请求按顺序流经 GPU
</code></pre><p>但流水线并行在现代 LLM 应用中也面临一些限制：</p>
<ul>
<li><strong>“流水线气泡”（pipeline bubble）效率低下</strong>：某些 GPU 在等待上游阶段的数据时处于空闲状态。</li>
<li><strong>通信开销高</strong>：阶段间数据传输成为瓶颈。</li>
<li><strong>延迟敏感</strong>：每个阶段都会增加延迟，对“第一个 token 的时间”（time-to-first-token）有负面影响。</li>
<li><strong>对 MoE（Mixture of Experts，混合专家模型）不友好</strong>：MoE 模型中只激活某些专家（非密集执行），这样无法很好地映射到按层划分的流水线结构中。</li>
</ul>
<p>因为这些限制，很多生产环境更倾向于将张量并行（Tensor Parallelism）用于节点内部扩展，将数据并行（Data Parallelism）用于跨节点扩展。尽管如此，在内存受限或与其他并行策略组合使用的场景中，流水线并行仍然有价值。</p>
<h3 id="张量并行tensor-parallelism-tp切分模型权重">张量并行（Tensor Parallelism, TP）：切分模型权重</h3>
<p>当模型太大，单个加速器无法容纳其所有权重时，就使用张量并行来将模型权重拆分到多个 GPU 上：</p>
<ul>
<li><strong>水平层拆分</strong>：每次矩阵乘法操作被分配到多个 GPU，按部分计算。</li>
<li><strong>同步计算</strong>：GPU 之间通过高速互联（如 NVLink、Infinity Fabric 等）进行同步和数据交换。</li>
<li><strong>内存效率</strong>：可使得模型可服务的规模比单个 GPU 容量大出约 4～8 倍。</li>
<li><strong>低延迟</strong>：如果硬件条件合适，通信开销低的话，张量并行还能保持较低的延迟。</li>
</ul>
<pre tabindex="0"><code class="language-code" data-lang="code">Original Matrix Operation:
[Large Weight Matrix] × [Input] = [Output]

With Tensor Parallelism:
[Weight Part 1] → GPU1 ↘
[Weight Part 2] → GPU2 → [Combine] → [Output]
[Weight Part 3] → GPU3 ↗

关键：单矩阵运算跨 GPU 拆分，需要高速互连（NVLink）
</code></pre><p>vLLM 的张量并行实现是硬件无关的，支持多种互联技术和不同类型的加速器。</p>
<h3 id="专家并行expert-parallelism-epmoe-模型中的专家分布策略">专家并行（Expert Parallelism, EP）：MoE 模型中的专家分布策略</h3>
<p>对于<strong>混合专家模型（MoE）</strong>，专家并行把不同的专家（experts）分配给多个 GPU 或多个节点，而不是让每个 GPU 都保存所有专家，由一个路由层（router layer）动态地将 token 分发给需要的专家：</p>
<ul>
<li><strong>分布式专家分片</strong>：专家（experts）被分片（shard）分布在不同的 GPU／节点，使拥有上百个专家的模型能扩展到远超过单设备的内存容量。</li>
<li><strong>动态 token 路由</strong>：每个 token 只被发送到它应使用的一个或多个专家，以减少计算开销。</li>
<li><strong>专家并行负载平衡（Expert Parallel Load Balancing, EPLB）</strong>：防止某些热门专家／专家组被频繁调用导致不均匀负载，通过动态复制或分布专家，以分散负载。</li>
<li><strong>分层调度（Hierarchical Scheduling）</strong>：在多节点集群中，路由与复制先在节点间协调，再在节点内部的 GPU 间协调，确保整体利用率均衡且减少跨节点通信。</li>
<li><strong>性能收益</strong>：对大型的 MoE 模型，这种专家并行在具有高速互联（NVLink、InfiniBand 等）的环境中可以实现接近线性扩展的吞吐效率。</li>
</ul>
<pre tabindex="0"><code class="language-code" data-lang="code">               [Router Layer]
                      |
        ---------------------------------
        |          |          |         |
      GPU1        GPU2       GPU3      GPU4
    [Exp 1,2]   [Exp 3,4]  [Exp 5,6]  [Exp 7,8]
    
令牌流：路由器 → 选定专家 → 输出
</code></pre><h3 id="vllm-的统一策略">vLLM 的统一策略</h3>
<p>vLLM 的区别与优势在于它能够无缝结合这些并行策略，为分布式推理提供高度灵活性，使组织能够：</p>
<ol>
<li><strong>混合使用策略</strong>：例如，在节点内部用张量并行，在节点间用流水线并行。</li>
<li><strong>根据硬件特性调整</strong>：基于可用的互联速度、GPU 内存、延迟敏感性等因素做最优规划。</li>
<li><strong>动态扩展</strong>：可以从单节点部署起步，随着需求增长扩展到多节点／多 GPU。</li>
<li><strong>保持 API 的一致性</strong>：无论使用哪种并行策略，道线路径、调用接口等对使用者尽量透明，不需要因为并行方式不同而改很多逻辑。</li>
</ol>
<p>这种灵活性在混合云环境中尤其关键，因为不同环境（本地、云端、不同厂商硬件）之间的配置可能大不相同。</p>
<pre tabindex="0"><code class="language-code" data-lang="code">Hybrid TP+PP+EP+DP Deployment (MoE Model):

Pipeline Stage 1:               Pipeline Stage 2:              Pipeline Stage 3:
Node 1 (Attention Layers)       Node 2 (MoE FFN Layers)        Node 3 (Output Layers)
[Attn Part A] ←→ [Attn Part B]  [Router + Experts Subset]      [Out Part A] ←→ [Out Part B]
    GPU1           GPU2          GPU3: Experts 1-4                  GPU5            GPU6
         ↕ NVLink ↕              GPU4: Experts 5-8                       ↕ NVLink ↕
                                     ↕ NVLink ↕

Expert Distribution Detail (Node 2):
            [Router]
         GPU3  |  GPU4
        -------|-------
        Exp1   |  Exp5
        Exp2   |  Exp6  
        Exp3   |  Exp7
        Exp4   |  Exp8

Request Flow: → [Attention] → [MoE Layer/Router] → [Output] → Response
                              ↓
                    [Route to specific experts]

Parallelization Breakdown:
- Tensor Parallel (TP): Attention and Output layers split within nodes.
- Pipeline Parallel (PP): Different model stages across nodes .
- Expert Parallel (EP): MoE experts distributed across GPUs in Node 2.
- Data Parallel (DP): Entire pipeline can be replicated for more throughput.
</code></pre><hr>
<h2 id="更好的内存管理kv-cachekey-value-缓存">更好的内存管理：KV-Cache（Key-Value 缓存）</h2>
<h3 id="在-llm-推理中理解-kv-cache">在 LLM 推理中理解 KV-Cache</h3>
<p>KV-Cache（键-值缓存）是 LLM 推理优化中最关键的特性之一。在注意力计算期间，模型必须访问先前 token 的表示 —— 随着序列长度增长，这个过程变得非常消耗内存。</p>
<p>高效的 KV-Cache 管理能决定同一硬件上能同时服务 10 个并发用户或 100 个并发用户，因为缓存复用将计算换成内存利用率。</p>
<pre tabindex="0"><code class="language-code" data-lang="code">(Q * K^T) * V computation process with caching

Step 1 (Prefill Phase)
                    Keys Transpose
                          ↓
    Queries              Keys              Values            Results
       |                  |                  |                  |
    [  Q  ]      ×     [  K  ]      →    [Scores]     ×    [  V  ]    →    [Output]
       |                  |                  |                  |
      64                 64                                    64              64
                          ↓                                     ↓
                    [Storing K]                           [Storing V]
                          ↓                                     ↓
                    ←── Cache memory (stores both K and V) ──→

Step N (Decode Phase)
                    Keys Transpose
                          ↓
    Query              Cached K            Cached V          Results
       |                  |                  |                  |
    [  q  ]      ×   [K₁...Kₙ]      →    [Scores]     ×   [V₁...Vₙ]   →    [output]
    (new)             (from cache)                         (from cache)       (new)
       |                  |                  |                  |
       1                 64                                    64               1
                          ↑                                     ↑
                    ←── Reading from cache (both K and V) ──→

Key Points:
- Prefill: Computes K and V for all input tokens, stores both in cache
- Decode: For each new token, reads ALL previous K and V values from cache
- Both K and V matrices must be cached (not just V)
- Cache size grows with sequence length
</code></pre><h3 id="prefill-阶段-vs-decode-阶段两个截然不同的阶段">Prefill 阶段 vs Decode 阶段：两个截然不同的阶段</h3>
<p>LLM 推理包含两个本质不同的阶段：</p>
<h4 id="prefill-阶段prompt-处理阶段">Prefill 阶段（Prompt 处理阶段）</h4>
<ul>
<li>并行处理所有输入 token。</li>
<li>计算密集型，GPU 利用率高。</li>
<li>为 prompt 中的所有 token 生成初始的 KV-Cache 条目。</li>
<li>延迟与 prompt 长度成正比。</li>
<li>大批量（larger batch sizes）带来好处。</li>
</ul>
<h4 id="decode-阶段token-生成阶段">Decode 阶段（Token 生成阶段）</h4>
<ul>
<li>一个 token 一个 token 顺序生成。</li>
<li>此阶段受限于内存带宽（memory-bandwidth bound）。</li>
<li>每新增一个 token，都要读取整个 KV-Cache 来自缓存的所有先前的 K 和 V。</li>
<li>延迟与输出 token 数量成正比。</li>
<li>依赖高效的缓存管理。</li>
</ul>
<h3 id="pagedattentionvllm-的内存革命">PagedAttention：vLLM 的内存革命</h3>
<p>vLLM 引入 PagedAttention，这是一种在 KV-Cache 管理方面的突破性设计，它将 GPU 内存视为类似操作系统中的虚拟内存（virtual memory）来处理，其关键设计包括：</p>
<ul>
<li><strong>非连续存储（Non-contiguous Storage）</strong>：KV-Cache 块可以存储在 GPU 内存的任意地方，不必是连续的。</li>
<li><strong>动态分配（Dynamic Allocation）</strong>：随着序列长度增长时才分配内存。</li>
<li><strong>内存共享（Memory Sharing）</strong>：完全相同的 prompt 前缀（prompt prefixes）可以共享 KV-Cache 块。</li>
<li><strong>几乎零浪费（Near-Zero Waste）</strong>：消除静态分配中常见的内部碎片问题。</li>
</ul>
<p>这种设计让 vLLM 可以维持更大的批量大小、更高的并发性、以及比那些依赖静态、大型 KV-Cache 缓冲区的系统更好的 GPU 利用率。</p>
<h3 id="连续批处理continuous-batching最大化-gpu-利用率">连续批处理（Continuous Batching）：最大化 GPU 利用率</h3>
<p>vLLM 的连续批处理机制带来了几个举措：</p>
<ul>
<li><strong>动态请求加入</strong>：新的请求可以在 decode 步骤之间加入正在运行的 batch。</li>
<li><strong>提前完成处理</strong>：已经完成的序列立即释放资源。</li>
<li><strong>GPU 使用最优化</strong>：通过混合处理 prefill 与 decode 操作以保持高利用率。</li>
<li><strong>支持抢占（Preemption）</strong>：可以暂停低优先级的请求以优先处理紧急请求。</li>
</ul>
<h3 id="在部署中的实际意义">在部署中的实际意义</h3>
<p>这些内存管理上的创新在实际部署中带来了具体的效益：</p>
<ol>
<li><strong>更高的并发性</strong>：在相同硬件上服务更多用户。</li>
<li><strong>更佳的成本效率</strong>：大幅减少基础设施需求。</li>
<li><strong>改善延迟</strong>：通过高效调度缩短 time-to-first-token。</li>
<li><strong>灵活性</strong>：可以处理变长的序列长度，而无需重新配置系统。</li>
</ol>
<p>KV-Cache 优化在即将推出的 llm-d 分布式架构中变得尤为关键，因为高效的内存使用使得过去因内存约束而无法实现的新部署模式成为可能。</p>
<hr>
<h2 id="使用-llm-d-扩展kubernetes-原生的分布式推理">使用 llm-d 扩展：Kubernetes 原生的分布式推理</h2>
<h3 id="超越单服务器部署">超越单服务器部署</h3>
<p>虽然 vLLM 自身是高性能推理引擎，但在大规模生产环境中，还需要复杂的编排和智能请求调度。2025 年 5 月，红帽、Google Cloud、IBM 研究院、NVIDIA 和 CoreWeave 联手推出 llm-d，构建在 vLLM 之上的 Kubernetes 原生分布式推理栈。</p>
<p>llm-d 并不是 vLLM 的内置功能，而是一个补充的编排层。可以把它比作 Linux 与 Kubernetes 的关系：vLLM 负责推理引擎，llm-d 负责跨实例的调度与编排。它结合了三项开源技术：vLLM、Inference Gateway（AI 路由层）和 Kubernetes。</p>
<p>KServe 也在集成 llm-d，通过新的 <a href="https://docs.google.com/document/d/11ZQJ2VhTc42S9K4yau2dMs3Q3f4jqWJL_7Sq14C3hzY/edit?tab=t.0">LLMInference CRD</a>，实现统一的服务接口，简化生成式 AI 的企业上手路径。</p>
<p><img alt="Kubernetes 中的 AI 推理" loading="lazy" src="/zh/blog/2025/the-best-ai-inference-vllm/3-ai-inference-k8s.png"></p>
<p>llm-d 将三大基础开源技术集成到一个统一的服务栈中：</p>
<ul>
<li><strong>vLLM</strong> - 执行模型推理的高性能推理引擎。</li>
<li><strong>推理网关（IGW）</strong> - 一个官方 Kubernetes 项目，通过 AI 感知路由扩展了 Gateway API。</li>
<li><strong>Kubernetes</strong> - 用于部署和扩展的行业标准编排平台。</li>
</ul>
<p>通过整合这些技术，llm-d 使企业能够在混合云环境中大规模部署 LLM 推理，并实现最快的价值实现时间和极具竞争力的性价比。</p>
<h3 id="三项关键创新">三项关键创新</h3>
<p>传统的负载均衡采用简单的轮询（round-robin）路由，对所有服务器一视同仁。llm-d 的 <strong>vLLM 感知调度程序</strong>能够做出智能决策，将请求路由到具有匹配缓存前缀的实例，根据实例是处理计算密集型预填充操作还是内存密集型解码操作来分配负载，并使用来自 vLLM 的实时可观测数据来避免实例过载，同时优先处理低延迟路径。这种智能路由可将基础设施成本降低 30% 到 50%，同时保持延迟 SLO。</p>
<h4 id="解耦式服务disaggregated-serving">解耦式服务（Disaggregated Serving）</h4>
<p>llm-d 在生产规模上协调 vLLM 的本机分解服务（通过 KVConnector API），将预填充和解码分离到专门的工作节点中：</p>
<ul>
<li>
<p><strong>预填充工作节点</strong>：负责计算密集型的提示处理，使用高性能 GPU（如 H100、MI300X），并可根据需求独立扩展。</p>
</li>
<li>
<p><strong>解码工作节点</strong>：专注于受内存带宽限制的 token 生成任务，使用具成本效益的 GPU（如 A100、L40S），并根据并发会话数进行弹性扩缩。</p>
</li>
<li>
<p><strong>KV 缓存传输</strong>：使用 NVIDIA NIXL 通过 UCX 提供高效的缓存移动，支持卸载到存储后端（未来交付）以及跨集群的全局缓存感知。</p>
</li>
</ul>
<p>这允许适当规模的基础设施：昂贵的 GPU 仅用于预填充，成本优化的硬件可为数千个并发用户提供服务。</p>
<h4 id="分布式前缀缓存distributed-prefix-caching">分布式前缀缓存（Distributed Prefix Caching）</h4>
<p>llm-d 将 vLLM 的前缀缓存扩展到多个实例之间，通过两种方式实现：</p>
<ul>
<li>
<p><strong>本地缓存</strong>：在每个实例上将缓存卸载（offload）到内存或磁盘，几乎没有运维成本。</p>
</li>
<li>
<p><strong>共享缓存（计划中）</strong>：通过实例之间的 KV 传输实现共享，并使用全局索引实现集群范围的缓存感知。</p>
</li>
</ul>
<hr>
<h4 id="部署模式deployment-patterns">部署模式（Deployment Patterns）</h4>
<p>llm-d 支持多种先进的企业部署模式：</p>
<ul>
<li>
<p><strong>异构硬件</strong>：根据工作负载混合 GPU 供应商和代数——用于预填充的高端 GPU、用于解码的成本优化 GPU 或用于低频请求的 CPU 集群。</p>
</li>
<li>
<p><strong>动态扩缩</strong>：在高峰时段独立调整预填充容量，同时保持活动会话的稳定解码容量，并具有自动资源分配和故障转移功能。</p>
</li>
<li>
<p><strong>地理分布（规划中）</strong>：在主数据中心部署集中式预填充工作者，并在用户附近部署边缘解码工作者，以实现低延迟响应。</p>
</li>
</ul>
<hr>
<h2 id="多加速器与混合云支持">多加速器与混合云支持</h2>
<h3 id="摆脱硬件锁定">摆脱硬件锁定</h3>
<p>AI 加速器快速演进，使市场变得多样化。专优化 NVIDIA 的方案如 TensorRT-LLM 虽然性能优秀，但容易导致锁定。vLLM 的硬件中立设计则允许用户自由选择最适合的加速器。</p>
<h3 id="广泛硬件支持">广泛硬件支持</h3>
<p>vLLM 支持 NVIDIA GPU（针对 H100 进行一流的优化，支持 V100 及更新版本的所有 NVIDIA GPU）、AMD GPU（MI200、MI300 和 Radeon RX 7900 系列）、Google TPU（v4、v5p、v5e 和最新的 v6e）、AWS Inferentia 和 Trainium（trn1/inf2 实例）、Intel Gaudi（HPU）和 GPU（XPU）以及支持 x86、ARM 和 PowerPC <a href="https://blog.vllm.ai/2025/01/10/vllm-2024-wrapped-2025-vision.html">vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）</a>。</p>
<p>这种广泛支持带来以下战略优势：</p>
<h4 id="成本优化">成本优化</h4>
<ul>
<li>选择 AMD MI300X，在特定工作负载下实现高性价比。</li>
<li>使用 AWS Inferentia，在 AWS 上进行经济高效的推理。</li>
<li>在低吞吐量用例中，可部署在现有 CPU 基础架构上。</li>
</ul>
<h4 id="供应链弹性">供应链弹性</h4>
<ul>
<li>避免依赖单一 GPU 供应商的可用性。</li>
<li>通过多种选择协商更优惠的价格。</li>
<li>根据区域硬件可用性进行调整。</li>
</ul>
<h4 id="工作负载匹配">工作负载匹配</h4>
<ul>
<li>NVIDIA H100 可实现最佳性能。</li>
<li>AMD GPU 适用于开源部署。</li>
<li>TPU 适用于 Google Cloud 部署。</li>
<li>Intel Gaudi 适用于特定企业协议。</li>
</ul>
<h3 id="混合云部署模式">混合云部署模式</h3>
<p>在企业级产品及解决方案中，vLLM 支持真正的混合云：</p>
<h4 id="本地核心">本地核心</h4>
<ul>
<li>在本地基础架构上处理敏感数据。</li>
<li>满足合规性要求的工作负载。</li>
<li>可预测的基线负载容量。</li>
</ul>
<h4 id="云爆发式扩展">云爆发式扩展</h4>
<ul>
<li>利用云资源处理峰值负载。</li>
<li>试用新硬件（H100s、TPU v6e）。</li>
<li>无需基础设施投资即可实现地域扩展。</li>
</ul>
<h4 id="边缘推理">边缘推理</h4>
<ul>
<li>部署在适合边缘计算的硬件上。</li>
<li>CPU 或更小的 GPU 推理。</li>
<li>与集中管理集成。</li>
</ul>
<hr>
<h2 id="统一运维跨环境">统一运维跨环境</h2>
<p>借助企业级产品的相关功能，用户可从单一控制平面管理全部部署（公有云 + 私有云），主要从一下几个方面考虑。</p>
<ul>
<li><strong>单一控制平面</strong>：通过统一界面管理所有部署。</li>
<li><strong>一致的 API</strong>：跨环境实现相同的应用程序集成。</li>
<li><strong>统一监控</strong>：跨混合部署的聚合指标。</li>
<li><strong>策略执行</strong>：一致的安全和合规策略。</li>
</ul>
<hr>
<h2 id="模型生态与兼容性">模型生态与兼容性</h2>
<h3 id="支持广泛的开源模型体系">支持广泛的开源模型体系</h3>
<p>随着人工智能“淘金热”步伐的加快，vLLM 已发展到支持<a href="https://docs.vllm.ai/en/stable/models/supported_models.html#text-generation">超过 100 种模型架构</a>的高性能推理：涵盖几乎所有著名的开源大型语言模型（LLM）、多模态模型（图像、音频、视频）、编码器-解码器模型、推测解码模型、分类模型、嵌入模型和奖励模型。<a href="https://blog.vllm.ai/2025/01/10/vllm-2024-wrapped-2025-vision.html">vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）</a>。与专注于有限模型系列的专业解决方案相比，这种全面的支持具有根本性的优势。</p>
<h3 id="不只是传统-llm">不只是传统 LLM</h3>
<p>vLLM 的架构支持更多样的模型类型：</p>
<h4 id="语言模型">语言模型</h4>
<ul>
<li>Llama 系列。</li>
<li>Mistral 和 Mixtral 多模态模型。</li>
<li>Google 的 Gemma 模型。</li>
<li>IBM 的 Granite 系列。</li>
<li>阿里巴巴的 Qwen 模型。</li>
</ul>
<h4 id="多模态模型">多模态模型</h4>
<ul>
<li>视觉-语言模型（LLaVA, Qwen-VL）。</li>
<li>文档理解模型。</li>
<li>音频-语言模型。</li>
<li>视频理解能力。</li>
</ul>
<h4 id="专用架构">专用架构</h4>
<ul>
<li>状态空间语言模型的生产支持，探索非 Transformer 语言模型的未来。<a href="https://blog.vllm.ai/2025/01/10/vllm-2024-wrapped-2025-vision.html">vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）</a>。</li>
<li>混合专家（MoE）模型。</li>
<li>编码器-解码器架构。</li>
<li>嵌入和重排序模型。</li>
</ul>
<hr>
<h2 id="模型集成简易流程">模型集成简易流程</h2>
<p>将新模型加入 vLLM 通常包括以下步骤：</p>
<ol>
<li><strong>模型架构定义</strong>：使用熟悉的 PyTorch 模式实现。</li>
<li><strong>Attention 后端集成</strong>：利用现有的优化内核。</li>
<li><strong>Tokenizer 支持</strong>：兼容 Direct Hugging Face。</li>
<li><strong>配置映射</strong>：基于标准 YAML 的配置。</li>
</ol>
<p>这样的标准化流程意味着组织能更快将新模型投入生产。例如，在 2025 年 8 月 GPT-OSS 发布后不久，vLLM 的 <a href="https://github.com/vllm-project/vllm/releases/tag/v0.11.0">v0.11.0 版本</a>就加入了对其生产部署的支持。</p>
<hr>
<h2 id="与-hugging-face-生态整合">与 Hugging Face 生态整合</h2>
<p>vLLM 与 Hugging Face 的原生兼容带来了便捷性：</p>
<h3 id="直接模型加载">直接模型加载</h3>
<ul>
<li>直接从 Hugging Face Hub 和/或兼容 S3 的对象存储后端（例如 MinIO）加载模型。</li>
<li>支持私有模型库。</li>
<li>自动权重转换处理。</li>
</ul>
<h3 id="分词器兼容性">分词器兼容性</h3>
<ul>
<li>使用现有的分词器实现。</li>
<li>支持自定义分词器。</li>
<li>快速分词器优化</li>
</ul>
<h3 id="配置保存">配置保存</h3>
<ul>
<li>尊重特定于模型的配置。</li>
<li>支持自定义模型参数。</li>
<li>兼容经过微调的变体。</li>
</ul>
<hr>
<h2 id="生产级模型管理">生产级模型管理</h2>
<p>在一些企业级产品和方案中增加了模型生命周期的企业功能：</p>
<h3 id="模型注册集成">模型注册集成</h3>
<ul>
<li>已部署模型的版本控制。</li>
<li>A/B 测试功能。</li>
<li>回滚机制。</li>
</ul>
<h3 id="模型监控">模型监控</h3>
<ul>
<li>每个模型的性能跟踪。</li>
<li>使用情况分析和成本归因。</li>
<li>偏差检测功能。</li>
</ul>
<hr>
<h2 id="面向未来的模型支持">面向未来的模型支持</h2>
<p>模型创新速度极快，推理平台必须具备适应能力：</p>
<h3 id="社区贡献">社区贡献</h3>
<ul>
<li>活跃的社区不断添加新模型。</li>
<li>供应商中立的开发流程。</li>
<li>快速集成突破性架构。</li>
</ul>
<h3 id="灵活的架构">灵活的架构</h3>
<ul>
<li>模块化设计支持新的模型范式。</li>
<li>不受特定模型假设的约束。</li>
<li>为后转型架构做好准备。</li>
</ul>
<h3 id="企业验证">企业验证</h3>
<ul>
<li>企业的测试和认证流程。</li>
<li>模型工件的安全扫描。</li>
<li>跨硬件性能验证。</li>
</ul>
<p>这种全面的模型支持确保组织能够在新模型出现时立即采用它们，而无需进行平台迁移或架构更改，这是人工智能领域持续快速发展的一项关键能力。</p>
<hr>
<h2 id="使用企业级产品进行企业部署">使用企业级产品进行企业部署</h2>
<h3 id="从第一天起就是生产级">从第一天起就是生产级</h3>
<p>大多数企业级产品提供了一个灵活、可扩展的 MLOps 平台，提供构建、部署和管理 AI 应用的工具。它采用开源技术构建，为团队提供值得信赖且操作一致的功能，用于实验、模型服务和交付创新应用。了解更多关于企业级产品如何应对这一挑战的信息。</p>
<h3 id="与-kserve-的整合智能模型服务">与 KServe 的整合：智能模型服务</h3>
<p>在许多产品和方案中对 vLLM 与 KServe 的集成提供了企业级服务功能：</p>
<h4 id="genai-功能">GenAI 功能</h4>
<ul>
<li>使用 vLLM 服务运行时进行多节点/多 GPU 推理。</li>
<li>通过 vLLM + LMCache 集成卸载键值缓存。</li>
<li>通过模型缓存高效重用模型。</li>
<li>集成 KEDA，允许基于外部指标自动扩缩。</li>
<li>通过与 Envoy AI Gateway 集成实现速率限制和请求路由。</li>
<li>通过 LLMInferenceService CRD 访问 llm-d 功能。</li>
</ul>
<h4 id="高级自动扩缩">高级自动扩缩</h4>
<ul>
<li>基于请求的扩缩，实现资源利用率优化。</li>
<li>缩减​​至零功能，实现成本优化。</li>
<li>基于流量模式的预测性扩缩。</li>
<li>多指标扩缩（GPU 利用率、队列深度、延迟）。</li>
</ul>
<h4 id="流量管理">流量管理</h4>
<ul>
<li>金丝雀部署，确保模型更新安全。</li>
<li>蓝绿部署，实现即时回滚。</li>
<li>A/B 测试，用于模型比较。</li>
<li>影子流量，用于验证。</li>
</ul>
<h4 id="服务网格集成">服务网格集成</h4>
<ul>
<li>使用 Istio 进行端到端加密。</li>
<li>高级路由和负载均衡。</li>
<li>熔断和重试逻辑。</li>
<li>用于调试的分布式跟踪。</li>
</ul>
<h4 id="自动化运维">自动化运维</h4>
<ul>
<li>健康检查和自动恢复。</li>
<li>资源优化建议。</li>
<li>自动化证书管理。</li>
<li>日志聚合和分析。</li>
</ul>
<h3 id="安全与合规">安全与合规</h3>
<p>在企业部署中，必须重视安全：</p>
<h4 id="访问控制">访问控制</h4>
<ul>
<li>RBAC 与企业身份提供商集成。</li>
<li>模型级访问权限。</li>
<li>API 密钥管理。</li>
<li>所有操作的审计日志记录。</li>
</ul>
<h4 id="数据保护">数据保护</h4>
<ul>
<li>静态和传输中加密。</li>
<li>私有端点选项。</li>
<li>网络策略实施。</li>
<li>合规性报告工具。</li>
</ul>
<h4 id="供应链安全">供应链安全</h4>
<ul>
<li>签名容器镜像。</li>
<li>SBOM（软件物料清单）生成。</li>
<li>漏洞扫描。</li>
<li>基于策略的部署控制。</li>
</ul>
<h3 id="mlperf-验证的性能">MLPerf 验证的性能</h3>
<p>一些企业级解决方案和产品与众多知名服务器原始设备制造商（OEM）合作，使用搭载 NVIDIA GPU 和 vLLM 推理运行时的产品发布了令人印象深刻的 MLPerf 推理结果，在应对这一挑战方面取得了重大进展。加速生成式 AI 的采用，这些结果验证了：</p>
<ul>
<li>规模化生产级性能。</li>
<li>高效的资源利用率。</li>
<li>负载下一致的延迟。</li>
<li>多实例协调能力。</li>
</ul>
<h3 id="集成可观测性">集成可观测性</h3>
<p>无需额外工具即可进行全面监控：</p>
<h4 id="指标和仪表板">指标和仪表板</h4>
<ul>
<li>预建 Grafana 仪表板，用于 vLLM 指标。</li>
<li>预建 Grafana 仪表板，用于驱动路由决策的请求调度程序指标。</li>
<li>令牌生成率和延迟。</li>
<li>GPU 利用率和内存使用率。</li>
<li>队列深度和拒绝率。</li>
</ul>
<h4 id="警报和响应">警报和响应</h4>
<ul>
<li>SLA 违规自动警报。</li>
<li>与企业监控系统集成。</li>
<li>Runbook 自动化功能。</li>
<li>容量规划洞察。</li>
</ul>
<h3 id="成本管理与优化未来交付">成本管理与优化（未来交付）</h3>
<p>专为企业成本控制设计的功能：</p>
<ul>
<li><strong>计费/显示</strong>：按团队或项目跟踪使用情况。</li>
<li><strong>资源配额</strong>：防止成本失控。</li>
<li><strong>竞价型实例支持</strong>：降低批量工作负载的成本。</li>
<li><strong>空闲检测</strong>：自动缩减未使用的资源。</li>
</ul>
<p>这个企业级平台将 vLLM 从高性能推理引擎转变为完整的生产解决方案，可用于关键任务部署。</p>
<hr>
<h2 id="特性对比vllm-vs-tgi-vs-tensorrt-llm">特性对比：vLLM vs TGI vs TensorRT-LLM</h2>
<p>✓ = 全面支持，◐ = 部分/有限支持，✗ = 不支持</p>
<p><img alt="vLLM vs TGI vs TensorRT-LLM" loading="lazy" src="/zh/blog/2025/the-best-ai-inference-vllm/4-vllm-tgi-tensorrt-llm.png"></p>
<hr>
<h2 id="企业级产品与开源-vllm-的主要区别">企业级产品与开源 vLLM 的主要区别</h2>
<ul>
<li>硬件灵活性：最广泛的加速器支持，包括 AMD、Intel、Google TPU 和 CPU。</li>
<li>模型生态系统：支持超过 100 种模型架构，而其他替代方案仅有 25-40 种。</li>
<li>分布式架构：即将推出的 llm-d 支持分解式预填充/解码和系统级 kv-cache 路由，以实现分布式最优扩展。</li>
<li>企业集成：企业级产品集成 KServe 自动扩展。</li>
<li>内存效率：先进的 PagedAttention 和 KV-Cache 管理。</li>
<li>开放开发：PyTorch 基金会项目，社区创新迅速。</li>
</ul>
<hr>
<h2 id="如何作出正确选择">如何作出正确选择</h2>
<p>文中给出一个简单的决策框架：对每个判断点打 “Yes”：</p>
<ul>
<li><strong>0–2 个 “Yes”</strong>：可考虑专用方案（若其满足你的特定需求）</li>
<li><strong>3–5 个 “Yes”</strong>：vLLM 在你的场景中将提供显著优势</li>
<li><strong>6+ 个 “Yes”</strong>：基于 vLLM 的企业级产品是战略性选择</li>
</ul>
<p>每个决策点都涉及基本的架构约束：</p>
<ul>
<li><strong>灵活性要求</strong>决定您是否需要与硬件无关的解决方案。</li>
<li><strong>运营复杂性</strong>评估简化的运营是否值得采用开源技术。</li>
<li><strong>模型与创新</strong>评估快速发展是否需要一个适应性强的平台。</li>
<li><strong>长期可持续性</strong>考虑总体拥有成本和战略风险。</li>
</ul>
<hr>
<h2 id="战略推荐">战略推荐</h2>
<p>如果你对多个维度都倾向 “Yes”，那 vLLM + 企业级产品将提供：</p>
<ul>
<li><strong>最大的灵活性</strong>：可在任何受支持的硬件上部署。</li>
<li><strong>优秀的运维体验</strong>：企业级平台，复杂性极低。</li>
<li><strong>面向未来的能力</strong>：支支持新兴模型和架构。</li>
<li><strong>成本优化</strong>：为每种工作负载选择最佳硬件。</li>
<li><strong>风险控制</strong>：开源基础，提供企业级支持。</li>
</ul>
<p>这些因素的融合使得 vLLM 成为构建可持续人工智能基础设施的组织的战略选择。</p>
<hr>
<h2 id="采纳步骤建议">采纳步骤建议</h2>
<h3 id="立即阶段">立即阶段</h3>
<ul>
<li>在开发环境部署 vLLM + 企业级产品</li>
<li>用自己的模型和工作负载进行测试</li>
<li>验证硬件灵活性与性能</li>
</ul>
<h3 id="短期阶段">短期阶段</h3>
<ul>
<li>实施 KServe 的自动扩缩能力</li>
<li>建立监控与可观察性体系</li>
<li>培训团队操作流程</li>
</ul>
<h3 id="中期阶段">中期阶段</h3>
<ul>
<li>设计生产部署架构</li>
<li>若需要，实施混合云模式</li>
<li>为 llm-d 分布式架构做准备</li>
</ul>
<h3 id="长期阶段">长期阶段</h3>
<ul>
<li>优化成本（选硬件、资源调度）</li>
<li>引入更高级部署模式</li>
<li>将自己的改进贡献给社区</li>
</ul>
<hr>
<h2 id="总结">总结</h2>
<p>选择 LLM 推理平台代表着一项战略承诺，它将影响您组织未来几年的人工智能能力。</p>
<p>我们的分析表明，企业级产品上的 vLLM 独特地满足了企业 LLM 部署的三个关键要求：</p>
<ol>
<li><strong>灵活性</strong>：可在任意硬件与混合云环境部署</li>
<li><strong>可扩展性</strong>：先进内存管理与 llm-d 架构使资源利用效率提升 10–100 倍</li>
<li><strong>可持续性</strong>：开源基础 + 企业支持，避免陷入锁定</li>
</ol>

<blockquote>
  <p>虽然 TensorRT-LLM 提供了 NVIDIA 专属优化，TGI 也提供了 Hugging Face 集成，但“只有 vLLM 能够提供快速发展的 AI 领域所需的架构灵活性”。
凭借对 100 多种模型架构的支持、与硬件无关的设计以及 PyTorch 和 Linux 基金会的支持，-&gt;“搭载 vLLM 的企业级产品为长期成功奠定了最坚实的基础”。</p>

</blockquote>
]]></content:encoded></item><item><title>玩转大语言模型：微软最新开源长语音大模型 VibeVoice 入门</title><link>https://wilsonwu.me/zh/blog/2025/microsoft-vibevoice/</link><pubDate>Thu, 18 Sep 2025 00:00:00 -0400</pubDate><guid>https://wilsonwu.me/zh/blog/2025/microsoft-vibevoice/</guid><description>本文介绍使用微软最新开源的 VibeVoice 大模型，在本地进行推理实现长文本，多角色文字转语音。</description><content:encoded><![CDATA[<h2 id="什么是-vibevoice">什么是 VibeVoice？</h2>
<p><strong><a href="https://microsoft.github.io/VibeVoice/">VibeVoice</a></strong> 是 Microsoft Research 发布的一套面向<strong>长篇、多说话人、对话式</strong>语音合成的研究框架，目标场景例如整集播客、有声剧或访谈：能在单次生成中维持说话人一致性并处理自然的换手（turn-taking）。模型家族包含多个规模（例如 1.5B、7B 等），并在 Hugging Face 上以 <a href="https://huggingface.co/microsoft/VibeVoice-1.5B"><code>microsoft/VibeVoice-1.5B</code></a> 形式开放（模型卡、模型文件、model card 中有安装/使用与责任使用说明）。</p>
<p>它解决了传统 TTS（Text-To-Speech）系统的一些痛点，比如：</p>
<ul>
<li>难以维持<strong>长时间</strong>对话的语音一致性（speaker consistency）；</li>
<li>多说话人的切换自然性（turn-taking）差；</li>
<li>效率低 — 长文本 + 多说话人时，资源消耗大。</li>
</ul>
<h2 id="核心创新与架构">核心创新与架构</h2>
<p>VibeVoice 有几个比较新的或者关键的技术设计：</p>
<table>
	<thead>
			<tr>
					<th>组件</th>
					<th>功能 / 目的</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Continuous Speech Tokenizers</strong>（声学 + 语义两种）</td>
					<td>用来把音频压缩成低帧率（7.5 Hz）表示，同时保留语义与音质信息。声学 token 与语义 token 分别负责声音细节和内容表达。</td>
			</tr>
			<tr>
					<td><strong>LLM 基础模型</strong>（Large Language Model）</td>
					<td>在该版本里用的是 Qwen2.5-1.5B，用来处理文本、说话人信息以及上下文对话流。</td>
			</tr>
			<tr>
					<td><strong>Diffusion Head</strong></td>
					<td>对声学 VAE 的特征进行预测，是生成高保真声音细节的模块。这个模块较轻 (大致 4 层结构)，在推理阶段使用 diffusion 的技术（包括去噪等）。</td>
			</tr>
			<tr>
					<td><strong>上下文长度 &amp; 多说话人</strong></td>
					<td>支持高达 <strong>90 分钟</strong>语音生成，最多 <strong>4 个</strong>说话人。</td>
			</tr>
	</tbody>
</table>
<p>架构图如下：</p>
<p><img alt="VibeVoice 架构" loading="lazy" src="/zh/blog/2025/microsoft-vibevoice/vibevoice-arch.jpg"></p>
<h2 id="优点和局限">优点和局限</h2>
<h3 id="优点">优点</h3>
<ol>
<li><strong>长篇幅对话能力</strong> — 能生成近 90 分钟的连续对话，并维持说话人一致性。</li>
<li><strong>多说话人支持</strong> — 最多支持 4 个不同说话人的切换，且对话流程自然。</li>
<li><strong>压缩效率高</strong> — 用 7.5 Hz 帧率的 speech tokenizer，音频被高度压缩但仍保证质量。</li>
<li><strong>语义 + 声学分离</strong> — 语义 tokenizer（semantic）负责内容，声学 tokenizer（acoustic）负责声音细节，两者协同。</li>
</ol>
<h3 id="局限">局限</h3>
<ol>
<li><strong>语言支持有限</strong> — 目前主要是英语与中文，跨语言能力还在研究中。</li>
<li><strong>背景音乐 / 音效不支持</strong> — 当前专注纯语音合成，没有加入背景音或重叠说话人（speaker overlap）。</li>
<li><strong><a href="https://github.com/microsoft/VibeVoice">仓库暂时关闭</a></strong> — 官方声明因 “部分用途超出初衷 / 不符合责任使用” 的情况，仓库暂时被 disable。意味着可能无法立即拿到源码或全部功能。</li>
<li><strong>硬件要求 / 资源消耗</strong> — 虽然压缩效率高，但生成长篇语音 + 多说话人 + 高保真度仍需要较好的 GPU 资源。</li>
<li><strong>潜在滥用风险</strong> — 用来做语音克隆、假扮、deepfake 等可能的恶意用途。官方在文档中也提到要谨慎。</li>
</ol>
<h2 id="快速上手">快速上手</h2>
<p>下面给出具体实现步骤及上手流程。注意：由于官方仓库目前不包含完整脚本，实际推理通常依赖社区实现或 Hugging Face Spaces / Colab 示例。所列步骤已参考 Hugging Face 模型页与社区示例仓库。</p>
<h3 id="先决条件">先决条件</h3>
<ul>
<li>
<p>Python 3.8+；建议在隔离虚拟环境（venv / conda）中操作。</p>
</li>
<li>
<p>GPU：带 CUDA 的 NVIDIA GPU（具体 VRAM 与性能见第 5 节）。若使用 Apple Silicon / macOS，可参见社区讨论与专门脚本。</p>
</li>
<li>
<p>安装工具（示例）：</p>
<ul>
<li><code>pip install huggingface_hub transformers accelerate torch soundfile torchaudio numpy einops</code></li>
<li>本地需要 <code>ffmpeg</code>（用于音频切分/拼接与格式转换）。</li>
</ul>

<blockquote>
  <p>具体版本请按 PyTorch 官网或社区脚本说明选择（不同 CUDA 版本的 wheel 不同）。参考社区仓库的 requirements 列表以确保兼容。</p>

</blockquote>
</li>
</ul>
<h3 id="下载模型权重">下载模型权重</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> huggingface_hub <span style="color:#f92672">import</span> snapshot_download
</span></span><span style="display:flex;"><span>model_dir <span style="color:#f92672">=</span> snapshot_download(<span style="color:#e6db74">&#34;microsoft/VibeVoice-1.5B&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;模型已下载到&#34;</span>, model_dir)
</span></span></code></pre></div><p>注意：<code>snapshot_download</code> 会把 HF 模型文件和模型卡同步拉取到本地；若模型文件较大，请确保磁盘与网络条件。</p>
<h3 id="使用社区推理脚本">使用社区推理脚本</h3>
<p>很多社区仓库或 Hugging Face Spaces 提供了 <code>demo/inference_from_file.py</code> 之类的脚本。示例命令：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 在 Colab / 本地环境中操作</span>
</span></span><span style="display:flex;"><span>git clone https://huggingface.co/technicalheist/vibevoice-1.5b
</span></span><span style="display:flex;"><span>cd vibevoice-1.5b
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 安装 Python 依赖（按模型页 “Requirements” 部分）</span>
</span></span><span style="display:flex;"><span>pip install torch transformers soundfile ffmpeg-python  <span style="color:#75715e"># plus 其他必要包</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 创建一个 transcript 文件，比如 transcript.txt</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Speaker 1: Hello, how are you doing?
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Speaker 2: I&#39;m good, thanks. What about you?&#34;</span> &gt; transcript.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 用 demo 脚本生成语音</span>
</span></span><span style="display:flex;"><span>python demo/inference_from_file.py <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --model_path /path/to/technicalheist/vibevoice-1.5b <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --txt_path transcript.txt <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --speaker_names <span style="color:#e6db74">&#34;Alice,Bob&#34;</span>
</span></span></code></pre></div><p>社区实现（例如 <code>technicalheist/vibevoice-1.5b</code>、<code>shijincai/VibeVoice</code> 等）提供了可运行的示例与依赖说明；使用前请仔细阅读其 README 并依据其中的依赖和指引来配置环境。示例脚本名与参数在不同实现中可能不同，请以具体仓库中的文档为准。</p>
<h3 id="简单示例">简单示例</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Speaker 1: Hello, welcome to our podcast.
</span></span><span style="display:flex;"><span>Speaker 2: Thanks — happy to be here. Let&#39;s talk about VibeVoice.
</span></span><span style="display:flex;"><span>Speaker 1: Sure, start with the key idea...
</span></span></code></pre></div><p>将上面保存为 <code>transcript.txt</code> 并传入 <code>--txt_path</code> 即可（脚本会解析说话人标记并输出对应音轨）。社区脚本通常会把每个说话人的音色/说话风格作为参数或示例内置。</p>
<h2 id="结论">结论</h2>
<p>微软的 VibeVoice 是当前 TTS / 语音合成领域一个非常令人兴奋的进展。它在<strong>长篇幅语音 + 多说话人</strong>上取得了突破，尤其是在对话流畅性和说话人一致性方面，技术设计（如低帧率的双 tokenizers + next-token diffusion + LLM 架构）也很前沿。</p>
<p>如果你正在做播客、有声书或者对话剧本的语音合成，这可能是一个非常有用的工具／研究基础。不过在商业部署或者包含敏感语音身份／法律边界用途时，需要格外注意责任使用，以及是否有完整的许可与风险评估。</p>
]]></content:encoded></item><item><title>玩转大语言模型：使用 SGLang 框架实现大语言模型推理入门教程</title><link>https://wilsonwu.me/zh/blog/2025/getting-started-with-sglang/</link><pubDate>Thu, 10 Jul 2025 00:00:00 -0400</pubDate><guid>https://wilsonwu.me/zh/blog/2025/getting-started-with-sglang/</guid><description>这篇文章带你从零开始，快速上手 SGLang：了解它是什么、为什么快、如何安装与启动、如何用 OpenAI 兼容接口调用，并通过一个完整的 Demo（含流式输出与并发调用）一步步跑通。文章面向刚接触 SGLang 的同学，几分钟内即可看到本地模型在线响应。</description><content:encoded><![CDATA[<p>随着大语言模型热度的升级，企业和个人使用者的研究重心逐步从训练转移至推理（说白了是由造轮子转变为务实的使用）。而在模型推理领域，最炙手可热的两大框架无疑是 vLLM 和 SGLang，而作为后起之秀的 SGLang，其表现也值得大家关注，今天就基于 SGLang 为大家带来一篇入门教程文章，希望能帮助更多希望了解大语言模型及 SGLang 框架的朋友。</p>
<h2 id="sglang-简介">SGLang 简介</h2>
<p>SGLang 是一款面向大语言模型（LLM）和视觉语言模型（VLM）的高性能推理框架，通过精心设计的后端运行时与前端语言协同工作，使模型交互更加高效且可控。其核心优势包括：</p>
<ul>
<li><strong>高效后端运行时</strong>：采用创新的 RadixAttention 技术实现前缀缓存，支持跳跃式受限解码、零开销 CPU 调度、连续批处理、令牌注意力（分页注意力）、张量并行、FlashInfer 内核、分块预填充以及多种量化技术（FP8/INT4/AWQ/GPTQ），显著提升推理效率。</li>
<li><strong>灵活前端语言</strong>：提供直观且强大的 LLM 编程接口，支持链式生成调用、高级提示工程、复杂控制流、多模态输入、并行处理及外部系统交互。</li>
<li><strong>广泛模型兼容性</strong>：支持多种主流生成式模型（Llama、Gemma、Mistral、QWen、DeepSeek、LLaVA 等）、嵌入模型（e5-mistral、gte）及奖励模型（Skywork），并提供简便的新模型扩展机制。</li>
<li><strong>活跃开源生态</strong>：拥有蓬勃发展的社区支持，已获得广泛业界认可（截至 2025 年 3 月 17 日，GitHub 累计星标超过 12,000）。</li>
</ul>
<p>其技术架构如下：</p>
<p><img alt="SGLang 架构" loading="lazy" src="/zh/blog/2025/getting-started-with-sglang/1-sglang-architecture.png"></p>
<p>除此之外，对于初学者，需要了解其以下特性：</p>
<ul>
<li><strong>OpenAI 兼容 API</strong>：直接用 openai Python SDK 或 cURL 调用，不用改你的上层业务代码。</li>
<li><strong>高吞吐与低延迟</strong>：结合连续批处理与前缀缓存等技巧，让相同前缀的请求复用计算。</li>
<li><strong>生产友好</strong>：支持多并发、流式输出、可与 Hugging Face 模型库直接对接。</li>
</ul>
<h2 id="环境准备">环境准备</h2>
<p>工欲善其事必先利其器，要完成本文的新手实验，需准备如下环境：</p>
<ul>
<li>操作系统：建议 Linux（常用为 Ubuntu 20.04+）。WSL2 也可尝试。</li>
<li>Python：建议 3.10 或 3.11。</li>
<li>GPU：建议 NVIDIA 显卡，24GB 显存可跑 7B/8B 级（如 Llama 3.1 8B）。没有 GPU 也能跑小模型或量化模型，但性能有限。</li>
<li>模型来源：Hugging Face（如 meta-llama/Llama-3.1-8B-Instruct）或本地路径。</li>
</ul>
<h2 id="安装-sglang">安装 SGLang</h2>
<p>环境就绪后，使用 pip 安装 SGLang（确保已安装 pip、可选创建虚拟环境）。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>pip install sglang openai huggingface<span style="color:#f92672">-</span>hub
</span></span></code></pre></div><p>如果你需要从 Hugging Face 自动下载私有或受限模型，请先登录或设置 Token：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>huggingface-cli login
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 或</span>
</span></span><span style="display:flex;"><span>export HF_TOKEN<span style="color:#f92672">=</span>您的Token
</span></span></code></pre></div><h2 id="启动-sglang-服务">启动 SGLang 服务</h2>
<p>可以通过以下两种启动方式进行 SGLang 服务的启动。不同版本的 SGLang 命令行参数可能略有差异，如果出现参数名称不匹配，先执行 &ndash;help 查看本机版本支持的参数。</p>
<h3 id="通过-launcher-启动">通过 launcher 启动</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>python -m sglang.launcher <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --model meta-llama/Llama-3.1-8B-Instruct <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --host 0.0.0.0 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --port <span style="color:#ae81ff">30000</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --tp <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --max-model-len <span style="color:#ae81ff">8192</span>
</span></span></code></pre></div><p>其中常用参数说明如下：</p>
<ul>
<li>&ndash;model：Hugging Face 模型名称或本地模型权重路径</li>
<li>&ndash;host/&ndash;port：服务监听地址与端口</li>
<li>&ndash;tp：Tensor Parallel 并行度（单卡设为 1；多卡可根据需要设为 2、4 等）</li>
<li>&ndash;max-model-len：上下文最大长度</li>
</ul>
<p>另外需要注意以下两点：</p>
<ul>
<li>如果使用的是 Hugging Face 模型，首次运行会下载模型权重，请确保网络通畅。</li>
<li>如果显存吃紧，可优先尝试更小模型，或减少 &ndash;max-model-len。</li>
</ul>
<h3 id="通过-sglang-命令启动">通过 sglang 命令启动</h3>
<p>如果您使用的版本提供 sglang serve 命令，也可使用如下命令进行启动：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sglang serve -m meta-llama/Llama-3.1-8B-Instruct --host 0.0.0.0 --port <span style="color:#ae81ff">30000</span>
</span></span></code></pre></div><h2 id="快速验证">快速验证</h2>
<p>服务启动后，OpenAI 兼容接口即可使用：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl http://127.0.0.1:30000/v1/chat/completions <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -H <span style="color:#e6db74">&#34;Content-Type: application/json&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -H <span style="color:#e6db74">&#34;Authorization: Bearer EMPTY&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -d <span style="color:#e6db74">&#39;{
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;model&#34;: &#34;meta-llama/Llama-3.1-8B-Instruct&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;messages&#34;: [
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      {&#34;role&#34;: &#34;system&#34;, &#34;content&#34;: &#34;You are a helpful assistant.&#34;},
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      {&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;用两句话介绍一下你自己。&#34;}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    ],
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;max_tokens&#34;: 128,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;temperature&#34;: 0.2
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  }&#39;</span>
</span></span></code></pre></div><p>如果运行正常，将返回标准的 ChatCompletion JSON 响应。</p>
<h2 id="使用-python-openai-sdk-进行调用">使用 Python OpenAI SDK 进行调用</h2>
<p>非流式调用（最简单的问答）：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> openai <span style="color:#f92672">import</span> OpenAI
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>client <span style="color:#f92672">=</span> OpenAI(
</span></span><span style="display:flex;"><span>    base_url<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://127.0.0.1:30000/v1&#34;</span>,
</span></span><span style="display:flex;"><span>    api_key<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;EMPTY&#34;</span>  <span style="color:#75715e"># 本地服务通常不校验</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>messages <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    {<span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;system&#34;</span>, <span style="color:#e6db74">&#34;content&#34;</span>: <span style="color:#e6db74">&#34;You are a helpful assistant who answers in Chinese.&#34;</span>},
</span></span><span style="display:flex;"><span>    {<span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;user&#34;</span>, <span style="color:#e6db74">&#34;content&#34;</span>: <span style="color:#e6db74">&#34;请用三句话介绍帕累托最优。&#34;</span>}
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>resp <span style="color:#f92672">=</span> client<span style="color:#f92672">.</span>chat<span style="color:#f92672">.</span>completions<span style="color:#f92672">.</span>create(
</span></span><span style="display:flex;"><span>    model<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;meta-llama/Llama-3.1-8B-Instruct&#34;</span>,
</span></span><span style="display:flex;"><span>    messages<span style="color:#f92672">=</span>messages,
</span></span><span style="display:flex;"><span>    temperature<span style="color:#f92672">=</span><span style="color:#ae81ff">0.2</span>,
</span></span><span style="display:flex;"><span>    max_tokens<span style="color:#f92672">=</span><span style="color:#ae81ff">256</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(resp<span style="color:#f92672">.</span>choices[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>message<span style="color:#f92672">.</span>content)
</span></span></code></pre></div><p>流式输出（边生成边打印）：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> openai <span style="color:#f92672">import</span> OpenAI
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>client <span style="color:#f92672">=</span> OpenAI(base_url<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://127.0.0.1:30000/v1&#34;</span>, api_key<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;EMPTY&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>messages <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    {<span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;system&#34;</span>, <span style="color:#e6db74">&#34;content&#34;</span>: <span style="color:#e6db74">&#34;You are a helpful assistant that speaks Chinese.&#34;</span>},
</span></span><span style="display:flex;"><span>    {<span style="color:#e6db74">&#34;role&#34;</span>: <span style="color:#e6db74">&#34;user&#34;</span>, <span style="color:#e6db74">&#34;content&#34;</span>: <span style="color:#e6db74">&#34;简述 Transformer 的核心思想，并给出一个现实类比。&#34;</span>}
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>stream <span style="color:#f92672">=</span> client<span style="color:#f92672">.</span>chat<span style="color:#f92672">.</span>completions<span style="color:#f92672">.</span>create(
</span></span><span style="display:flex;"><span>    model<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;meta-llama/Llama-3.1-8B-Instruct&#34;</span>,
</span></span><span style="display:flex;"><span>    messages<span style="color:#f92672">=</span>messages,
</span></span><span style="display:flex;"><span>    temperature<span style="color:#f92672">=</span><span style="color:#ae81ff">0.2</span>,
</span></span><span style="display:flex;"><span>    max_tokens<span style="color:#f92672">=</span><span style="color:#ae81ff">300</span>,
</span></span><span style="display:flex;"><span>    stream<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> chunk <span style="color:#f92672">in</span> stream:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> chunk<span style="color:#f92672">.</span>choices <span style="color:#f92672">and</span> chunk<span style="color:#f92672">.</span>choices[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>delta:
</span></span><span style="display:flex;"><span>        delta <span style="color:#f92672">=</span> chunk<span style="color:#f92672">.</span>choices[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>delta<span style="color:#f92672">.</span>content
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> delta:
</span></span><span style="display:flex;"><span>            print(delta, end<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&#34;</span>, flush<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">[Done]&#34;</span>)
</span></span></code></pre></div><p>通过以上两种方式，我们可以构建自己的推理服务应用，也可以通过市面上开源的软件来接入您部署的推理服务，如 Open-WebUI 等。</p>
<h2 id="常见问题与排错">常见问题与排错</h2>
<ul>
<li>启动时报显存不足（CUDA OOM）
<ul>
<li>尝试更小的模型（如 7B/8B）、减小 <code>--max-model-len</code>、或降低并发。</li>
<li>如果有多卡，增加 &ndash;tp 并用多卡分摊显存。</li>
</ul>
</li>
<li>拉取模型失败或速度慢
<ul>
<li>先执行 <code>huggingface-cli login</code>；或提前手动把模型权重下载到本地路径，并在 <code>--model</code> 里填本地路径。</li>
</ul>
</li>
<li>API 报 <code>Unknown argument</code>
<ul>
<li>不同版本的命令行参数可能有差异。请运行：<code>python -m sglang.launcher --help</code></li>
<li>根据你安装的版本调整参数名，如 <code>--model</code> / <code>--model-path</code>、<code>--tp</code> / <code>--tensor-parallel-size</code> 等。</li>
</ul>
</li>
<li>Python 客户端 <code>401/Key</code> 错误
<ul>
<li>本地服务通常不校验 Key，只要带上一个占位即可（如 <code>EMPTY</code>）。</li>
</ul>
</li>
<li>流式输出不打印
<ul>
<li>确认传了 <code>stream=True</code>；迭代 chunk 时取 <code>choices[0].delta.content</code>。</li>
</ul>
</li>
<li>并发性能不达预期
<ul>
<li>尝试用 Async 客户端并发请求；避免在单线程里串行等待。</li>
<li>检查 GPU 利用率；确认没有 CPU/GPU 数据传输瓶颈。</li>
</ul>
</li>
</ul>
<h2 id="总结">总结</h2>
<p>至此，你已经完成了：</p>
<ul>
<li>安装与启动 SGLang 服务</li>
<li>用 OpenAI 兼容接口进行非流式与流式推理</li>
</ul>
<p>祝你上手顺利，玩得开心！如果遇到命令行参数或 API 的小差异，参考本地版本的 &ndash;help 与官方文档进行快速适配即可。</p>
]]></content:encoded></item></channel></rss>